① ReadWriteLock是一个接口,提供了readLock和writeLock两种锁的操作机制,一个是读锁,一个是写锁。
读锁可在没有写锁的时候被多个线程同时持有,写锁是独占的(排他的)。 每次只能有一个写线程,但是可以有多个线程并发地读数据。
一个获得了读锁的线程必须能看到前一个释放的写锁所更新的内容。
理论上,读写锁比互斥锁允许对于共享数据更大程度的并发。与互斥锁相比,读写锁是否能够提高性能取决于读写数据的频率、读取和写入操作的持续时间、以及读线程和写线程之间的竞争。
② 使用场景
对共享资源的读和写操作,且写操作没有读操作那么频繁
ReentrantReadWriteLock是ReadWriteLock接口的实现类–可重入的读写锁
ReentrantReadWriteLock类
编号 | 方法 | 描述 |
---|---|---|
1 | public Lock readLock() | 返回用于读的锁。(共享锁) |
2 | public Lock writeLock() | 返回用于写的锁。(排它锁) |
DEMO:
package practice;
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Task
{
// private static Lock lock = new ReentrantLock();//互斥锁
private static ReentrantReadWriteLock reenLock = new ReentrantReadWriteLock();
private static Lock readLock = reenLock.readLock();
private static Lock writeLock = reenLock.writeLock();
private int value;
public static void main(String[] args)
{
Task t = new Task();
//读线程
Runnable readRunnable = new Runnable(){
public void run()
{
int read = t.handleRead(readLock);
System.out.println("读到:"+read);
}
};
//写线程
Runnable writeRunnable = new Runnable(){
public void run()
{
t.handleWrite(writeLock, new Random().nextInt(1000));
System.out.println("写入");
}
};
for (int i = 0; i < 18; i++)
{//开启18个读线程
new Thread(readRunnable).start();
}
for (int i = 18; i < 20; i++)
{//开启2个写线程
new Thread(writeRunnable).start();
}
}
public int handleRead(Lock lock){
try
{
lock.lock();
Thread.sleep(1000);
} catch (InterruptedException e)
{
e.printStackTrace();
}finally{
lock.unlock();
}
return this.value;
}
public void handleWrite(Lock lock,int index){
try
{
lock.lock();
Thread.sleep(1000);
this.value = index;
} catch (InterruptedException e)
{
e.printStackTrace();
}finally{
lock.unlock();
}
}
}
由于采用读写锁,读读之间并行,节省了大量实际。整个程序大约2秒运行完成。如果不采用读写锁,程序执行将长达20秒
- - 读读不互斥:读读之间不阻塞
- - 读写互斥:读阻写,写也会阻读
- - 写写互斥:写写阻塞
读写锁有以下三个重要的特性:
(1)公平选择性:支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平。
(2)重进入(可重入):读锁和写锁都支持线程重进入。
(3)锁降级:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁。但是,从读锁到写锁的升级是不可能的。