一、概念
ReadWriteLock:见词知意,读写锁。正常业务中读操作往往比写操作更多,然后用独占锁去加锁,就会造成同样的读操作,只能排队等待,业务执行效率会降低,JDK1.5之后添加了读写锁ReadWriteLock,是一个接口,提供了readLock和writeLock两种锁的操作机制,一个读锁(共享锁),一个是写锁(排它锁)。特性为:读读共享,读写互斥,写写互斥。
二、原理
ReadWriteLock底层也是基于AQS实现的,独占锁往往用int类型的全局变量state(0或者>0)来判断当前锁是否被占用,而共享锁是有读锁和写锁两种,并且锁的资源还是一个,如何区分?实现方式就是将一位int类型的4个字节(32位)分为高16位和低16位,高16位用来表示读锁状态,低16位用来表示写锁状态。读锁的加锁释放锁对高16位进行操作(+|-),写锁的加锁释放锁对低16位进行操作(+|-)。这样就能判读当前读写锁的状态和数量,由于只占16位,所以读写锁数量限制都为2^16-1。
获取写锁数量:当前状态值&0x0000FFFF,结果为0,无写锁,>0为写锁重入次数。写锁+1就是当前状态值+1,写锁-1就是当前状态值-1。
获取读锁数量:当前状态值>>>16,右移16位,得到的结果就是读锁的数量。
读锁+1就是,当前状态值 +(1<<16),-1就是S - (1<<16),超出会抛出异常。
写锁获取锁:
1.首先通过当前状态值,判断锁是否被占用,如果状态值为0,锁资源当前未被占用,cas自旋修改状态值,修改成功获取锁。
2.当前状态值为不为0,说明锁资源被占用,通过当前状态值&0x0000FFFF结果w判断当前写锁数量,如果w为0,则说明锁资源被读锁占用,竞争锁失败。如果w不为0,则判断持有锁的线程是否为当前线程,如果不是,竞争锁失败。如果是则竞争锁成功,执行逻辑,因为读写锁是可重入的。
读锁获取锁:
1.首先通过当前状态值,判断锁资源是否被占用,如果状态值为0,锁资源当前未被占用,cas自旋修改状态值,修改成功获取锁。
2.当前状态值为不为0,说明锁资源被占用,通过当前状态值&0x0000FFFF结果w判断当前写锁数量:
如果w>0:判断当前持有锁线程,是否为当前线程,如果是cas竞争锁,如果不是竞争锁失败。
如果w=0:判断是否超出最大值,是否需要等待,是否修改成功状态值,全成功获取所成功。
三、使用
1.读读共享:
public static void main(String[] args) {
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
//读锁1
Lock readLock1 = readWriteLock.readLock();
//读锁2
Lock readLock2 = readWriteLock.readLock();
new Thread(() -> {
readLock1.lock();
System.out.println("模拟读1操作开始");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("模拟读1操作结束");
readLock1.unlock();
}).start();
new Thread(() -> {
readLock2.lock();
System.out.println("模拟读2操作开始");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("模拟读2操作结束");
readLock2.unlock();
}).start();
}
2.读写互斥:
public static void main(String[] args) {
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
//读锁
Lock read = readWriteLock.readLock();
//写锁
Lock write = readWriteLock.writeLock();
new Thread(() -> {
read.lock();
System.out.println("模拟读操作开始");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("模拟读操作结束");
read.unlock();
}).start();
new Thread(() -> {
write.lock();
System.out.println("模拟写操作开始");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("模拟写操作结束");
write.unlock();
}).start();
}
3.写写互斥:
public static void main(String[] args) {
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
//读锁
Lock write1 = readWriteLock.readLock();
//写锁
Lock write2 = readWriteLock.writeLock();
new Thread(() -> {
write1.lock();
System.out.println("模拟写1操作开始");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("模拟写1操作结束");
write1.unlock();
}).start();
new Thread(() -> {
write2.lock();
System.out.println("模拟写2操作开始");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("模拟写操作结束");
write2.unlock();
}).start();
}
注意:读写锁支持锁降级,不支持锁升级:
锁降级:线程获取到了写锁,未释放写锁的情况下,获取读锁,是可以的。线程先拥有写锁,其他线程获取不到锁,无风险。
锁升级:线程获取到了读锁,在没有释放读锁的前提下,又获取写锁。如t1,t2都获取到读锁,在t1想要获取写锁时,t2有读锁,获取失败,t2想获取写锁时,t1有读锁,获取失败,造成死锁。
4.ReadWriteLock和synchronized对比
1.ReadWriteLock是接口,synchronized是关键字。
2.ReadWriteLock是共享锁,synchronized是排它锁。
3.ReadWriteLock读操作多时并发业务效率高。
4.都支持可重入,ReadWriteLock可设置为公平锁,synchronized为非公平锁。
5.ReadWriteLock是手动释放锁,synchronized是jvm管理。