Java并发编程学习——隐藏在并发包中的管程

Java SDK并发包中的管程

在Java SDK并发包中通过Lock和Condition两个接口来实现管程,其中Lock用于解决互斥问题,Condition用来解决同步问题

再造管程的理由

在Java 1.5之前synchronized是不如SDK中的Lock的,但1.6版本之后synchronized做了很多优化,将性能追了上来,所以为什么还要重新“造轮子”的原因不是性能问题,而是 死锁问题中的不可抢占条件

我们知道,synchronized是无法破坏不可抢占条件的,因为当线程申请不到锁的时候会直接阻塞。这个阻塞不会释放资源。

而让我们重新去设计一把互斥锁,其实有三种方法去解决。

  1. 能够响应中断。因为申请不到锁之后阻塞也不释放资源,所以我们希望阻塞线程能被通知中断唤醒并且释放曾经拥有过的资源。

  2. 支持超时。如果线程在一段时间内没有获取到锁,不是进入阻塞状态而是返回一个错误,这样线程也将右机会释放曾经获得的锁了。

  3. 非阻塞地获取锁。获取失败的时候不是进入阻塞状态而是直接返回,这样线程就有机会释放曾经持有的锁了。

    而这三种方案,在Lock接口中都有对应的API

    Lock接口

    对应的lockInterruptibly()是支持中断的API,tryLock()是支持非阻塞获取锁的API,tryLock(long time,TimeUnit unit)是支持超时的API。

如何保证可见性

对于synchronized来说因为happends-before规则可以保证可见性,而在ReentrantLock实现类中内部持有了一个volatile变量state,并且在lock和unlock的时候读写state, 通过volatile的happens-before规则

锁的最佳实践

  1. 永远只在更新对象的变量时加锁

  2. 永远只在访问可变的成员变量的时候加锁

  3. 永远不再调用其他对象的方法的时候加锁(也许其他方法里面有线程sleep()或者IO操作,其他类也可能加锁会导致死锁)

思考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Account {
private int balance;
private final Lock lock
= new ReentrantLock();
// 转账
void transfer(Account tar, int amt){
while (true) {
if(this.lock.tryLock()) {
try {
if (tar.lock.tryLock()) {
try {
this.balance -= amt;
tar.balance += amt;
} finally {
tar.lock.unlock();
}
}//if
} finally {
this.lock.unlock();
}
}//if
}//while
}//transfer
}

上面的代码不会产生死锁,但是可能会产生活锁。

-------------本文结束感谢阅读-------------