读写锁ReentrantReadWriteLock用法详解
前言
业务开发中我们可能涉及到读写操作。
面对写和读,对于数据同步,在使用Lock
锁和synchronized
关键字同步数据时候,对于读读而言,两个线程也需要争抢锁,此时额外争抢锁是没有意义的,造成性能损耗,写的时候,不能读,没有写的时候,读线程不能互斥。
对于Lock
锁和synchronized
来说。都是互斥锁,读读也存在互斥。对此,我们需要读写锁。
如实例:
生产者和消费者而言
当一个线程负责生产,2个线程负责消费,生产者没有进行生产时,两个消费线程都可以去消费数据(这里我们不考虑 重复数据问题)
两个线程彼此还要争抢资源
privatestaticfinalint LINED_SIZE=1000;privatestaticint num=0;privatestaticfinalObject lock=newObject();privatestaticfinalLinkedList<Integer> linkedList=newLinkedList<>();publicstaticvoidmain(String[] args)throwsInterruptedException{
t1.start();
t2.start();
t3.start();
t1.setPriority(5);
t2.setPriority(5);
t3.setPriority(5);
t1.join();
t2.join();
t3.join();TimeUnit.SECONDS.sleep(2);System.out.println(" main end ");}staticclassConsumerObjeimplementsRunnable{@Overridepublicvoidrun(){while(true){synchronized(lock){while(linkedList.size()==0){try{
lock.wait();}catch(InterruptedException e){
e.printStackTrace();}}try{Thread.sleep(5_00);}catch(InterruptedException e){
e.printStackTrace();}System.out.println(Thread.currentThread().getName()+" : "+ linkedList.removeFirst());
lock.notifyAll();}}}}staticclassProductObjeimplementsRunnable{@Overridepublicvoidrun(){while(true){synchronized(lock){while(linkedList.size()>= LINED_SIZE){try{
lock.wait();}catch(InterruptedException e){
e.printStackTrace();}}int n= num++;System.out.println(" 正在生产: "+ n);
linkedList.addLast(n);
lock.notifyAll();}}}}
思考
-
为什么需要使用读写锁ReentrantReadWriteLock
-
这个锁有什么好处?缺点?
好处: 读读不能互斥,提升锁性能,减少线程竞争。
缺点是:当读锁过多时候,写锁少,存在锁饥饿现象。
读写锁ReentrantReadWriteLock用法详解
ReentrantReadWriteLock 也提供了公平和非公平锁
基于构造默认非公平锁, ReentrantReadWriteLock 读写锁内部也是基于AQS队列实现的。
publicReentrantReadWriteLock(){this(false);}
//读写锁privatestaticReentrantReadWriteLock readWriteLock=newReentrantReadWriteLock(true);//写锁privatefinalstaticLock writeLock= readWriteLock.writeLock();//读锁privatefinalstaticLock readLock= readWriteLock.readLock();privatefinalstaticList<Long> longs=newArrayList<>();publicfinalstaticvoidmain(String[] args)throwsInterruptedException{// new Thread(ReentrantReadWriteLockTest::write).start();// TimeUnit.SECONDS.sleep(1);// new Thread(ReentrantReadWriteLockTest::write).start();newThread(ReentrantReadWriteLockTest::write).start();TimeUnit.SECONDS.sleep(1);newThread(ReentrantReadWriteLockTest::read).start();newThread(ReentrantReadWriteLockTest::read).start();}staticvoidwrite(){try{
writeLock.lock();TimeUnit.SECONDS.sleep(1);System.out.println(Thread.currentThread().getName()+" write ");
longs.add(System.currentTimeMillis());}catch(InterruptedException e){
e.printStackTrace();}finally{
writeLock.unlock();}}staticvoidread(){try{
readLock.lock();TimeUnit.SECONDS.sleep(1);
longs.forEach(x->System.out.println(x));}catch(InterruptedException e){
e.printStackTrace();}finally{
readLock.lock();}}
可以看到我们写了一条数据,两条数据同时打印出来,读读是不互斥的。
Thread-0 write
1648997092090
1648997092090
读写锁 存在一个问题:
当读锁比例很多,写锁很少,锁竞争情况下,写锁抢到锁的机会就回少,读锁数量太大的情况下,写锁不一定能抢到锁.
我们使用非公平锁,来测试,启动5个读锁,一个写锁。
//读写锁privatestaticReentrantReadWriteLock readWriteLock=newReentrantReadWriteLock(false);//写锁privatefinalstaticLock writeLock= readWriteLock.writeLock();//读锁privatefinalstaticLock readLock= readWriteLock.readLock();privatefinalstaticList<Long> longs=newArrayList<>();publicfinalstaticvoidmain(String[] args)throwsInterruptedException{newThread(ReentrantReadWriteLockTest2::write).start();TimeUnit.SECONDS.sleep(1);//new Thread(ReentrantReadWriteLockTest2::read).start();//new Thread(ReentrantReadWriteLockTest2::read).start();for(int i=0; i<5; i++){newThread(ReentrantReadWriteLockTest2::read).start();}}staticvoidwrite(){for(;;){try{
writeLock.lock();TimeUnit.SECONDS.sleep(1);System.out.println(Thread.currentThread().getName()+" write ");
longs.add(System.currentTimeMillis());}catch(InterruptedException e){
e.printStackTrace();}finally{
writeLock.unlock();}}}staticvoidread(){for(;;){try{
readLock.lock();TimeUnit.SECONDS.sleep(1);
longs.forEach(x->System.out.println(x));}catch(InterruptedException e){
e.printStackTrace();}finally{
readLock.lock();}}}
测试结果这里就不写了,刚开始一直写,后来一直读,写锁机会很少,当读线程比例再大时,写的机会就更少了。
最后
ReentrantReadWriteLock 读写锁既有有点也有缺点
好处: 读读不能互斥,提升锁性能,减少线程竞争。
缺点是:当读锁过多时候,写锁少,存在锁饥饿现象。
使用时候需要控制读写比例,防止出现锁饥饿现象。
当出现读比例特别大时候,ReentrantReadWriteLock锁就不适合了,此时JDK8之后提供的StampedLock
锁更适合读写比例大的场景