标签:取消 this adl unlock 响应 重入 中断 art add
????在了解ReentrantLock之前,我们首先回忆一下synchronized,synchronized是java内置的关键字,锁的获取和释放都是由jvm实现,因此用户就不需要显示的去释放锁,是一种独占的加锁方式,但是虽然方便,也有一定的弊端:
????接下来我们还需要了解几个相关的概念:
public class Demo {
ReentrantLock lock = new ReentrantLock();
public static int num = 0;
public void add(){
lock.lock();
try{
num++;
}finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
Demo demo = new Demo();
Thread t1 = new Thread(()->{
for(int i=0;i<1000;i++){
demo.add();
}
});
Thread t2 = new Thread(()->{
for(int i=0;i<1000;i++){
demo.add();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(demo.num);
}
}
使用过程:
public class Demo {
ReentrantLock lock = new ReentrantLock();
public static int num = 0;
public void add(){
lock.lock();
lock.lock();
try{
num++;
}finally {
lock.unlock();
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
Demo demo = new Demo();
Thread t1 = new Thread(()->{
for(int i=0;i<1000;i++){
demo.add();
}
});
Thread t2 = new Thread(()->{
for(int i=0;i<1000;i++){
demo.add();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(demo.num);
}
}
????在这里lock.lock();
执行了两遍,第一次执行获取到了锁,第二次同样可以执行,所以一个线程可以多次获取同一把锁,说明ReentrantLock是可重入锁,不过在这里需要注意,获取了多少次锁,就要释放多少次锁,否则,该线程还是一直占有着该把锁,其它线程仍然会一直阻塞。因此lock()
和unlock()
一定要是成对出现的。
????大多数情况下都是非公平锁,ReentrantLock的默认构造方法是非公平锁。如果一个锁是公平的,那么锁的获取顺序就应该符合请求的绝对时间顺序,也就是先来的就先获取到锁。
根据源码可以知道,构造方法时加上一个true就可以创建公平锁
private final Sync sync;
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
公平锁测试:
public class FairLock {
ReentrantLock lock = new ReentrantLock(true);
public void add(String str){
lock.lock();
try{
System.out.println(str);
}finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
FairLock fairlock = new FairLock();
Thread t1 = new Thread(()->{
for(int i=0;i<3;i++){
fairlock.add("T1获取到锁");
}
});
Thread t2 = new Thread(()->{
for(int i=0;i<3;i++){
fairlock.add("T2获取到锁");
}
});
Thread t3 = new Thread(()->{
for(int i=0;i<3;i++){
fairlock.add("T3获取到锁");
}
});
Thread t4 = new Thread(()->{
for(int i=0;i<3;i++){
fairlock.add("T4获取到锁");
}
});
t1.start();
t2.start();
t3.start();
t4.start();
}
}
结果:
T2获取到锁
T1获取到锁
T3获取到锁
T4获取到锁
T2获取到锁
T1获取到锁
T3获取到锁
T4获取到锁
T2获取到锁
T1获取到锁
T3获取到锁
T4获取到锁
可以看到获取锁的顺序都是:2,1,3,4,说明锁时按照先后顺序获得的。
如果改成非公平锁:把true
改为false
;
那么结果如下
T1获取到锁
T3获取到锁
T3获取到锁
T3获取到锁
T2获取到锁
T2获取到锁
T2获取到锁
T1获取到锁
T1获取到锁
T4获取到锁
T4获取到锁
T4获取到锁
可以看到t3可能会连续获得锁,结果是比较随机的,不公平的。为什么会出现线程连续获得锁的情况呢?当一个线程请求锁时,只要获取了同步状态即成功获取锁,在这个前提下,刚释放锁的线程再次获取同步状态的几率会非常大,使得其他线程只能在同步队列中等待。
????对于synchronized关键字,如果一个线程在等待获取锁,最终只有2种结果:
????而ReentrantLock提供了另外一种可能,就是在等的获取锁的过程中(发起获取锁请求到还未获取到锁这段时间内)是可以被中断的,也就是说在等待锁的过程中,程序可以根据需要取消获取锁的请求。有些使用这个操作是非常有必要的。比如:你和好朋友越好一起去打球,如果你等了半小时朋友还没到,突然你接到一个电话,朋友由于突发状况,不能来了,那么你一定达到回府。中断操作正是提供了一套类似的机制,如果一个线程正在等待获取锁,那么它依然可以收到一个通知,被告知无需等待,可以停止工作了。
public class InterruptLockDemo extends Thread{
private static ReentrantLock lock1 = new ReentrantLock();
private static ReentrantLock lock2 = new ReentrantLock();
private int threadNum;
private String name;
public InterruptLockDemo(String name,int threadNum){
super(name);
this.name = name;
this.threadNum = threadNum;
}
@Override
public void run() {
try{
if(threadNum==1){
lock1.lockInterruptibly();
TimeUnit.SECONDS.sleep(1);
lock2.lockInterruptibly();
}
else{
lock2.lockInterruptibly();
TimeUnit.SECONDS.sleep(1);
lock1.lockInterruptibly();
}
}catch (InterruptedException e){
System.out.println("线程"+name+"被中断,中断标志位:"+this.isInterrupted());
e.printStackTrace();
}finally {
// 如果lock1被当前线程拥有,就释放
if(lock1.isHeldByCurrentThread()){
lock1.unlock();
}
if(lock2.isHeldByCurrentThread()){
lock2.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new InterruptLockDemo("t1",1);
Thread t2 = new InterruptLockDemo("t2",2);
t1.start();
t2.start();
}
????运行如上代码会产生死锁,lock1被线程t1占用,lock2倍线程t2占用,线程t1在等待获取lock2,线程t2在等待获取lock1,都在相互等待获取对方持有的锁,最终产生了死锁,如果是在synchronized关键字情况下发生了死锁现象,程序是无法结束的。使用jps和jstack,可以查看到如下信息:
Found one Java-level deadlock:
=============================
"t2":
waiting for ownable synchronizer 0x000000076b424700, (a java.util.concurrent.locks.ReentrantLock$NonfairSync),
which is held by "t1"
"t1":
waiting for ownable synchronizer 0x000000076b424730, (a java.util.concurrent.locks.ReentrantLock$NonfairSync),
which is held by "t2"
我们对上面代码改造一下,线程t2一直无法获取到lock1,那么等待5秒之后,我们中断获取锁的操作。主要修改一下main方法,如下:
public static void main(String[] args) throws InterruptedException {
Thread t1 = new InterruptLockDemo("t1",1);
Thread t2 = new InterruptLockDemo("t2",2);
t1.start();
t2.start();
// 等待5秒后中断t2线程
TimeUnit.SECONDS.sleep(5);
t2.interrupt();
}
线程t2响应中断请求,然后释放锁
线程t2被中断,中断标志位:false
java.lang.InterruptedException
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
at ReentrantLock.InterruptLockDemo.run(InterruptLockDemo.java:33)
为什么中断标志位是false?t2线程调用了interrupt()方法,将线程的中断标志置为true,线程发送中断信号触发InterruptedException异常之后,中断标志将被清空。因此中断标志位变化是:false -> true -> false
获取锁的4种方法对比:
附上ReentrantLock源码——获取公平锁、非公平锁、释放锁:
https://www.jianshu.com/p/259d076ada14
原文链接:
https://blog.csdn.net/u013851082/article/details/70140223
https://mp.weixin.qq.com/s/gm-EQp_dP7fKHe4-AzLX0g
标签:取消 this adl unlock 响应 重入 中断 art add
原文地址:https://www.cnblogs.com/chenshy/p/11667961.html