在 Java 中有两种锁,一种是内置锁 synchronized,一种是显示锁 Lock,其中 Lock 锁是可中断锁,而 synchronized 则为不可中断锁。
所谓的 中断锁指的是锁在执行时可被中断,也就是在执行时能够接管 interrupt 的告诉,从而中断锁执行。
PS:默认状况下 Lock 也是不可中断锁,然而能够通过非凡的“伎俩”,能够让其变为可中断锁,接下来咱们一起来看。
为什么须要可中断锁?
不可中断锁的问题是,当呈现“异样”时,只能始终阻塞期待,别无其他方法,比方上面这个程序。上面的这个程序中有两个线程,其中线程 1 先获取到锁资源执行相应代码,而线程 2 在 0.5s 之后开始尝试获取锁资源,但线程 1 执行时遗记开释锁了,这就造成线程 2 始终阻塞期待的状况,实现代码如下:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class InterruptiblyExample {public static void main(String[] args) {Lock lock = new ReentrantLock();
// 创立线程 1
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {lock.lock();
System.out.println("线程 1:获取到锁.");
// 线程 1 未开释锁
}
});
t1.start();
// 创立线程 2
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
// 先休眠 0.5s,让线程 1 先执行
try {Thread.sleep(500);
} catch (InterruptedException e) {e.printStackTrace();
}
// 获取锁
System.out.println("线程 2: 期待获取锁.");
lock.lock();
try {System.out.println("线程 2:获取锁胜利.");
} finally {lock.unlock();
}
}
});
t2.start();}
}
以上代码执行的后果如下:
从上述后果能够看出,此时线程 2 在期待获取锁的操作,然而经验了 N 久之后 …
再次查看后果,仍然是相熟的画面:
线程 2 还在阻塞期待获取线程 1 开释锁资源,此时的线程 2 除了等之外,并无其余办法。
并且,但咱们纯熟的拿出了 JConsole,试图失去一个死锁的具体信息时,却失去了这样的后果:
并没有检测到任何死锁信息,从上图咱们能够看出,当只有一个锁资源的时候,零碎并不会把这种状况断定为死锁,当然也没有阻塞期待的具体信息喽,此时只剩下线程 2 孤独地期待着它的“锁儿”。
应用中断锁
然而,中断锁的呈现,就能够突破这一僵局,它能够在期待肯定工夫之后,被动的中断线程 2,以解决线程阻塞期待的问题。
中断锁的外围实现代码是 lock.lockInterruptibly() 办法,它和 lock.lock() 办法作用相似,只不过应用 lockInterruptibly 办法能够优先接管中断的申请,中断锁的具体实现如下:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class InterruptiblyExample {public static void main(String[] args) throws InterruptedException {Lock lock = new ReentrantLock();
// 创立线程 1
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
// 加锁操作
lock.lock();
System.out.println("线程 1: 获取到锁.");
} catch (InterruptedException e) {e.printStackTrace();
}
// 线程 1 未开释锁
}
});
t1.start();
// 创立线程 2
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
// 先休眠 0.5s,让线程 1 先执行
try {Thread.sleep(500);
} catch (InterruptedException e) {e.printStackTrace();
}
// 获取锁
try {System.out.println("线程 2: 尝试获取锁.");
lock.lockInterruptibly(); // 可中断锁
System.out.println("线程 2: 获取锁胜利.");
} catch (InterruptedException e) {System.out.println("线程 2: 执行已被中断.");
}
}
});
t2.start();
// 期待 2s 后, 终止线程 2
Thread.sleep(2000);
if (t2.isAlive()) { // 线程 2 还在执行
System.out.println("执行线程的中断.");
t2.interrupt();} else {System.out.println("线程 2: 执行实现.");
}
}
}
以上代码执行后果如下:
从上述后果能够看出,当咱们应用了 lockInterruptibly 办法就能够在一段时间之后,判断它是否还在阻塞期待,如果后果为真,就能够间接将他中断,如上图成果所示。
但当咱们尝试将 lockInterruptibly 办法换成 lock 办法之后(其余代码都不变),执行的后果就齐全不一样了,实现代码如下:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class InterruptiblyExample {public static void main(String[] args) throws InterruptedException {Lock lock = new ReentrantLock();
// 创立线程 1
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
// 加锁操作
lock.lockInterruptibly();
System.out.println("线程 1: 获取到锁.");
} catch (InterruptedException e) {e.printStackTrace();
}
// 线程 1 未开释锁
}
});
t1.start();
// 创立线程 2
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
// 先休眠 0.5s,让线程 1 先执行
try {Thread.sleep(500);
} catch (InterruptedException e) {e.printStackTrace();
}
// 获取锁
try {System.out.println("线程 2: 尝试获取锁.");
lock.lock();
System.out.println("线程 2: 获取锁胜利.");
} catch (Exception e) {System.out.println("线程 2: 执行已被中断.");
}
}
});
t2.start();
// 期待 2s 后, 终止线程 2
Thread.sleep(2000);
if (t2.isAlive()) { // 线程 2 还在执行
System.out.println("执行线程的中断.");
t2.interrupt();} else {System.out.println("线程 2: 执行实现.");
}
}
}
以上程序执行后果如下:
从上图能够看出,当应用 lock 办法时,即便调用了 interrupt 办法仍然不能将线程 2 进行中断。
总结
本文介绍了中断锁的实现,通过显示锁 Lock 的 lockInterruptibly 办法来实现,它和 lock 办法作用相似,但 lockInterruptibly 能够优先接管到中断的告诉,而 lock 办法只能“死等”锁资源的开释,同时这两个办法的区别也是常见的面试题,心愿本文对你有用。
并发原创文章举荐
- 线程的 4 种创立办法和应用详解!
- Java 中用户线程和守护线程区别这么大?
- 深刻了解线程池 ThreadPool
- 线程池的 7 种创立形式,强烈推荐你用它 …
- 池化技术达到有多牛?看了线程和线程池的比照吓我一跳!
- 并发中的线程同步与锁
- synchronized 加锁 this 和 class 的区别!
- volatile 和 synchronized 的区别
- 轻量级锁肯定比重量级锁快吗?
- 这样终止线程,居然会导致服务宕机?
- SimpleDateFormat 线程不平安的 5 种解决方案!
- ThreadLocal 不好用?那是你没用对!
- ThreadLocal 内存溢出代码演示和起因剖析!
- Semaphore 自白:限流器用我就对了!
- CountDownLatch:别浪,等人齐再团!
- CyclicBarrier:人齐了,司机就能够发车了!
- synchronized 优化伎俩之锁收缩机制!
- synchronized 中的 4 个优化,你晓得几个?
- ReentrantLock 中的 4 个坑!
- 图解:为什么非偏心锁的性能更高?
- 死锁的 4 种排查工具!
- 死锁终结者:程序锁和轮询锁!
- 轮询锁在应用时遇到的问题与解决方案!
关注公号「Java 中文社群」查看更多有意思、涨常识的 Java 并发文章。