摘要:实现死锁的方法有两种,一种是使用同步代码块,另一种是使用重入锁。但是如果调用带超时的方法,那么如果线程在等待时被中断,将抛出一个异常,这是一个非常有用的特性,因为它允许程序打破死锁。
思路:
死锁是指在多线程环境下的这么一种场景,两个(多个)线程在分别拿到自己的锁时尝试获取对方的锁,由于必须等待对方释放锁才能获取,然而双方谁也不肯先释放自己的锁, 导致双方谁都无法继续执行。
通过一个实现runnable接口的类实例作为两个线程的执行对象,在该类中有两个Object的静态变量作为锁.通过该类的一个开关变量实现在同一个run方法中执行两段不同的逻辑,一个先获取锁1, 再获取锁2,另一个分支则刚好相反。
为了使第一个执行的线程在拿到第二个锁之前失去cpu执行权,方便构造死锁场景,在尝试获取第二个锁之前,让线程休眠一段时间,因为sleep()方法不会释放锁。
实现死锁的方法有两种,一种是使用synchronized同步代码块,另一种是使用reentrantlock重入锁。
使用同步代码块实现死锁
代码
public class TestDeadLock implements Runnable {
//开关 private boolean flag; //锁1 private static Object lock1 = new Object(); //锁2 private static Object lock2 = new Object(); public TestDeadLock(boolean flag) { this.flag = flag; } @Override public void run() { if (flag) { synchronized (lock1) { System.out.println(flag + "线程拿到了lock1"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock2) { System.out.println(flag + "线程拿到了lock2"); } } } else { synchronized (lock2) { System.out.println(flag + "线程拿到了lock2"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock1) { System.out.println(flag + "线程拿到了lock1"); } } } } public static void main(String[] args) { Thread thread1 = new Thread(new TestDeadLock(true)); Thread thread2 = new Thread(new TestDeadLock(false)); thread1.start(); thread2.start(); }
}
运行结果
true线程拿到了lock1
false线程拿到了lock2
使用ReentrantLock实现死锁
代码
public class TestDeadLock2 implements Runnable{
private boolean flag; private static ReentrantLock lock1=new ReentrantLock(); private static ReentrantLock lock2=new ReentrantLock(); public TestDeadLock2(boolean flag) { this.flag = flag; } @Override public void run() { try { if(flag){ lock1.lock(); System.out.println(flag + "线程获取了Lock1"); TimeUnit.SECONDS.sleep(1); lock2.lock(); System.out.println(flag+"线程获取了Lock2"); }else{ lock2.lock(); System.out.println(flag + "线程获取了Lock2"); TimeUnit.SECONDS.sleep(1); lock1.lock(); System.out.println(flag+"线程获取了Lock1"); } } catch (InterruptedException e) { e.printStackTrace(); } finally { if(lock1.isHeldByCurrentThread()){ lock1.unlock(); } if(lock2.isHeldByCurrentThread()){ lock2.unlock(); } } } public static void main(String[] args) throws InterruptedException { Thread thread1=new Thread(new TestDeadLock2(true)); Thread thread2=new Thread(new TestDeadLock2(false)); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println("主线程已结束"); }
}
运行结果
false线程获取了Lock2
true线程获取了Lock1
ReentrantLock和Synchronized的区别,具体可见
Java中的ReentrantLock和synchronized两种锁定机制的对比
。总的来说,ReentrantLock所提供的功能比Synchronized要丰富的多,比如
lockInterruptibly
API签名
public void lockInterruptibly() throws InterruptedException
代码
public class TestDeadLock3 implements Runnable {
private boolean flag; static ReentrantLock lock1 = new ReentrantLock(); static ReentrantLock lock2 = new ReentrantLock(); public TestDeadLock3(boolean flag) { this.flag = flag; } @Override public void run() { try { if (flag) { //可中断地加锁 lock1.lockInterruptibly(); System.out.println(flag + "线程获取了lock1"); TimeUnit.SECONDS.sleep(1); lock2.lockInterruptibly(); System.out.println(flag + "线程获取了lock2"); } else { lock2.lockInterruptibly(); System.out.println(flag + "线程获取lock2"); TimeUnit.SECONDS.sleep(1); lock1.lockInterruptibly(); System.out.println(flag + "线程获取了lock1"); } } catch (InterruptedException e) { e.printStackTrace(); } finally { if (lock1.isHeldByCurrentThread()) { lock1.unlock(); System.out.println(flag + "线程释放lock1锁"); } if (lock2.isHeldByCurrentThread()) { lock2.unlock(); System.out.println(flag + "线程释放lock2锁"); } System.out.println(flag + "线程已退出"); } } public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(new TestDeadLock3(true)); Thread thread2 = new Thread(new TestDeadLock3(false)); thread1.start(); thread2.start(); //主线程休眠5秒 TimeUnit.SECONDS.sleep(5); thread1.interrupt(); }
}
运行结果
true线程获取了lock1
false线程获取lock2
java.lang.InterruptedException
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:896) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1221) at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:340)
true线程释放lock1锁
at com.akane.test.reentrantlock.TestDeadLock3.run(TestDeadLock3.java:31)
true线程已退出
at java.lang.Thread.run(Thread.java:744)
false线程获取了lock1
false线程释放lock1锁
false线程释放lock2锁
false线程已退出
Process finished with exit code 0
关于interrupt的用法
synchronized在获锁的过程中是不能被中断的,意思是说如果产生了死锁,则不可能被中断(请参考后面的测试例子)。与synchronized功能相似的reentrantLock.lock()方法也是一样,它也不可中断的,即如果发生死锁,那么reentrantLock.lock()方法无法终止,如果调用时被阻塞,则它一直阻塞到它获取到锁为止。但是如果调用带超时的tryLock方法reentrantLock.tryLock(long timeout, TimeUnit unit),那么如果线程在等待时被中断,将抛出一个InterruptedException异常,这是一个非常有用的特性,因为它允许程序打破死锁。你也可以调用reentrantLock.lockInterruptibly()方法,它就相当于一个超时设为无限的tryLock方法
主线程对Thread1进行了中断,thread1抛出异常,异常被捕获,在finally中释放thread1获得的锁,线程2获得需要的锁,该线程得以继续执行,死锁就被解决了
tryLock
当然,ReentrantLock还提供了另外一个更好的方法解决死锁问题,那就是使用tryLock()方法,该方法会尝试获得锁,如果成功,返回true,失败则返回false。该方法不等待或等待一段时间就返回。
API签名
public boolean tryLock() 立即返回
public boolean tryLock(long timeout, TimeUnit unit) 等待一段时间后返回
死锁的原因在于吃着碗里的看着锅里的,我们让线程拿到一个锁之后无论是否拿到第二个锁,都释放已经拿到的锁,可以将此逻辑放入finally中,配合外层的while(true)多次重复尝试,如果成功获取两个锁,则释放两个锁的同时推出while循环,以下是代码实现,线程睡眠时间由1秒改为1毫秒,减少测试需要的时间
代码
public class TestDeadLock4 implements Runnable{
private boolean flag; static ReentrantLock lock1 = new ReentrantLock(); static ReentrantLock lock2 = new ReentrantLock(); //统计发生死锁的次数 private static int count; public TestDeadLock4(boolean flag) { this.flag = flag; } @Override public void run() { if(flag){ while (true) { if(lock1.tryLock()){ System.out.println(flag+"线程获得了lock1"); try { TimeUnit.MILLISECONDS.sleep(1); try { if(lock2.tryLock()){ System.out.println(flag+"获得了lock2"); } } finally { //同时获得Lock1和lock2,没有发生死锁,任务完成,退出循环 if(lock1.isHeldByCurrentThread()&&lock2.isHeldByCurrentThread()){ System.out.println(flag+"线程执行完毕"+"---------------------"); lock1.unlock(); lock2.unlock(); break; }else{ //说明发生了死锁,只需要释放lock1 count++; System.out.println("发生了"+count+"次死锁"); lock1.unlock(); } } } catch (InterruptedException e) { e.printStackTrace(); } } } }else{ while (true) { if(lock2.tryLock()){ System.out.println(flag+"线程获得了lock2"); try { TimeUnit.MILLISECONDS.sleep(1); try { if(lock1.tryLock()){ System.out.println(flag+"线程获得了lock1"); } } finally { if(lock1.isHeldByCurrentThread()&&lock2.isHeldByCurrentThread()){ System.out.println(flag+"线程执行完毕"+"---------------------"); lock1.unlock(); lock2.unlock(); break; }else{ count++; System.out.println("发生了"+count+"次死锁"); lock2.unlock(); } } } catch (InterruptedException e) { e.printStackTrace(); } } } } } public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(new TestDeadLock4(true)); Thread thread2 = new Thread(new TestDeadLock4(false)); thread1.start(); thread2.start(); }
}
运行结果(部分)
全选复制放进笔记true线程获得了lock1
false线程获得了lock2
发生了3358次死锁
false获得了lock1
false线程执行完毕---------------------
true线程获得了lock1
true获得了lock2
true线程执行完毕---------------------
Process finished with exit code 0
公平锁
除此之外,ReentrantLock还有能实现线程公平获取锁的功能,所谓的公平,指的是在申请获取锁的队列中,排在前面的线程总是优先获得需要的锁,Synchronized同步获得锁的方式是非公平的,举个例子,线程A和B都尝试获得C持有的锁,当C释放该锁时,A和B谁能获得该锁是不确定的,也就是非公平的,而ReentrantLock提供公平地,即先来后到地获取锁的方式。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/65534.html
摘要:的锁是非公平锁,默认情况下也是非公平锁,但可以通过带布尔值的构造函数要求使用公平锁。有序性,是保证线程内串行语义,避免指令重排等。公平性是减少线程饥饿个别线程长期等待锁,但始终无法获取情况发生的一个办法。 目录介绍 1.Synchronize和ReentrantLock区别 1.1 相似点 1.2 区别 1.3 什么是线程安全问题?如何理解 1.4 线程安全需要保证几个基本特性 ...
摘要:线程启动规则对象的方法先行发生于此线程的每一个动作。所以局部变量是不被多个线程所共享的,也就不会出现并发问题。通过获取到数据,放入当前线程处理完之后将当前线程中的信息移除。主线程必须在启动其他线程后立即调用方法。 一、线程安全性 定义:当多个线程访问某个类时,不管运行时环境采用何种调度方式,或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行...
摘要:但是单核我们还是要应用多线程,就是为了防止阻塞。多线程可以防止这个问题,多条线程同时运行,哪怕一条线程的代码执行读取数据阻塞,也不会影响其它任务的执行。 1、多线程有什么用?一个可能在很多人看来很扯淡的一个问题:我会用多线程就好了,还管它有什么用?在我看来,这个回答更扯淡。所谓知其然知其所以然,会用只是知其然,为什么用才是知其所以然,只有达到知其然知其所以然的程度才可以说是把一个知识点...
摘要:近段时间在准备实习的面试,在网上看到一份面试题,就慢慢试着做,争取每天积累一点点。现在每天给自己在面试题编写的任务是题,有时候忙起来可能就没有时间写了,但是争取日更,即使当天没更也会在之后的更新补上。 近段时间在准备实习的面试,在网上看到一份面试题,就慢慢试着做,争取每天积累一点点。 暂时手头上的面试题只有一份,题量还是挺大的,有208题,所以可能讲的不是很详细,只是我自...
阅读 1678·2021-11-22 15:33
阅读 1938·2021-10-08 10:04
阅读 3507·2021-08-27 13:12
阅读 3355·2019-08-30 13:06
阅读 1432·2019-08-29 16:43
阅读 1353·2019-08-29 16:40
阅读 679·2019-08-29 16:15
阅读 2696·2019-08-29 14:13