Java深入学习之死锁
程序员文章站
2022-07-15 13:59:29
...
最近在研究Java并发,学习死锁时偶然发现了一种嵌套管程锁死,所以自己实现了下,可能在不小心中就会犯这种错误。
1、死锁实现
死锁原理很简单,就是线程1先获取锁A,在获取锁B;而线程2先获取锁B,在获取锁A,由于两个线程获取顺序不一样,都没有将各自的锁释放,所以就出现了死锁。代码实现也很简单:
简单运行下,
Thread-0 输入为:true
Thread-1 输入为:false
然后都停留在获取第二个锁的阶段,从而照成死锁,解决死锁也很简单,要么保持获取锁的顺序一致,要么就是保证获取锁时,没有其他线程占有锁,或者用JDK1.5后提供的Lock实现都可以,比如tryLock等方法,获取之前先判断下。
当然还有就是将对象锁Object用static修饰,测试的时候无论new多少个实例,都会造成死锁。
2、嵌套管程锁死
前几天再并发编程网上发现,然后自己实现了一下,这种情况不容易制造,也比较难于重现。其照成的原因是:线程1获取了A和B锁,然后释放B锁,等待线程2发过来的信号,然后释放A锁;线程2必须同时获取A锁和B锁,才能向线程1发送信号,这样就会照成锁死。代码实现也不复杂,就在上述代码中添加一个lock和unlock方法,run方法进行修改下:
这是一段简单的lock实现,原理类似java新增concurrent包的Lock类
执行结果为:
Thread-0 : 我获得了锁
Thread-0 : 我将要释放锁
由此可以看出,线程Thread-0 在执行unlock时就锁死了,因为lock的o1锁没有进行释放,而unlock又需要获取o1锁,那么就照成死锁了。
当然,由于测试时都是用的同一个实例,没有释放当然或照成锁死了。确实,如果是对两个不同的实例进行测试,就不会出现这种情形,当然也就不会有线程安全问题了。
总之,不管什么形式的死锁,造成的原因就是就是因为同一个锁没有被正确的释放时,其他线程希望获取改锁,当然,在JDK1.5之后,提供的Lock类就提供了避免这种情况的方式,使用起来非常方便。
1、死锁实现
死锁原理很简单,就是线程1先获取锁A,在获取锁B;而线程2先获取锁B,在获取锁A,由于两个线程获取顺序不一样,都没有将各自的锁释放,所以就出现了死锁。代码实现也很简单:
public class DeathLock implements Runnable{ private boolean flag = true; private Object o1 = new Object(); private Object o2 = new Object(); /* (non-Javadoc) * @see java.lang.Runnable#run() */ public void run() { if (flag) { System.out.println(Thread.currentThread().getName()+ " 输入为:" + flag); flag = false; synchronized (o1) { try { Thread.sleep(2000); synchronized (o2) { System.out.println(Thread.currentThread().getName()+ " 我是真的"); } } catch (InterruptedException e) { e.printStackTrace(); } } } else { System.out.println(Thread.currentThread().getName()+ " 输入为:" + flag); synchronized (o2) { try { Thread.sleep(1000); synchronized (o1) { System.out.println(Thread.currentThread().getName()+ " 我是假的"); } } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) { DeathLock lock1 = new DeathLock(); Thread th1 = new Thread(lock1); Thread th2 = new Thread(lock1); th1.start(); th2.start(); } }
简单运行下,
Thread-0 输入为:true
Thread-1 输入为:false
然后都停留在获取第二个锁的阶段,从而照成死锁,解决死锁也很简单,要么保持获取锁的顺序一致,要么就是保证获取锁时,没有其他线程占有锁,或者用JDK1.5后提供的Lock实现都可以,比如tryLock等方法,获取之前先判断下。
当然还有就是将对象锁Object用static修饰,测试的时候无论new多少个实例,都会造成死锁。
2、嵌套管程锁死
前几天再并发编程网上发现,然后自己实现了一下,这种情况不容易制造,也比较难于重现。其照成的原因是:线程1获取了A和B锁,然后释放B锁,等待线程2发过来的信号,然后释放A锁;线程2必须同时获取A锁和B锁,才能向线程1发送信号,这样就会照成锁死。代码实现也不复杂,就在上述代码中添加一个lock和unlock方法,run方法进行修改下:
public void lock() { synchronized (o1) { while (!flag) { try { Thread.sleep(200); synchronized (o2) { o2.wait(); } } catch (InterruptedException e) { e.printStackTrace(); } } flag = false; } } public void unlock() { synchronized (o1) { try { Thread.sleep(200); flag = true; synchronized (o2) { o2.notify(); } } catch (InterruptedException e) { e.printStackTrace(); } } } public void run(){ lock(); try { System.out.println(Thread.currentThread().getName() + " : 我获得了锁"); } finally { System.out.println(Thread.currentThread().getName() + " : 我将要释放锁"); unlock(); System.out.println(Thread.currentThread().getName() + " : 我释放了锁"); } }
这是一段简单的lock实现,原理类似java新增concurrent包的Lock类
执行结果为:
Thread-0 : 我获得了锁
Thread-0 : 我将要释放锁
由此可以看出,线程Thread-0 在执行unlock时就锁死了,因为lock的o1锁没有进行释放,而unlock又需要获取o1锁,那么就照成死锁了。
当然,由于测试时都是用的同一个实例,没有释放当然或照成锁死了。确实,如果是对两个不同的实例进行测试,就不会出现这种情形,当然也就不会有线程安全问题了。
总之,不管什么形式的死锁,造成的原因就是就是因为同一个锁没有被正确的释放时,其他线程希望获取改锁,当然,在JDK1.5之后,提供的Lock类就提供了避免这种情况的方式,使用起来非常方便。