ReadWriteLock读写锁
读写分离锁可以有效帮助减少锁竞争以提高系统性能。比如线程A1、A2、A3进行写操作,B1、B2、B3进行读操作,如果使用重入锁或者内部锁,那么所有读之间、读写之间、写写之间都是串行执行的。无论哪一个操作先获得了锁,其他操作都必须等待锁。而读写锁则允许多个线程同时读,使得B1、B2、B3之间并行执行。考虑到数据的完整性,写写操作和读写操作间需要相互等待和持有锁。
读 | 写 | |
---|---|---|
读 | 非阻塞 | 阻塞 |
写 | 阻塞 | 阻塞 |
- 读-读不互斥:读读之间不阻塞
- 读-写互斥:读阻塞写,写也阻塞读
- 写-写互斥:写写阻塞
如果某个系统的读操作次数远远大于写操作,则读写锁可以发挥最大功效
package readWriteLock;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @Time : 2019/05/07 下午 03:30
* @Author : xiuc_shi
**/
public class ReadWriteLockDemo {
private static ReentrantLock lock = new ReentrantLock();
private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private static Lock readLock = readWriteLock.readLock();
private static Lock writeLock = readWriteLock.writeLock();
public int value;
//模拟读操作
public int readData(Lock lock){
try {
lock.lock();
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
return value;
}
//模拟写操作
public void writeData(Lock lock, int value){
try {
lock.lock();
Thread.sleep(1000);
this.value = value;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ReadWriteLockDemo demo = new ReadWriteLockDemo();
ExecutorService es = Executors.newFixedThreadPool(20);
Runnable readTask = new Runnable() {
@Override
public void run() {
demo.readData(readLock);
}
};
Runnable writeTask = new Runnable() {
@Override
public void run() {
demo.writeData(writeLock,new Random().nextInt(10));
}
};
// long time = System.currentTimeMillis();
for(int i = 0;i < 18;i++){
es.submit(readTask);
}
for(int i = 18;i < 20;i++){
es.submit(writeTask);
}
es.shutdown();
// System.out.println("运行用时:" + (System.currentTimeMillis() - time));
}
}
以上例子如果使用重入锁,执行时间大概需要20秒,因为每个操作都要等待锁。而换成读写锁,读操作不阻塞,执行时间大概2秒。
ps:本来想通过程序输出执行时间的,后来发现我还是太年轻,脑子里时刻要想到的是多线程,主程序将所有子线程提交之后,马上就继续执行了,算出来的时间根本不是所有子线程执行总时长,应该等待所有子线程执行完,主线程再执行,才能输出时长。对于计算时长这个问题,我们使用CountDownLatch
来完成。
倒计时器:CountDownLatch
CountDownLatch
通常用来控制线程等待,它可以让某一个线程等待直到倒计时结束,再开始执行。
CountDownLatch
的构造函数接受一个整数作为参数,表示计数器的计数个数。
public CountDownLatch(int count)
那么我们ReadWriteLockDemo
执行了20
个线程任务,那么我们创建一个倒计时为20
的CountDownLatch
对象,每完成一个任务减1
,倒计时完成再继续执行主线程。
package readWriteLock;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @Time : 2019/05/07 下午 03:30
* @Author : xiuc_shi
**/
public class ReadWriteLockDemo {
private static ReentrantLock lock = new ReentrantLock();
private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private static Lock readLock = readWriteLock.readLock();
private static Lock writeLock = readWriteLock.writeLock();
public int value;
//模拟读操作
public int readData(Lock lock){
try {
lock.lock();
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
return value;
}
//模拟写操作
public void writeData(Lock lock, int value){
try {
lock.lock();
Thread.sleep(1000);
this.value = value;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ReadWriteLockDemo demo = new ReadWriteLockDemo();
ExecutorService es = Executors.newFixedThreadPool(20);
//创建一个倒计时为20的倒计时器
CountDownLatch cdl = new CountDownLatch(20);
Runnable readTask = new Runnable() {
@Override
public void run() {
demo.readData(readLock);
//任务完成,倒计时减1
cdl.countDown();
}
};
Runnable writeTask = new Runnable() {
@Override
public void run() {
demo.writeData(writeLock,new Random().nextInt(10));
//任务完成,倒计时减1
cdl.countDown();
}
};
long time = System.currentTimeMillis();
for(int i = 0;i < 18;i++){
es.submit(readTask);
}
for(int i = 18;i < 20;i++){
es.submit(writeTask);
}
es.shutdown();
//等待倒计时完成
cdl.await();
System.out.println("运行用时:" + (System.currentTimeMillis() - time)/1000 + "s");
}
}
>>> 结果
运行用时:3s
cdl.countDown()
方法通知CountDownLatch
,一个线程完成了任务,倒计时器可以减1
;cdl.await()
方法要求主线程等待所有20
个线程全部执行完任务才能继续执行。
网友评论