java死锁-编程思维

一、死锁产生的四个必要条件

1.1、互斥

即当资源被一个线程使用(占有)时,别的线程不能使用

1.2、不可剥夺

资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。

1.3、请求和保持

即当资源请求者在请求其他的资源的同时保持对原有资源的占有。

1.4、循环等待

即存在一个等待闭环链路:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。

二、模拟死锁产生

假如有这样的一个需求,线程需要获取MN两个数据资源之后,才能进行下一步的操作,MN获取顺序没有先后之分

2.1、思路

定义两个数据资源M、N,两个线程Thread-A、Thread-B分别获取资源M资源N两个资源

2.2、synchronized实现

package com.duchong.concurrent.thread;

import java.util.concurrent.TimeUnit;

/**
 * 线程A和线程B同时去获取数据dataM和dataN
 * 每个线程获取两个锁
 * @author DUCHONG
 * @since 2020-09-04 15:02
 **/
public class DeadLockDemo {

    //定义两个资源dataA和dataB
    public static final String DATA_M="dataM";
    public static final String DATA_N="dataN";


    public static void main(String[] args) {

        new Thread(new DeadThreadA(),"Thread-A").start();

        new Thread(new DeadThreadB(),"Thread-B").start();

    }


    static class DeadThreadA implements Runnable{

        public DeadThreadA() {
        }

        @Override
        public void run() {
            try {

                    System.out.println(Thread.currentThread().getName()+"---启动");
                    synchronized (DATA_M){

                        System.out.println(Thread.currentThread().getName()+"---获得数据M");
                        System.out.println(Thread.currentThread().getName()+"---尝试获取数据N");
                        //睡一会,模拟 线程B锁住数据B
                        TimeUnit.SECONDS.sleep(3L);
                        synchronized (DATA_N){
                            System.out.println(Thread.currentThread().getName()+"---获取到数据N");
                            System.out.println(Thread.currentThread().getName()+"---数据M和N获取完毕,可以进行下一步操作");
                        }
                    }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    static class DeadThreadB implements Runnable{

        public DeadThreadB() {
        }

        @Override
        public void run() {
            try {

                    System.out.println(Thread.currentThread().getName()+"---启动");
                    synchronized (DATA_N){

                        System.out.println(Thread.currentThread().getName()+"---获得数据N");
                        System.out.println(Thread.currentThread().getName()+"---尝试获取数据M");

                        synchronized (DATA_M){
                            System.out.println(Thread.currentThread().getName()+"---获取到数据M");
                            System.out.println(Thread.currentThread().getName()+"---数据M和N获取完毕,可以进行下一步操作");
                        }
                    }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }


}

2.3、ReentrantLock实现

package com.duchong.concurrent.thread;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 线程A和线程B同时去获取数据dataM和dataN
 * 每个线程获取两个锁
 * @author DUCHONG
 * @since 2020-09-04 15:02
 **/
public class DeadLockDemo3 {

    static Lock lockM=new ReentrantLock();
    static Lock lockN=new ReentrantLock();

    public static void main(String[] args) {

        new Thread(new DeadThreadA(),"Thread-A").start();

        new Thread(new DeadThreadB(),"Thread-B").start();

    }


    static class DeadThreadA implements Runnable{

        public DeadThreadA() {
        }

        @Override
        public void run() {
            try {

                    System.out.println(Thread.currentThread().getName()+"---启动");
                    lockM.lock();
                        System.out.println(Thread.currentThread().getName()+"---获得数据M");
                        System.out.println(Thread.currentThread().getName()+"---尝试获取数据N");

                    //睡一会,模拟 线程B锁住数据B
                    TimeUnit.SECONDS.sleep(3L);

                    lockN.lock();
                        System.out.println(Thread.currentThread().getName()+"---获取到数据N");
                        System.out.println(Thread.currentThread().getName()+"---数据M和N获取完毕,可以进行下一步操作");

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    static class DeadThreadB implements Runnable{

        public DeadThreadB() {
        }

        @Override
        public void run() {
            try {

                System.out.println(Thread.currentThread().getName()+"---启动");
                lockN.lock();
                    System.out.println(Thread.currentThread().getName()+"---获得数据N");
                    System.out.println(Thread.currentThread().getName()+"---尝试获取数据M");

                lockM.lock();
                    System.out.println(Thread.currentThread().getName()+"---获取到数据M");
                    System.out.println(Thread.currentThread().getName()+"---数据M和N获取完毕,可以进行下一步操作");
            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
    }


}

2.4、运行结果

2.5、VisualVM查看

三、死锁解决方法

3.1、synchronized死锁解决方法

synchronized实现的死锁是由于在一个线程里面获取了两个锁导致的,如果一个线程每次只能获取一个锁,那么就不会出现由于嵌套持有锁导致的死锁,要达到既能一个线程每次获取一个锁,有能获取到两个资源的效果,可以把资源数据抽取出来放在一个独立的Data类里,然后让线程A线程B去获取

3.1.1、Data类

package com.duchong.concurrent.thread;

/**
 * 资源数据类-单独抽出来
 * @author DUCHONG
 * @since 2020-09-04 17:57:43
 */
public class Data{

        public static final String DATA_M="dataM";
        public static final String DATA_N="dataN";

         /**
          * 假如有一个线程持有dataM的锁,另一个线程进来,阻塞,更不用说进来的线程获取dataN了
          * 先获取数据B
          */
        public  void getData(){
            try {

                System.out.println(Thread.currentThread().getName()+"---启动");
                synchronized (Data.DATA_M){

                    System.out.println(Thread.currentThread().getName()+"---获得数据M");
                    System.out.println(Thread.currentThread().getName()+"---尝试获取数据N");

                    synchronized (Data.DATA_N){
                        System.out.println(Thread.currentThread().getName()+"---获取到数据N");
                        System.out.println(Thread.currentThread().getName()+"---数据M和N获取完毕,可以进行下一步操作");
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }

3.1.2、线程A和线程B

package com.duchong.concurrent.thread;

/**
 * 将资源数据单独抽离出来
 * 每隔线程获取一把锁
 * @author DUCHONG
 * @since 2020-09-04 15:02
 **/
public class DeadLockDemo2 {

    public static void main(String[] args) {

        new Thread(new DeadThreadA(),"Thread-A").start();

        new Thread(new DeadThreadB(),"Thread-B").start();

    }



    static class DeadThreadA implements Runnable{

        public DeadThreadA() {
        }

        @Override
        public void run() {
            new Data().getData();
        }
    }

    static class DeadThreadB implements Runnable{

        public DeadThreadB() {
        }

        @Override
        public void run() {
            new Data().getData();
        }
    }


}

3.1.3、运行结果

没有发生死锁,符合预期

3.2、ReentrantLock死锁解决方法

利用tryLock()方法尝试获取锁,设置超时时间为5s,如果获取不成功,释放已占有的资源给另外一个线程

package com.duchong.concurrent.thread;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 线程A和线程B同时去获取数据dataM和dataN
 * 每个线程获取两个锁
 * @author DUCHONG
 * @since 2020-09-04 15:02
 **/
public class DeadLockDemo4 {


    static Lock lockM=new ReentrantLock();
    static Lock lockN=new ReentrantLock();

    public static void main(String[] args) {

        new Thread(new DeadThreadA(),"Thread-A").start();

        new Thread(new DeadThreadB(),"Thread-B").start();

    }


    static class DeadThreadA implements Runnable{

        public DeadThreadA() {
        }

        @Override
        public void run() {
            try {

                System.out.println(Thread.currentThread().getName()+"---启动");
                //lockM 属于thread-a 独占
                lockM.lock();
                    System.out.println(Thread.currentThread().getName()+"---获得数据M");
                    System.out.println(Thread.currentThread().getName()+"---尝试获取数据N");

                //睡一会,模拟 线程B锁住数据B
                TimeUnit.SECONDS.sleep(3L);

                //以下代码不执行,此时lockN 属于thread-b 独占,thread-b释放lockN之后,thread-a 获取到lockN,在执行下面的代码
                lockN.lock();
                        System.out.println(Thread.currentThread().getName()+"---获取到数据N");
                        System.out.println(Thread.currentThread().getName()+"---数据M和N获取完毕,可以进行下一步操作");
                //thread-a 获取到lockN之后,释放lockM
                lockM.unlock();

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    static class DeadThreadB implements Runnable{

        public DeadThreadB() {
        }

        @Override
        public void run() {
            try {

                System.out.println(Thread.currentThread().getName()+"---启动");
                //此时lockN 属于thread-b 独占
                lockN.lock();
                    System.out.println(Thread.currentThread().getName()+"---获得数据N");
                    System.out.println(Thread.currentThread().getName()+"---尝试获取数据M");

                //thread-b尝试获取thread-a的lockM 5s ,失败则释放thread-b独占的 lockN,以防死锁
                if (lockM.tryLock(5L, TimeUnit.SECONDS)) {

                    System.out.println(Thread.currentThread().getName()+"---获取到数据M");
                    System.out.println(Thread.currentThread().getName()+"---数据M和N获取完毕,可以进行下一步操作");
                }
                else{
                    //释放thread-b独占的 lockN,此时thread-a 可以获得lockN
                    lockN.unlock();
                    //thread-a 释放lockM后执行
                    lockM.lock();
                    System.out.println(Thread.currentThread().getName()+"---获取到数据M");
                    System.out.println(Thread.currentThread().getName()+"---数据M和N获取完毕,可以进行下一步操作");
                }

            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
    }


}

3.2.1、运行结果

没有发生死锁。

版权声明:本文版权归作者所有,遵循 CC 4.0 BY-SA 许可协议, 转载请注明原文链接
https://www.cnblogs.com/geekdc/p/13616848.html

关于多线程的参数问题-编程思维

体会多线程的参数问题,可以用下面的代码测试,分别放开 注释1 和 注释2 部分,查看两次结果的不同之处。 注释1 部分,pageData 是一个引用对象,在其中一个线程中,引用对象的值进行的变动,它会影响到 其他的线程上。注释2部分,则根据传入的参数,构造了一个线程内临时变量。 method1 和 method2 提供

谈谈iOS中的锁-编程思维

1 前言 近日工作不是太忙,刚好有时间了解一些其他东西,本来打算今天上午去体检,但是看看天气还是明天再去吧,也有很大一个原因:就是周六没有预约上!闲话少说,这里简单对锁来个简单介绍分享。 2 目录 第一部分:什么是锁 第二部分:锁的分类 第三部分:锁的作用 第四部分:iOS中锁的实现 第一部分:什么是锁 从小就知道

【Java进阶】并发编程-编程思维

PS:整理自极客时间《Java并发编程》 1. 概述 三种性质 可见性:一个线程对共享变量的修改,另一个线程能立刻看到。缓存可导致可见性问题。 原子性:一个或多个CPU执行操作不被中断。线程切换可导致原子性问题。 有序性:编译器优化可能导致指令顺序发生改变。编译器优化可能导致有序性问题。 三个问题 安全性问题

java并发锁-编程思维

序号 锁名称 应用 1 乐观锁 CAS 2 悲观锁 synchronized、vector、hashtable 3 自旋锁 CAS 4 可重入锁 synchronized、Reentrantlock、Lock 5 读写锁 ReentrantReadWriteLock,CopyOnWriteArr

volatile底层实现原理-编程思维

内存屏障 原文地址 作者:Martin Thompson 译者:一粟 校对:无叶,方腾飞 本文我将和大家讨论并发编程中最基础的一项技术:内存屏障或内存栅栏,也就是让一个CPU处理单元中的内存状态对其它处理单元可见的一项技术。 CPU使用了很多优化技术来实现一个目标:CPU执行单元的速度要远超主存访问速度。在上一篇文章

java并发工具类-编程思维

一、CountDownLatch 字面意思:倒计时锁闩,该类可以实现一个线程在等其他多个线程执行完之后,继续执行。 入参是一个计数器的值,当一个线程执行完毕时调用countDown()方法,计数器值会减1,当计数器值为0时,被await()阻塞的线程将被唤醒。 CountDownLatch latch = new Co

Java编程的逻辑 (66) - 理解synchronized-编程思维

​本系列文章经补充和完善,已修订整理成书《Java编程的逻辑》,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http://item.jd.com/12299018.html 上节我们提到了多线程共享内存的两个问题,一个是竞态条件,另一个是内存可见

Java编程的逻辑 (65) - 线程的基本概念-编程思维

​本系列文章经补充和完善,已修订整理成书《Java编程的逻辑》,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http://item.jd.com/12299018.html 在之前的章节中,我们都是假设程序中只有一条执行流,程序从main方法的第