1 简介
ReentrantLock和ReentrantReadWriteLock都是可重入锁。可重入锁,顾名思义,就是支持重进入的锁,它表示该锁能够支持一个线程对资源的重复加锁
ReentrantLock和ReentrantReadWriteLock都支持获取锁时的公平和非公平性选择。默认是非公平的
ReentrantLock读读、读写、写写全部互斥。ReentrantReadWriteLock读读共享,读写互斥,写写互斥,且支持锁降级
ReentrantReadWriteLock由于读读共享,且支持锁降级,所及效率会高一些。由于它读写不共享,所以在读写高并发操作时,可能导致写的操作锁饥饿。
是否可重入 | 公平性选择 | 读读 | 读写 | 写写 | 锁降级 | |
ReentrantLock | 是 | 是 | 互斥 | 互斥 | 互斥 | 不支持 |
ReentrantReadWriteLock | 是 | 是 | 共享 | 互斥 | 互斥 | 支持 |
2 ReentrantLock示例
public class ReentrantLockTest1 { static ReentrantLock lo = new ReentrantLock(); //读读互斥 读写互斥 写写互斥 public static void main(String[] args) { for (int i = 0;i < 5;i++) { new Thread(() -> operate(), "要进行读操作的线程" + i).start(); } for (int i = 0;i < 5;i++) { new Thread(() -> operate(), "要进行写操作的线程线程" + i).start(); } } private static void operate() { lo.lock(); System.out.println(Thread.currentThread().getName() + "开始操作-----------"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "结束操作-----------"); lo.unlock(); } }
执行结果,只有执行完一个操作,才能够执行另一个操作,所有操作互斥
要进行读操作的线程0开始操作----------- 要进行读操作的线程0结束操作----------- 要进行读操作的线程1开始操作----------- 要进行读操作的线程1结束操作----------- 要进行读操作的线程2开始操作----------- 要进行读操作的线程2结束操作----------- 要进行读操作的线程3开始操作----------- 要进行读操作的线程3结束操作----------- 要进行读操作的线程4开始操作----------- 要进行读操作的线程4结束操作----------- 要进行写操作的线程线程0开始操作----------- 要进行写操作的线程线程0结束操作----------- 要进行写操作的线程线程1开始操作----------- 要进行写操作的线程线程1结束操作----------- 要进行写操作的线程线程2开始操作----------- 要进行写操作的线程线程2结束操作----------- 要进行写操作的线程线程3开始操作----------- 要进行写操作的线程线程3结束操作----------- 要进行写操作的线程线程4开始操作----------- 要进行写操作的线程线程4结束操作----------- Process finished with exit code 0
3 ReentrantReadWriteLock 示例
ReentrantReadWriteLock分为:
读锁-ReentrantReadWriteLock.ReadLock
写锁-ReentrantReadWriteLock.WriteLock
3.1 示例1
下面示例演示出了:读读不互斥,读写互斥,写写互斥
public class ReentrantLockTest2 { static ReentrantReadWriteLock lo = new ReentrantReadWriteLock(); static ReentrantReadWriteLock.ReadLock readLock = lo.readLock(); static ReentrantReadWriteLock.WriteLock writeLock = lo.writeLock(); public static void main(String[] args) { for (int i = 0;i < 10;i++) { new Thread(() -> read(), "read线程" + i).start(); } for (int i = 0;i < 10;i++) { new Thread(() -> write(), "write线程" + i).start(); } } private static void read() { readLock.lock(); System.out.println(Thread.currentThread().getName() + "开始读-----------"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "结束读-----------"); readLock.unlock(); } private static void write() { writeLock.lock(); System.out.println(Thread.currentThread().getName() + "开始写-----------"); try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "结束写-----------"); writeLock.unlock(); } }
执行结果,发现多个读操作可以同时进行,读写操作互斥,写写也互斥
read线程0开始读----------- read线程4开始读----------- read线程3开始读----------- read线程1开始读----------- read线程2开始读----------- read线程0结束读----------- read线程3结束读----------- read线程4结束读----------- read线程1结束读----------- read线程2结束读----------- write线程0开始写----------- write线程0结束写----------- write线程1开始写----------- write线程1结束写----------- write线程2开始写----------- write线程2结束写----------- write线程3开始写----------- write线程3结束写----------- write线程4开始写----------- write线程4结束写----------- Process finished with exit code 0
3.2 示例2
这个示例是为了进一步演示读写互斥,和示例2相比,这里for循环先调用写,再调用的读,发现读写,还是互斥
public class ReentrantLockTest3 { static ReentrantReadWriteLock lo = new ReentrantReadWriteLock(); static ReentrantReadWriteLock.ReadLock readLock = lo.readLock(); static ReentrantReadWriteLock.WriteLock writeLock = lo.writeLock(); public static void main(String[] args) { for (int i = 0;i < 5;i++) { new Thread(() -> write(), "write线程" + i).start(); } for (int i = 0;i < 5;i++) { new Thread(() -> read(), "read线程" + i).start(); } } private static void read() { readLock.lock(); System.out.println(Thread.currentThread().getName() + "开始读-----------"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "结束读-----------"); readLock.unlock(); } private static void write() { writeLock.lock(); System.out.println(Thread.currentThread().getName() + "开始写-----------"); try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "结束写-----------"); writeLock.unlock(); } }
执行结果
write线程0开始写----------- write线程0结束写----------- write线程1开始写----------- write线程1结束写----------- write线程2开始写----------- write线程2结束写----------- write线程3开始写----------- write线程3结束写----------- write线程4开始写----------- write线程4结束写----------- read线程0开始读----------- read线程1开始读----------- read线程2开始读----------- read线程4开始读----------- read线程3开始读----------- read线程4结束读----------- read线程1结束读----------- read线程2结束读----------- read线程0结束读----------- read线程3结束读----------- Process finished with exit code 0
4 ReentrantReadWriteLock锁降级
简单来说,就是一个线程在持有写锁,且还未释放的时候,可以去获取读锁,这样子,该线程就可以同时持有读写锁。
多个线程操作一个变量a,使用ReentrantReadWriteLock线程1对a进行修改,值为100,它想要保证100这个值被其它所有的线程获取到,该怎么做?
那么我们要去写的时候,先去获取写锁(此时其它线程不能读写),写完了,再获取读锁(此时其它线程不能读写),此时同时持有读写锁,然后释放写锁,只持有读锁((此时其它线程不能写,但是可以读)),这个从写锁变为读锁的过程,就叫做锁降级。在持有读锁变为持有写锁的过程中,其它线程都不能写,保证我写的数据能够被其它线程看到。如果不能同时持有读写锁那么就只能这么操作,获取写锁-写-释放写锁-获取读锁-其它线程读-释放读锁,在释放写锁和获取读锁之间就会存在空隙,有可能被其它线程进行写操作,导致它写的结果不能被其他线程获取。
所以,锁降级解决的就是即写即读的问题
5 锁降级示例
5.1 示例1
在释放写锁后,释放读锁前,其它线程可读,且中间其它线程都不可写
public class ReentrantLockTest5 { static ReentrantReadWriteLock lo = new ReentrantReadWriteLock(); static ReentrantReadWriteLock.ReadLock readLock = lo.readLock(); static ReentrantReadWriteLock.WriteLock writeLock = lo.writeLock(); public static void main(String[] args) { for (int i = 0;i < 10;i++) { new Thread(() -> write(), "write线程" + i).start(); } for (int i = 0;i < 10;i++) { new Thread(() -> read(), "read线程" + i).start(); } } private static void read() { readLock.lock(); System.out.println(Thread.currentThread().getName() + "开始读-----------"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "结束读-----------"); readLock.unlock(); } private static void write() { writeLock.lock(); System.out.println(Thread.currentThread().getName() + "开始写-----------"); try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "结束写结束写-----------"); readLock.lock(); //锁降级 同时持有写锁和读锁 System.out.println(Thread.currentThread().getName() + "锁降级-----------"); System.out.println(Thread.currentThread().getName() + "释放写锁-----------"); writeLock.unlock(); //释放写锁,只持有读锁,此时其它线程可读 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //此时,它只拥有读锁 System.out.println(Thread.currentThread().getName() + "释放读锁-----------"); readLock.unlock(); // } }
执行结果,可以看到write8在释放了写锁后释放读锁前,其它线程进来读了
write线程1开始写----------- write线程1结束写结束写----------- write线程1锁降级----------- write线程1释放写锁----------- write线程1释放读锁----------- write线程0开始写----------- write线程0结束写结束写----------- write线程0锁降级----------- write线程0释放写锁----------- write线程0释放读锁----------- write线程2开始写----------- write线程2结束写结束写----------- write线程2锁降级----------- write线程2释放写锁----------- write线程2释放读锁----------- write线程3开始写----------- write线程3结束写结束写----------- write线程3锁降级----------- write线程3释放写锁----------- write线程3释放读锁----------- write线程4开始写----------- write线程4结束写结束写----------- write线程4锁降级----------- write线程4释放写锁----------- write线程4释放读锁----------- write线程5开始写----------- write线程5结束写结束写----------- write线程5锁降级----------- write线程5释放写锁----------- write线程5释放读锁----------- write线程7开始写----------- write线程7结束写结束写----------- write线程7锁降级----------- write线程7释放写锁----------- write线程7释放读锁----------- write线程6开始写----------- write线程6结束写结束写----------- write线程6锁降级----------- write线程6释放写锁----------- read线程1开始读----------- read线程1结束读----------- write线程6释放读锁----------- write线程9开始写----------- write线程9结束写结束写----------- write线程9锁降级----------- write线程9释放写锁----------- read线程0开始读----------- read线程0结束读----------- write线程9释放读锁----------- write线程8开始写----------- write线程8结束写结束写----------- write线程8锁降级----------- write线程8释放写锁----------- read线程7开始读----------- read线程3开始读----------- read线程5开始读----------- read线程4开始读----------- read线程8开始读----------- read线程9开始读----------- read线程2开始读----------- read线程6开始读----------- read线程9结束读----------- read线程7结束读----------- read线程3结束读----------- read线程4结束读----------- read线程6结束读----------- read线程2结束读----------- read线程8结束读----------- read线程5结束读----------- write线程8释放读锁----------- Process finished with exit code 0
5.2 示例2
在释放读锁前其它线程不可写
public class ReentrantLockTest4 { static ReentrantReadWriteLock lo = new ReentrantReadWriteLock(); static ReentrantReadWriteLock.ReadLock readLock = lo.readLock(); static ReentrantReadWriteLock.WriteLock writeLock = lo.writeLock(); public static void main(String[] args) { for (int i = 0;i < 10;i++) { new Thread(() -> write(), "write线程" + i).start(); } for (int i = 0;i < 10;i++) { new Thread(() -> read(), "read线程" + i).start(); } } private static void read() { readLock.lock(); System.out.println(Thread.currentThread().getName() + "开始读-----------"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "结束读-----------"); readLock.unlock(); } private static void write() { writeLock.lock(); System.out.println(Thread.currentThread().getName() + "开始写-----------"); try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "结束写结束写-----------"); readLock.lock(); //锁降级 同时持有写锁和读锁 System.out.println(Thread.currentThread().getName() + "锁降级-----------"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "释放写锁-----------"); writeLock.unlock(); //释放写锁-此时值持有读锁 System.out.println(Thread.currentThread().getName() + "释放读锁-----------"); readLock.unlock(); //释放读锁 } }
执行结果
write线程1开始写----------- write线程1结束写结束写----------- write线程1锁降级----------- write线程1释放写锁----------- write线程1释放读锁----------- write线程2开始写----------- write线程2结束写结束写----------- write线程2锁降级----------- write线程2释放写锁----------- write线程2释放读锁----------- write线程0开始写----------- write线程0结束写结束写----------- write线程0锁降级----------- write线程0释放写锁----------- write线程0释放读锁----------- write线程3开始写----------- write线程3结束写结束写----------- write线程3锁降级----------- write线程3释放写锁----------- write线程3释放读锁----------- write线程4开始写----------- write线程4结束写结束写----------- write线程4锁降级----------- write线程4释放写锁----------- write线程4释放读锁----------- write线程7开始写----------- write线程7结束写结束写----------- write线程7锁降级----------- write线程7释放写锁----------- write线程7释放读锁----------- write线程8开始写----------- write线程8结束写结束写----------- write线程8锁降级----------- write线程8释放写锁----------- write线程8释放读锁----------- write线程5开始写----------- write线程5结束写结束写----------- write线程5锁降级----------- write线程5释放写锁----------- write线程5释放读锁----------- write线程6开始写----------- write线程6结束写结束写----------- write线程6锁降级----------- write线程6释放写锁----------- write线程6释放读锁----------- write线程9开始写----------- write线程9结束写结束写----------- write线程9锁降级----------- write线程9释放写锁----------- write线程9释放读锁----------- read线程0开始读----------- read线程1开始读----------- read线程2开始读----------- read线程3开始读----------- read线程4开始读----------- read线程5开始读----------- read线程6开始读----------- read线程7开始读----------- read线程8开始读----------- read线程9开始读----------- read线程4结束读----------- read线程1结束读----------- read线程0结束读----------- read线程2结束读----------- read线程5结束读----------- read线程3结束读----------- read线程6结束读----------- read线程9结束读----------- read线程7结束读----------- read线程8结束读-----------
5.3 再来个更清晰的例子
持有读写锁,释放写锁后释放读锁前,其它线程可读
public class ReentrantLockTest8 { static ReentrantReadWriteLock lo = new ReentrantReadWriteLock(); static ReentrantReadWriteLock.ReadLock readLock = lo.readLock(); static ReentrantReadWriteLock.WriteLock writeLock = lo.writeLock(); //读读共享 读写互斥 写写互斥 public static void main(String[] args) { CountDownLatch c = new CountDownLatch(10); new Thread(() ->write(c), "write线程" ).start(); for (int i = 0;i < 10;i++) { new Thread(() -> read(c), "read线程" + i).start(); } } private static void read(CountDownLatch c) { readLock.lock(); System.out.println(Thread.currentThread().getName() + "开始读-----------"); System.out.println(Thread.currentThread().getName() + "结束读-----------"); readLock.unlock(); c.countDown(); } private static void write(CountDownLatch c) { writeLock.lock(); System.out.println(Thread.currentThread().getName() + "写-----------"); readLock.lock(); //锁降级 同时持有写锁和读锁 System.out.println(Thread.currentThread().getName() + "释放写锁-----------"); writeLock.unlock(); //释放写锁,只持有读锁,此时其它线程可读 try { c.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "释放读锁-----------"); readLock.unlock(); //释放读锁 } }
执行结果,执行完成
write线程写----------- write线程释放写锁----------- read线程0开始读----------- read线程5开始读----------- read线程4开始读----------- read线程4结束读----------- read线程1开始读----------- read线程1结束读----------- read线程7开始读----------- read线程7结束读----------- read线程6开始读----------- read线程5结束读----------- read线程3开始读----------- read线程9开始读----------- read线程9结束读----------- read线程2开始读----------- read线程0结束读----------- read线程2结束读----------- read线程3结束读----------- read线程6结束读----------- read线程8开始读----------- read线程8结束读----------- write线程释放读锁----------- Process finished with exit code 0
持有读写锁,释放读锁和写锁前,其它线程不可读
把try { c.await(); } catch (InterruptedException e) { e.printStackTrace(); }放到释放写锁前
public class ReentrantLockTest7 { static ReentrantReadWriteLock lo = new ReentrantReadWriteLock(); static ReentrantReadWriteLock.ReadLock readLock = lo.readLock(); static ReentrantReadWriteLock.WriteLock writeLock = lo.writeLock(); //读读共享 读写互斥 写写互斥 public static void main(String[] args) { CountDownLatch c = new CountDownLatch(10); new Thread(() ->write(c), "write线程" ).start(); for (int i = 0;i < 10;i++) { new Thread(() -> read(c), "read线程" + i).start(); } } private static void read(CountDownLatch c) { readLock.lock(); System.out.println(Thread.currentThread().getName() + "开始读-----------"); System.out.println(Thread.currentThread().getName() + "结束读-----------"); readLock.unlock(); c.countDown(); } private static void write(CountDownLatch c) { writeLock.lock(); System.out.println(Thread.currentThread().getName() + "写-----------"); readLock.lock(); //锁降级 同时持有写锁和读锁 try { c.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "释放写锁-----------"); writeLock.unlock(); //释放写锁,只持有读锁,此时其它线程可读 System.out.println(Thread.currentThread().getName() + "释放读锁-----------"); readLock.unlock(); //释放读锁 } }
执行结果,卡住了,没有执行完
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
2021-04-22 spring-cloud03-consul