前言
在没有读写锁之前,ReentrantLock 虽然可以保证了线程安全,但是也浪费了一定的资源,因为如果多个读操作同时进行,其实并没有线程安全问题,我们可以允许让多个读操作并行,以便提高程序效率。
但是写操作不是线程安全的,如果多个线程同时写,或者在写的同时进行读操作,便会造成线程安全问题。
我们的读写锁就解决了这样的问题,它设定了一套规则,既可以保证多个线程同时读的效率,同时又可以保证有写入操作时的线程安全。
整体思路是它有两把锁,第 1 把锁是写锁,获得写锁之后,既可以读数据又可以修改数据,而第 2 把锁是读锁,获得读锁之后,只能查看数据,不能修改数据。读锁可以被多个线程同时持有,所以多个线程可以同时查看数据。
在读的地方合理使用读锁,在写的地方合理使用写锁,灵活控制,可以提高程序的执行效率。
1.读写锁的获取规则
我们在使用读写锁时遵守下面的获取规则:
如果有一个线程已经占用了读锁,则此时其他线程如果要申请读锁,可以申请成功。
如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁,因为读写不能同时操作。
如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,都必须等待之前的线程释放写锁,同样也因为读写不能同时,并且两个线程不应该同时写。
一句话总结:要么是一个或多个线程同时有读锁,要么是一个线程有写锁,但是两者不会同时出现。也可以总结为:读读共享、其他都互斥(写写互斥、读写互斥、写读互斥)。
2.使用案例
下面我们举个例子来应用读写锁,ReentrantReadWriteLock 是 ReadWriteLock 的实现类,最主要的有两个方法:readLock() 和 writeLock() 用来获取读锁和写锁。
代码如下:
public class ReadWriteLockDemo {
private static final ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
private static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
private static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
public static void main(String[] args) {
new Thread(()->read()).start();
new Thread(()->read()).start();
new Thread(()->write()).start();
new Thread(()->write()).start();
}
private static void read() {
String threadName = Thread.currentThread().getName();
readLock.lock();
try {
System.out.println(threadName + "-获取读锁(readLock),读取数据...");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(threadName + "-释放读锁(readLock)");
readLock.unlock();
}
}
private static void write() {
String threadName = Thread.currentThread().getName();
writeLock.lock();
try {
System.out.println(threadName + "-获取写锁(writeLock),写入数据...");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(threadName + "-释放读锁(writeLock)");
writeLock.unlock();
}
}
}
执行结果:
Thread-0-获取读锁(readLock),读取数据...
Thread-1-获取读锁(readLock),读取数据...
Thread-0-释放读锁(readLock)
Thread-1-释放读锁(readLock)
Thread-2-获取写锁(writeLock),写入数据...
Thread-2-释放写锁(writeLock)
Thread-3-获取写锁(writeLock),写入数据...
Thread-3-释放写锁(writeLock)
可以看到
线程0和线程1都获得了读锁,读取数据;
但是线程2获得写锁之后,线程3在线程2释放了写锁之后,才获取写锁,写入数据。
3.读写锁适用场合
最后我们来看下读写锁的适用场合,相比于 ReentrantLock 适用于一般场合,ReadWriteLock 适用于读多写少的情况,合理使用可以进一步提高并发效率。