死锁

讲到死锁,首先我们需要知道什么是锁?只有清楚了锁的概念,我们才会明白死锁的真正含义。下面我会按照我的理解,分成几部分来阐述:

  1. 我们为什么需要锁以及锁的概念
  2. 为什么会出现死锁
  3. 怎么解决死锁
  4. 产生死锁的代码案例
    • synchronized 实现死锁以及解决办法
    • Lock 实现死锁以及解决办法

1、 我们为什么需要锁以及锁的概念

在单线程的时候,我们往往不需要考虑到锁的概念,因为单线程的时候,系统和变量资源是被一个线程使用,按照代码顺序执行。但是当我们考虑到多线程的时候,就需要考虑到资源的安全性了。比如,多个线程共同操作一个变量资源(我们称之为共享变量),但是根据java的内存模型,每个线程会把共享变量拷贝到自己的工作内存(称为副本),当每个线程操作这个共享变量的时候,都会在自己的工作内存中进行操作,而后在主内存中进行刷新变量的值,但是别的线程往往并不能及时看到这个刷新的值,而是对旧值进行操作,所以会出现我们不希望的结果。所以当多个线程会对同一个资源进行操作时,我们希望当一个线程对这个资源进行操作时,其它线程不能进行操作(拿不到这个资源),此时就只有一个线程对这个资源进行操作,然后及时更新到主内存。这个过程我们可以称之为锁,我们对这个资源进行了上锁。当然我们可以对变量,对象,以及整个方法过程等进行上锁。上锁的目的是保证资源的安全性。

2、为什么会出现死锁

简单的来说,当某个线程想要操作某个资源的时候,发现这个资源已经被上锁了(被其他的线程使用并且上锁),但是自己又需要完成对这个资源的操作,才能进行对自己锁的释放,或者程序的完成。但是另外一个使用这个资源的线程,出现了不能对这个资源进行锁的释放,比如陷入了死循环或者需要调用另外一个线程已经上锁的资源,这个时候,某个线程或者很多线程始终结束不了代码的运行,这种情况称之为死锁。
其实换句话说,也就是某个线程对某个资源进行了独占,导致其它的线程一直访问不到这个资源,使得代码陷入了死循环。

3、怎么解决死锁

  • 避免某个线程对某个资源进行独占,及时释放对该资源的锁。
  • 设置计时,某个线程长时间不能结束,就要及时的释放对某个资源的锁。
  • 避免把代码写成闭环的状态,避免死锁。

4、产生死锁的代码案例

synchronized 实现死锁的思路:
  1. 分别定义两个资源类ResA和ResB
  2. 定义一个使用资源的类UseRes,并继承Thread类
  3. 在UseRes类中定义ResA和ResB的对象,并用statis修饰
  4. 定义成员变量id,记录该线程的id号
  5. 定义UseRes的有参构造方法,参数为int id,并传给成员变量id
  6. 重写UseRes类中的run方法
  7. run方法的实现
    当是id=0的线程对象时,首先synchronized(ResA){
    // 在这个锁中,我们对ResB继续上锁
    synchronized(ResB){
    // to do
    }
    }
    当是id=1的线程对象时,首先synchronized(ResB){
    // 在这个锁中,我们对ResB继续上锁
    synchronized(ResA){
    // to do
    }
    }
public class UseRes extends Thread{

    static ResA A = new ResA();
    static ResB B = new ResB();
//    int resA=0;
//    int resB=0;
    // 线程的编号
    int id;

    // 构造方法,传入线程的编号
    public  UseRes(int i) {
        this.id=i;
    }
	
	// 内部类A
    public static class ResA{
        String resA="A";

        ResA(){};
    }

	// 内部类B
    public static class ResB{
        String resA="B";

        ResB(){};
    }


    @Override
    public void run() {
        if(id==0){
            // 给某个对象加锁
            synchronized (A){
                System.out.println("线程id0正在使用A资源");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (B){
                    System.out.println("线程id0正在使用B资源");
                }
            }
        }else {
            synchronized (B){
                System.out.println("线程id1正在使用B资源");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (A){
                    System.out.println("线程id1正在使用A资源");
                }
            }
        }
    }

    public static void main(String[] args) {
        UseRes use01 = new UseRes(0);
        UseRes use02 = new UseRes(1);

        use01.start();
        use02.start();
    }
}

改进方法:
把每个嵌套的synchronized()移到外面,避免了锁的嵌套和锁的及时释放。

Lock 实现死锁的思路

因为Lock是个接口,我们使用ReentrantLock这个实现类来创建对象

  1. 定义一个类并实现Runnable 接口
  2. 创建两个Lock 的对象,lock1和lock2
  3. 定义一个flag的布尔成员变量
  4. 定义构造方法,传入的值为flag
  5. 重写run方法
  6. run方法的实现思路
    if(flag){
    // 线程1获得锁1
    lock1.lock()
    // 线程1获得锁2
    lock1.lock()
    }else{
    // 线程2获得锁2
    lock2.lock()
    // 线程2获得锁1
    lock2.lock()
    }
    // 释放锁
    lock1.unlock()
    lock2.unlock()
public class TestLockDemo implements Runnable{

    private boolean flag;
    private static ReentrantLock lock1= new ReentrantLock();
    private static ReentrantLock lock2= new ReentrantLock();

    public TestLockDemo(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        try {
            if(flag){
                lock1.lock();
                System.out.println(Thread.currentThread().getName()+"获得了锁lock1");
                lock2.lock();
                System.out.println(Thread.currentThread().getName()+"获得了锁lock2");
            }else {
                lock2.lock();
                System.out.println(Thread.currentThread().getName()+"获得了锁lock2");
                lock1.lock();
                System.out.println(Thread.currentThread().getName()+"获得了锁lock1");
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            // isHeldByCurrentThread()   查询当前线程是否保持此锁。
            if(lock1.isHeldByCurrentThread()){
                lock1.unlock();
            }
            if(lock2.isHeldByCurrentThread()){
                lock2.unlock();
            }
        }

    }

    public static void main(String[] args) {
        TestLockDemo lock1= new TestLockDemo(true);
        TestLockDemo lock2= new TestLockDemo(false);

        Thread th1 = new Thread(lock1,"线程1");
        Thread th2 = new Thread(lock2,"线程2");

        th1.start();
        th2.start();

    }
}

改进方法:在每个线程使用一把锁的时候,把上把锁及时释放,避免死锁情况的发生。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

肖大仙~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值