文档章节

实现死锁的两种方式以及ReentrantLock的额外功能(未完待续)

dhehudhue
 dhehudhue
发布于 2016/02/27 22:35
字数 1895
阅读 462
收藏 36

思路:

  1. 死锁是指在多线程环境下的这么一种场景,两个(多个)线程在分别拿到自己的锁时尝试获取对方的锁,由于必须等待对方释放锁才能获取,然而双方谁也不肯先释放自己的锁, 导致双方谁都无法继续执行。

  2. 通过一个实现runnable接口的类实例作为两个线程的执行对象,在该类中有两个Object的静态变量作为锁.通过该类的一个开关变量实现在同一个run方法中执行两段不同的逻辑,一个先获取锁1, 再获取锁2,另一个分支则刚好相反。

  3. 为了使第一个执行的线程在拿到第二个锁之前失去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要丰富的多,比如

ReentrantLock的额外功能

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
true线程释放lock1锁
java.lang.InterruptedException
false线程获取了lock1
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:896)
true线程已退出
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1221)
false线程释放lock1锁
	at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:340)
false线程释放lock2锁
	at com.akane.test.reentrantlock.TestDeadLock3.run(TestDeadLock3.java:31)
false线程已退出
	at java.lang.Thread.run(Thread.java:744)

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
                                //统计变量也要注意多线程问题
                                synchronized (TestDeadLock4.class) {
                                    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{
                                synchronized (TestDeadLock4.class) {
                                    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();
    }
}

运行结果(部分)

发生了3335次死锁
false线程获得了lock2
发生了3336次死锁
true线程获得了lock1
发生了3337次死锁
false线程获得了lock2
发生了3338次死锁
true线程获得了lock1
发生了3339次死锁
false线程获得了lock2
发生了3340次死锁
true线程获得了lock1
发生了3341次死锁
true获得了lock2
true线程执行完毕---------------------
false线程获得了lock2
false线程获得了lock1
false线程执行完毕---------------------

Process finished with exit code 0

公平锁

除此之外,ReentrantLock还有能实现线程公平获取锁的功能,所谓的公平,指的是在申请获取锁的队列中,排在前面的线程总是优先获得需要的锁,Synchronized同步获得锁的方式是非公平的,举个例子,线程A和B都尝试获得C持有的锁,当C释放该锁时,A和B谁能获得该锁是不确定的,也就是非公平的,而ReentrantLock提供公平地,即先来后到地获取锁的方式。


© 著作权归作者所有

dhehudhue
粉丝 2
博文 4
码字总数 4902
作品 0
美国
私信 提问
加载中

评论(1)

byteRun
byteRun
我发现使用同步方法,死不了,怎么回事3
阅读JDK8 CopyOnWriteArraySet应该了解什么

1、为什么说该集合是线程安全 final transient ReentrantLock lock = new ReentrantLock(); private transient volatile Object[] array; public boolean add(E e) { } 该集合中add,set通过......

Huangchp
2017/10/19
44
0
ReentrantLock(重入锁)功能详解和应用演示

1. ReentrantLock简介 jdk中独占锁的实现除了使用关键字synchronized外,还可以使用ReentrantLock。虽然在性能上ReentrantLock和synchronized没有什么区别,但ReentrantLock相比synchronized而...

takumiCX
2018/07/19
0
0
Java多线程之内置锁与显式锁

Java中具有通过Synchronized实现的内置锁,和ReentrantLock实现的显示锁,这两种锁各有各的好处,算是互有补充,今天就来做一个总结。 Synchronized 内置锁获得锁和释放锁是隐式的,进入syn...

MageekChiu
2017/08/06
0
0
Java并发编程之线程安全、线程通信

Java多线程开发中最重要的一点就是线程安全的实现了。所谓Java线程安全,可以简单理解为当多个线程访问同一个共享资源时产生的数据不一致问题。为此,Java提供了一系列方法来解决线程安全问题...

leoliu168
2018/11/07
0
0
《Java并发编程实战》读书笔记四:活跃性和性能,死锁和显示锁

一、活跃性危险:死锁 定义:当一个线程永远占有一个锁, 而其他线程尝试去获得这个锁, 那么它们将永远被阻塞。 例如:当线程占有锁L时, 想要获得锁M, 但是同时, 线程B持有M, 并尝试获得L, 两...

小七奇奇
2018/08/17
0
0

没有更多内容

加载失败,请刷新页面

加载更多

好程序员大数据学习路线分享函数+map映射+元祖

好程序员大数据学习路线分享函数+map映射+元祖,大数据各个平台上的语言实现 hadoop 由java实现,2003年至今,三大块:数据处理,数据存储,数据计算 存储: hbase --> 数据成表 处理: hive --> 数...

好程序员官方
今天
7
0
tabel 中含有复选框的列 数据理解

1、el-ui中实现某一列为复选框 实现多选非常简单: 手动添加一个el-table-column,设type属性为selction即可; 2、@selection-change事件:选项发生勾选状态变化时触发该事件 <el-table @sel...

everthing
今天
6
0
【技术分享】TestFlight测试的流程文档

上架基本需求资料 1、苹果开发者账号(如还没账号先申请-苹果开发者账号申请教程) 2、开发好的APP 通过本篇教程,可以学习到ios证书申请和打包ipa上传到appstoreconnect.apple.com进行TestF...

qtb999
今天
10
0
再见 Spring Boot 1.X,Spring Boot 2.X 走向舞台中心

2019年8月6日,Spring 官方在其博客宣布,Spring Boot 1.x 停止维护,Spring Boot 1.x 生命周期正式结束。 其实早在2018年7月30号,Spring 官方就已经在博客进行过预告,Spring Boot 1.X 将维...

Java技术剑
今天
18
0
浅谈java过滤器Filter

一、简介 Servlet中的过滤器Filter是实现了javax.servlet.Filter接口的服务器端程序,主要的用途是过滤字符编码、做一些业务逻辑判断如是否有权限访问页面等。其工作原理是,只要你在web.xml...

青衣霓裳
今天
9
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部