并发包中的管程
并发包中的管程
什么是Lock和Condition
并发包SDK中存在管程的另一个实现即Lock和Condition,其中Lock可以解决互斥问题,Condition可以解决管程的同步问题(通信和协作)。
读到这里有人肯定马上就有疑问了,用synchronized实现的管程完全可以解决互斥和同步问题,为什么要在SDK中重新实现一种管程方式呢?这不是多此一举吗?
没错synchronized确实可以解决互斥和同步问题,但是存在即合理,回答这个问题之前先回顾下死锁问题成立的四大条件。
- 互斥(同一时间资源只有一个线程访问)
- 持有且等待(持有资源A时尝试获取资源B,尝试等待的时间段内不释放资源A)
- 不可抢占(持有的资源不能被其它线程抢占)
- 循环等待(持有资源A获取资源B时会一直等待)
而解决死锁问题就是破坏其中一个条件即可,但是互斥条件不能破坏,因为破坏失去加锁的意义,所以可破坏其它三个死锁条件。
- 破坏持有且等待:一次性申请所有的资源。
- 破坏循环等待:将资源排序,让资源有序就可以破坏死活。
- 破坏不可抢占:想要破坏不可抢占条件需要主动释放资源,但如果加锁的是synchronized那么获取不到资源就会进入睡眠,不会响应其它资源。
显然破坏不可抢占条件,synchronized锁并不能做到,所以只能另外设计一把互斥锁来解决死锁问题,那么互斥锁应该存在哪些优点呢?
- 能够响应中断:synchronized不能破坏不可抢占条件的原因也就是因为线程获取资源A后,去竞争资源B一旦失败就会进入睡眠状态,这时发生死锁将没有机会唤醒线程,如果阻塞的线程能够响应中断,唤醒线程那么就有机会去释放资源A,达到破坏不可抢占的条件。
- 非阻塞地获取锁:当获取资源失败后,不进入阻塞状态而是立马将结果返回,那么线程就能有机会去释放持有的资源。
- 支持超时:如果线程在一定时间内没有获取到锁,并不是进入阻塞状态而是返回错误,这样也能有机会释放持有的资源。
Lock如何保证互斥性
显然Lock做到了这一点,能够同时满足上诉的三个优点
-
支持中断的API
void lockInterruptibly() throws InterruptedException;
-
支持超时API
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
-
非阻塞获取锁API
boolean tryLock();
Lock如何保证可见性
Lock锁的可见性不能直接应用于JMM(JAVA内存模型)的Happens-Before规则,其锁的可见性规则只是针对synchronized,那么对于上面的代码value能不能保证可见性呢?
class Test {
private final Lock rtl =
new ReentrantLock();
int value;
public void addOne() {
rtl.lock();
try {
value+=1;
} finally {
rtl.unlock();
}
}
}
答案是肯定的,Lock接口的具体实现ReentrantLock中存在一个被volatile修饰的state变量,简称状态机模式。
private volatile int state;
protected final int getState() {
return state;
}
加锁部分源码
// acquires传进来是1
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
解锁部分源码
// releases传进来是1
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
简化版本如下
class SampleLock {
volatile int state;
// 加锁
lock() {
// 读取state变量
// 省略代码无数
state = 1;
}
// 解锁
unlock() {
// 读取state变量
// 省略代码无数
state = 0;
}
}
综上可以简单推出
-
根据顺序性规则(单线程下前面的操作Happens-Before后面的操作)线程T1 value+=1;Happens-Before于rtl.unlock();
-
根据valatile规则(对volatile变量的写操作Happens-Before变量的读操作)线程T1 unlock();Happens-Before于线程T2 lock();
-
根据传递性规则(A Happens-Before B,B Happens-Before C,那么A Happens-Before C)得到线程T1 value+=1 Happens-Before 线程T2 lock操作.
所以线程T1对value进行操作,对于线程T2是可见的。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)