发生死锁的原因:
1.1 锁顺序死锁:
synchronized(left){
synchronized(right){
...
}
}
synchronized(right){
synchronized(left){
...
}
}
1.2 动态的锁顺序死锁 (获取锁的顺序由参数决定):
func(A,B){
synchronized(A){
synchronized(B){
}
}
}
1.3 顺序死锁的解决方法
规定获取锁的顺序
2.在协作对象间发生死锁
在持有锁的情况下调用了某个外部方法,需要警惕死锁
2.1开放调用
即调用某个方法时不需要持有锁
public void setLocation(Point location){
synchronized(this){
this.location = location;
}
//notifyAvailable是需要加锁的
dispatcher.notifyAvailable(this);
}
3.资源死锁
一般出现在资源池为空(资源池一般由信号量实现)的阻塞行为
Ex: A持有数据库D1连接,等待D2连接;B持有D2连接,等待D1连接
线程饥饿死锁
Ex: 单线程Executor中一个任务提交另一个任务并等待执行完成
死锁的避免
1.使用定时锁:显示使用Lock类中tryLock代替内置锁
与上文毫无关系的
Semaphore用法:
Semaphore a = new Semaphore(0);
a.acquire(); //由于a中没有许可,所以会被阻塞
a.release(); //a中的可用许可变为1
a.release(); //a中的可用许可变为2
Semaphore acquire与release并没有先后使用关系。单纯的,acquire 执行 -1,release 执行 +1
并发程序单元测试
1. 单元测试 —— 测试阻塞状态
//中断状态单元测试
/**BoundedBuffer是一个基于信号量的有界缓存。《并行编程实战》P205
**/
class BoundedBufferTest extends TestCase{
public void testTakeBlocksWhenEmpty(){
final BoundedBuffer<Integer> bb = new BoundedBuffer<Integer>(10);
Thread taker = new Thread(){
public void run(){
try{
//按道理会被阻塞
int unused = bb.take();
//如果执行到这里算错误
fail();
}catch(InterruptedException e){
//检测到主线程传来的中断
System.out.println("I am interrupted");
}
}
};
try{
taker.start();
//等待一段时间
Thread.sleep(LOCKUP_DETECT_TIMEOUT);
//中断线程
taker.interrupted();
//为防止意外,比如线程无法响应中断,或者。。。自己恢复了中断
taker.join(LOCKUP_DETECT_TIMEOUT);
assertFalse(taker.isAlive());
}catch(Exception unexpected){
fail();
}
}
}
LOCK
1. synchronized 与 ReentrantLock 之间抉择:
ReentrantLock可以作为一种高级工具,这些功能包括:可定时的,可轮询的与可中断的锁获取操作,公平队列,以及非快结构的锁。否则还是应该优先使用synchronized
2.轮询+定时锁
使用轮询锁时,它会释放已获得的锁,然后尝试重新获取所有锁。
public boolean transferMoney(Account fromAcct,Account toAcct,long timeout,TimeUnit unit){
long fixedDelay; //在重新获取锁时,采用 固定时间+随机时间,从而降低发生活锁的可能性
long randMod;
long stopTime = System.nanoTime + unit.toNanos(timeout); //定时时间
while(true){
if(fromAcct.lock.tryLock()){
try{
if(toAcct.locl.tryLock()){
try{
...
return true; //即使已经返回true,finally仍然会执行。
}finally{
toAcct.lock.unlock();
}
}
}finally{
fromAcct.lock.unlock();
}
}
if(System.nanoTime > stopTime)
return false; //超时取消
//过一定的时间之后再获取锁
TimeUnit.NANOSECONDS.sleep(fixedDelay + randMod);
}
}
3.lock 自带的定时锁
if(!lock.tryLock(timeout,TimeUnit.NANOSECONDS)){
return false; //`在规定时间内未获取锁
}
4.可中断的锁
lock.lockInterruptibly(); 该方法获取锁时,如果处于阻塞状态时,线程被中断,就会响应中断。
而lock.lock(); 在阻塞状态时,即使线程被中断,仍然会等待获取锁,直到获取锁之后再响应中断。
5.读写锁
ReentrantReadWriteLock 特性:
- 可重入的加锁定义
- 写线程可降级为读线程,但读线程不能升级为写线程
- 同时只能有一个写线程,但允许多个读线程。
//用读写锁封装的map
public class ReadWriteMap<K,V>{
private final Map<K,V> map;
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock r = lock.readLock();
private final Lock w = lock.writeLock();
public ReadWriteMap(Map<K,V> map){
this.map = map;
}
public V put(K key,V value){
w.lock(); //写线程加锁
try{
return map.put(key,value);
}finally{
w.unlock();
}
}
public V get(Object key){
r.lock();
try{
return map.get(key);
}finally{
r.unlock();
}
}
}