关于lock.lockInterruptibly()方法和java.lang.IllegalMonitorStateException异常

本文深入探讨了Java中lock.lock()与lock.lockInterruptibly()的区别,通过实例解释了这两种加锁方式在线程中断时的行为差异,并提供了正确的实现方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在看java集合源码时,发现一些并发集合中加锁的方式有的是lock.lock(),有的是lock.lockInterruptibly()形式,不是很清楚其中的区别,故搜了一些资料,写了自己的理解。

API文档的说法:

  1. lock.lock():尝试获取锁。当该函数返回时,处于已经获取锁的状态。如果当前有别的线程获取了锁,则睡眠。
  2. 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抛出了异常,就自己返回了,不会走到后面,如果走到了后面,则必是获取了锁!!

思维定势害死人啊!!!!!!!


总结一下:

  1. lock.lock方法线程被中断时,是该线程已经获取了锁,在检查中断标志,发现自己被人中断了,则抛出异常,因此在finally中一定要释放锁。
  2. lock.lockInterruptibly():如果没有获取锁,并中断了,则立马抛出异常,如果你catch,则在后面的处理中,你需要区分是获取了锁还是没有获取锁!!!不要轻易lock.unlock(),也不要不unlock,则会造成严重后果。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值