何为读写锁
呃,同样回归到问题的本质,在并发编程过程中,多个线程对共享资源的访问会导致出现线程安全问题,所以对于共享资源的访问我们一般都会选择直接加锁,例如: Synchronized, 这样整个代码块对共享资源的访问都是串行化执行,就不会存在线程安全问题。 有没有改进空间?
一般来过找到共享资源,一般都是差不多比较细粒度的,呃但是对共享资源的操作又分为 读 和 写, 在结合线程安全问题出现的原因: 对共享资源的并发修改,所以对于读操作时不存在线程安全问题,完全可以并发执行,提示效率。
读写锁互斥一览表
读 | 写 | |
---|---|---|
写 | 互斥 | 互斥 |
读 | 不互斥 | 互斥 |
使用 Synchronized 实现读写锁
Lock接口
public interface Lock {
void lock();
void unLock();
}
读写锁
/**
* 我的读写锁
* @Author: puhaiguo
* @Date: 2022-07-11 00:22
* @Version 1.0
*/
public class MyWriteReadLock {
/*读线程的个数*/
private int readThreadCount;
/*写锁标志*/
private boolean flag = false;
/*synchronized 的拥有锁的对象*/
private Object monitor = new Object();
/*读锁*/
public class ReadLock implements Lock{
@Override
public void lock() {
synchronized (monitor){
/*说明有读锁*/
while (flag){
try {
monitor.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/*走到这里说明没有读锁*/
readThreadCount++;
}
}
@Override
public void unLock() {
synchronized (monitor){
readThreadCount--;
if (readThreadCount == 0){
/*唤醒所有的读写线程*/
monitor.notifyAll();
}
}
}
}
/*写锁*/
public class WriteLock implements Lock{
@Override
public void lock() {
synchronized (monitor){
while (readThreadCount != 0 || flag){
try {
monitor.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/*说明获取到写锁*/
flag = true;
}
}
@Override
public void unLock() {
synchronized (monitor){
/*唤醒所有的读写线程*/
monitor.notifyAll();
flag = false;
}
}
}
}
测试
public class Test {
public static void main(String[] args) {
MyWriteReadLock myWriteReadLock = new MyWriteReadLock();
MyWriteReadLock.ReadLock readLock = myWriteReadLock.new ReadLock();
MyWriteReadLock.WriteLock writeLock = myWriteReadLock.new WriteLock();
/*20 个读线程*/
for (int i = 0; i < 20; i++) {
new Thread(()->{
try {
readLock.lock();
System.out.println(Thread.currentThread().getName() + " 获取到读锁 ");
TimeUnit.SECONDS.sleep(2);
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + " 释放读锁 ");
readLock.unLock();
}
}, "读线程" + i).start();
}
for (int i = 0; i < 5; i++) {
new Thread(()->{
try {
writeLock.lock();
System.out.println(Thread.currentThread().getName() + " 获取到写锁 ");
TimeUnit.SECONDS.sleep(2);
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + " 释放写锁 ");
writeLock.unLock();
}
}, "写线程" + i).start();
}
}
}
结果
问题
呃,读写锁的效果时实现了,但是有没有注意到出现了写锁饥饿的问题, 读锁很多的情况下,写锁一直获取不到锁
解决 思路
设计一个优化开关,比如一开始时偏向写锁,如果写锁释放锁之后又把整个开关关闭,读锁接着获取锁,如果读锁释放锁的时候再把整个开关开起来
/**
* 我的读写锁
* @Author: puhaiguo
* @Date: 2022-07-11 00:22
* @Version 1.0
*/
public class MyWriteReadLock {
/*读线程的个数*/
private int readThreadCount;
/*写锁标志*/
private boolean flag = false;
/*synchronized 的拥有锁的对象*/
private Object monitor = new Object();
/*偏向写锁*/
private boolean preWirte = true;
/*写线程数量*/
private AtomicInteger writeThreadCount = new AtomicInteger(0);
/*读锁*/
public class ReadLock implements Lock{
@Override
public void lock() {
synchronized (monitor){
/*说明有读锁*/
//System.out.println(writeThreadCount.get());
while (flag || (preWirte && writeThreadCount.get() != 0 ) ){
try {
monitor.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/*走到这里说明没有读锁*/
readThreadCount++;
}
}
@Override
public void unLock() {
synchronized (monitor){
readThreadCount--;
/*设置偏向写锁*/
preWirte = true;
if (readThreadCount == 0){
/*唤醒所有的读写线程*/
monitor.notifyAll();
}
}
}
}
/*写锁*/
public class WriteLock implements Lock{
@Override
public void lock() {
int count = writeThreadCount.addAndGet(1);/*加一*/
System.out.println("写锁数量 " + count);
synchronized (monitor){
while (readThreadCount != 0 || flag){
try {
monitor.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/*说明获取到写锁*/
flag = true;
}
}
@Override
public void unLock() {
synchronized (monitor){
/*唤醒所有的读写线程*/
monitor.notifyAll();
flag = false;
/*设置偏向读锁*/
preWirte = false;
int count = writeThreadCount.decrementAndGet();
System.out.println("写锁数量 " + count);
}
}
}
}
总结
其实关于读写线程的问题,基于AQS实现的可重入读写锁得到了很好的解决。