提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- ReadWriteLock----读写锁
- 1.读写锁介绍
- 线程进入读锁的前提条件:
- 线程进入写锁的前提条件:
- 而读写锁有以下三个重要的特性:
- ReentrantReadWriteLock的使用
- 10、ReentrantReadWriteLock读写锁出现的目的?
- 11、ReentrantReadWriteLock如何基于AQS实现的?
ReadWriteLock----读写锁
1.读写锁介绍
现实中有这样一种场景
对共享资源有读和写的操作,且写操作没有读操作那么频繁(读多写少)
在没有写操作的时候,多个线程同时读一个资源没有任何问题,所以应该允许多个线程同时读取共享资源(读读可以并发)
但是如果一个线程想去写这些共享资源,就不应该允许其他线程对该资源进行读和写操作了(读写,写读,写写互斥)
在读多于写的情况下,读写锁能够提供比排它锁更好的并发性和吞吐量。
针对这种场景,JAVA的并发包提供了读写锁ReentrantReadWriteLock,它内部,维护了一对相关的锁,一个用于只读操作,称为读锁;一个用于写入操作,称为写锁
这个ReadWriteLock 是读写锁。读写锁的概念其实就是共享锁和排他锁,读锁就是共享锁,写锁就是排他锁
- 读锁 —共享锁
- 写锁 —排他锁
线程进入读锁的前提条件:
- 没有其他线程的写锁
- 没有写请求或者有写请求,但调用线程和持有锁的线程是同一个
线程进入写锁的前提条件:
- 没有其他线程的读锁
- 没有其他线程的写锁
而读写锁有以下三个重要的特性:
- 公平选择性:支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平。
- 可重入:读锁和写锁都支持线程重入。以读写线程为例:读线程获取读锁后,能够再次获取读锁。写线程在获取写锁之后能够再次获取写锁,同时也可以获取读锁。
- 锁降级:遵循获取写锁、再获取读锁最后释放写锁的次序,写锁能够降级成为读锁。
ReentrantReadWriteLock的使用
- 我们这有两个方法,read()读一个数据,write()写一个数据。read这个数据的时候我需要你往里头传一把锁,这个传那把锁你自己定,我们可以传自己定义的全都是排他锁,也可以传读写锁里面的读锁或写锁。write的时候也需要往里面传把锁,同时需要你传一个新值,在这里值里面传一个内容。我们模拟这个操作,读的是一个int类型的值,读的时候先上锁,设置一秒钟,完了之后read over,最后解锁unlock。再下面写锁,锁定后睡1000毫秒,然后把新值给value,write over后解锁,非常简单。
- new ReentrantReadWriteLock() 是ReadWriteLock的一种实现,在这种实现里头我又分出两把锁来,一把叫readLock,一把叫writeLock,通过他的方法readWriteLock.readLock()来拿到readLock对象,读锁我就拿到了。通过readWriteLock.writeLock()拿到writeLock对象。这两把锁在我读的时候扔进去,因此,读线程是可以一起读的,也就是说这8个线程可以一秒钟完成工作结束。所以使用读写锁效率会大大的提升。
package com.cy.month12;import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;public class T10_TestReadWriteLock {private static int value;static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();static Lock readLock = readWriteLock.readLock();static Lock writeLock = readWriteLock.writeLock();public static void read(Lock lock) {try {lock.lock();Thread.sleep(1000);System.out.println("read over!");//模拟读取操作} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}public static void write(Lock lock, int v) {try {lock.lock();Thread.sleep(1000);value = v;System.out.println("write over!");//模拟写操作} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}public static void main(String[] args) {Runnable readR = ()-> read(readLock);Runnable writeR = ()->write(writeLock, new Random().nextInt());for(int i=0; i<8; i++) new Thread(readR).start();for(int i=0; i<2; i++) new Thread(writeR).start();}
}
10、ReentrantReadWriteLock读写锁出现的目的?
首先,读写锁能解决的,互斥锁肯定能解决,但是,互斥锁的效率可能比较低。
比如说有一个业务,平均下来,10次读操作,1次写操作。
如果用互斥锁,可以保证线程安全,但是10次读操作也需要一个一个来。
但是,读读操作没有线程安全问题。
但是只要涉及到了写操作,比如读写操作,需要保证线程安全。只要有写线程,就必须满足互斥性。
所以JUC下就提供了一个ReentrantReadWriteLock,读写锁。这个锁的特点,就是读读可以一起操作,但是只要涉及到了写操作,就必须保证互斥。
11、ReentrantReadWriteLock如何基于AQS实现的?
lock锁,无论是读锁还是写锁,都是基于state属性判断的。
state是int,占32个bit为,将高16为,作为读锁的标识,低16位,作为写锁的标识。
如果线程要拿读锁,只需要确认没有写线程在持有写锁资源,并且队列中的head.next不是写锁(解决写锁饥饿问题),就可以直接获取读锁资源。
写锁需要确保没有读线程在持有读锁,并且没有其他线程在持有写锁,写锁才能拿到锁资源。