在看java集合源码时,发现一些并发集合中加锁的方式有的是lock.lock(),有的是lock.lockInterruptibly()形式,不是很清楚其中的区别,故搜了一些资料,写了自己的理解。
API文档的说法:
- lock.lock():尝试获取锁。当该函数返回时,处于已经获取锁的状态。如果当前有别的线程获取了锁,则睡眠。
- lockInterruptibly():尝试获取锁。如果当前有别的线程获取了锁,则睡眠。当该函数返回时,有两种可能:
a.已经获取了锁
b.获取锁不成功,但是别的线程打断了它。则该线程会抛出IterruptedException异常而返回,同时该线程的中断标志会被清除。
看一个例子:
class Run implements Runnable{
private static Lock lock = new ReentrantLock();
public void run(){
try{
System.out.println(Thread.currentThread().getName() + "trying to get lock");
lock.lock();
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + " finished");
} catch (InterruptedException e){
System.out.println(Thread.currentThread().getName() + " interrupted");
}
finally{
lock.unlock();
// System.out.println("now lock is avalibale");
}
}
}
public class Test {
public static void main(String args[]) throws InterruptedException
{
Thread i1 = new Thread(new Run());
Thread i2 = new Thread(new Run());
i1.start();
Thread.sleep(100);
i2.start();
//i0.start();
//Thread.sleep(100);
i2.interrupt();
}
}
运行结果:
Thread-0trying to get lock
Thread-1trying to get lock
–等待了一段时间后
Thread-0 finished
Thread-1 interrupted
从结构中可以分析:在线程0获取了锁后,线程1去lock.lock().然后主线程里面去中断线程1,线程1并没有立马中断,而是要等到Thread0运行完后,释放了锁,Thread1在成功获取锁之后,才会被中断,抛出异常。
将lock.unlock()后加上打印语句,则输入变成如下:
运行结果:
Thread-0trying to get lock
Thread-1trying to get lock
Thread-0 finished
now lock is avalibale
Thread-1 interrupted
now lock is avalibale
如果我们使用的是lock.lockInterruptibly(),代码如下:
public void run(){
try{
System.out.println(Thread.currentThread().getName() + "trying to get lock");
lock.lockInterruptibly();
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + " finished");
} catch (InterruptedException e){
System.out.println(Thread.currentThread().getName() + " interrupted");
}
finally{
lock.unlock();
System.out.println("now lock is avalibale");
}
}
则运行结果如下:
Thread-0trying to get lock
Exception in thread "Thread-1" java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(Unknown Source)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(Unknown Source)
at java.util.concurrent.locks.ReentrantLock.unlock(Unknown Source)
at Run.run(Test.java:39)
at java.lang.Thread.run(Unknown Source)
Thread-1trying to get lock
Thread-1 interrupted
--这里要等待一段时间
Thread-0 finished
now lock is avalibale
这是什么情况呢?为啥只是将lock.lock() 改成lock.lockInterruptibly()就是这样了呢?
。。。。
答案揭晓:在线程0获取了锁后,线程1去lock.lock().然后主线程里面去中断线程1。因为在中断时,线程1是没有拿到锁的(线程0获取了锁,在处于sleep状态),所以lock.lockInterruptibly()里面抛出了中断,此时并没有拿到锁!!而finally块就算是发生了中断也会执行lock.unlock(),这里就抛出异常了,因为你没有获取到锁啊,unlock啥!!
那么,这个情况怎么解决呢?
可以这样么:
public void run(){
try{
System.out.println(Thread.currentThread().getName() + "trying to get lock");
lock.lockInterruptibly();
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + " finished");
} catch (InterruptedException e){
System.out.println(Thread.currentThread().getName() + " interrupted");
}
finally{
//如果不是以中断方式返回,则应该是获取了锁,则释放锁!!
if(!Thread.currentThread().isInterrupted())
{
lock.unlock();
System.out.println("now lock is avalibale");
}
}
}
答案是:不行的!!!
为什么?
如前说说:lock.lockInterruptibly()在抛出了异常后,是会清除中断标志位的,所以,finally 中的if永远会执行,又回到了之前的状态。so,what should you do ?
有人要想了:那我在catch时,发现抛出了异常,则一定是没有获取成功,则重新抛出异常,设置标志位!!
如果你这么真这么想,就忽略了一件事:run方法是不能抛出异常的,你要抛出异常,你又要catch ……..
也有一个简单粗暴的方法:
public void run(){
try{
System.out.println(Thread.currentThread().getName() + "trying to get lock");
lock.lockInterruptibly();
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + " finished");
} catch (InterruptedException e){
System.out.println(Thread.currentThread().getName() + " interrupted");
return ;
}
lock.unlock();
}
直接return!!!! 但是这个方法太简单粗暴了,因为run方法没有返回值的,你直接这样返回了,那别人怎么知道你是成功了呢或是失败了呢?又或者是你还有一些后续的复杂的操作需要处理呢?
其实,比较好的方法是这样的:既然Thread自己的标志位会被清除,那自己设一个呗!!
public void run(){
boolean flag=true;
try{
lock.lockInterruptibly();
System.out.println(Thread.currentThread().getName() + " running");
// lock.lock();
Thread.sleep(1900);
System.out.println(Thread.currentThread().getName() + " finished");
} catch (InterruptedException e){
System.out.println(Thread.currentThread().getName() + " interrupted");
flag=false;
}
if(flag)
{
lock.unlock();
//其他处理
}
}
后来回过头来看ArarryBlockingQueue,PriorityBlockingQueue的源代码:
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
//获取锁时,都是通过lock.lockInterruptibly()方法进行的
//和lock.lock的区别就是如果当前锁被被人占用,则处于睡眠状态,如果外部打断了他,
//则会设置中断标志,抛出InterruptedException,中断标志被清除
lock.lockInterruptibly();
E result;
try {
while ( (result = dequeue()) == null)
notEmpty.await();
} finally {
lock.unlock();
}
return result;
}
被刚才的思维模式给固定了,这个怎么这样的,后面怎么直接lock.unlock()了?觉得不对(我也是不知道太高地厚,比起相信专家,更相信自己 )还实验了半天,都没见它抛出锁状态异常,后来翻了好几个类的相关方法,都是这种模式。想了好久想通,这里不是run方法,是可以抛出异常的,并且,这个finally也不是对应的lock.lockInterruptibly()。如果lock.lockInterruptibly抛出了异常,就自己返回了,不会走到后面,如果走到了后面,则必是获取了锁!!
思维定势害死人啊!!!!!!!
总结一下:
- lock.lock方法线程被中断时,是该线程已经获取了锁,在检查中断标志,发现自己被人中断了,则抛出异常,因此在finally中一定要释放锁。
- lock.lockInterruptibly():如果没有获取锁,并中断了,则立马抛出异常,如果你catch,则在后面的处理中,你需要区分是获取了锁还是没有获取锁!!!不要轻易lock.unlock(),也不要不unlock,则会造成严重后果。