深入学习JUC,深入了解Java线程中死锁与活锁问题,并理解其解决方法,笔记开记!!!

本文详细介绍了死锁的概念、产生条件及预防和解决策略,探讨了活锁现象,重点讲解了ReentrantLock的特性,如可重入、可打断、可超时和公平锁,以及条件变量的应用。
摘要由CSDN通过智能技术生成


在这里插入图片描述

死锁

检查是否发生了死锁

jstack通过 线程栈快照 定位线程中出现长时间停顿的原因, jconsole 图像界面 检查是否发生了死锁

死锁的概念

多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。
就是两个线程互相占有自己的锁, 却又尝试获取别人的锁, 导致进入死锁状态.

死锁产生的条件
  1. 互斥:

    进程要求对所分配的资源进行排他性控制, 就是一段时间内某资源仅为一个进程所占用

  2. 请求和保持:

    当进程因请求资源而阻塞时, 对已获得的资源保持不放

  3. 不可剥夺:

    进程已经获得的资源在为使用完之前,不能剥夺,只能在使用完由自己释放

  4. 环路等待:

    发生死锁时, 必然存在一个进程–资源的环形链等待链,链中每一个进程已获得的资源同时被下一个进程所请求.

预防死锁
  1. 破坏请求条件:

    资源一次性分配, 这样就不会再有请求了. 但是资源利用率会降低

  2. 破坏请求保持条件:

    进程运行前一次申请它所需要的全部资源, 在它的资源满足前, 不进行运行。一旦运行,资源将不可剥夺.
    这种做法可能严重浪费资源,可能导致饥饿现象.个别线程长时间占用某个资源, 导致该资源的进程迟迟无法运行.

  3. 破坏不可剥夺条件:

    进程申请资源一旦被阻塞, 就必须释放已经占有的所有资源.

  4. 破坏环路等待条件:

    系统给每类资源赋予一个编号, 每一个进程按照编号递增的顺序请求资源, 释放则相反.

解决死锁
  1. 每个线程按照确定的顺序获取锁 , 这样就不会发生一直等待的情况. 但是可能出现饥饿的情况.
  2. 使用tryLock方法固定时长等待锁, 在线程获取锁超时以后, 主动释放之前已经获得的所有锁.

活锁

概念

任务没有被阻塞, 但是由于某些条件没有满足, 导致一直重复尝试–失败–尝试–失败的过程, 处于活锁的实体不断的在改变状态, 活锁有可能自行解开的.

比如一个进程把一个数加到20就退出 , 一个数减到0就退出 ,他们两个一起执行就不会成功.

解决

解决活锁的一个简单的办法就是在下一次尝试获取资源之前, 随机休眠一段时间.

ReentrantLock

概念
  1. 可中断, 其他线程可以把竞争锁的线程打断, 去处理其他逻辑, 防止一直等待
  2. 可设置超时时间, 规定时间内竞争不到锁就放弃, 不像synchroniezd的EntryList一样会一直竞争, 可解决死锁
  3. 可设置公平锁, ReentrantLock默认不公平的,需要自己调用构造方法设置,预防饥饿的情况, 遵循先到先得的原则, 防止线程过多的情况 导致有些锁一直拿不到锁
  4. 可支持多个条件变量, 就是多个类似于synchroniezd一样的waitset. 以后notifyAll的时候可以选择同一个条件的线程一起唤醒.

一般解决饥饿问题不使用公平锁, 会降低并发度, 应该使用tryLock(Time)方法, 一旦获取锁超时就放弃

可重入

同一个线程如果首次获取了这把锁, 那么因为它是这把锁的拥有者, 因此有权利再次获得这把锁.
不可重入的话就会被锁住

public class ReentrantLockTest {
    private static ReentrantLock reentrantLock=new ReentrantLock();
    public static void main(String[] args) {
        reentrantLock.lock();
        try {
            System.out.println("进入m1");
            m1();
        }finally {
            reentrantLock.unlock();
        }
    }
    public static void m1(){
        reentrantLock.lock();
        try {
            System.out.println("进入m2");
            m2();
        }finally {
            reentrantLock.unlock();
        }
    }
    public static void m2(){
        reentrantLock.lock();
        try {
            System.out.println("已经在m2");
        }finally {
            reentrantLock.unlock();
        }
    }
}
可打断
public class ReentrantLockTest {
    private static ReentrantLock reentrantLock=new ReentrantLock();
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(()->{
            try {
                reentrantLock.lockInterruptibly();  //此锁可打断 , 如果仅仅是reentrantLock.lock, 则无法打断
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println("锁被打断");
                return;
            }
            System.out.println("竞争锁成功,执行逻辑");
        });
        reentrantLock.lock(); //主线程获取锁, t1线程就只能竞争 ,并且设置可打断的竞争
        t1.start();
        TimeUnit.SECONDS.sleep(2);  
        t1.interrupt();       //打断
    }
}
可超时
public class ReentrantLockTest {
    private static ReentrantLock reentrantLock=new ReentrantLock();
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(()->{
            try {
                if (!reentrantLock.tryLock(5,TimeUnit.SECONDS)) { //竞争5s锁, 竞争不到返回false
                    System.out.println("5s了,竞争不到锁,执行其他逻辑");
                    return;
                }
                System.out.println("竞争到锁了");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        reentrantLock.lock(); //主线程获取锁, t1线程就只能竞争
        t1.start();
    }
}
可设置公平锁
一般解决饥饿问题不使用公平锁, 会降低并发度, 应该使用tryLock(Time)方法, 一旦获取锁超时就放弃
条件变量
synchronized是所有不满足条件的线程都在一个WaitSet中等待,唤醒时就全部一起唤醒
而ReentrantLock支持多个专门等待不同条件的ConditionObject,类似于waitset,唤醒时按照特定条件的waitset进行唤醒
public class ReentrantLockTest {
    private static ReentrantLock reentrantLock=new ReentrantLock();
    public static void main(String[] args) throws InterruptedException {
        Condition condition1 = reentrantLock.newCondition();     //休息室1
        Condition condition2 = reentrantLock.newCondition();     //休息室2
        Thread t1=new Thread(()->{
            try {
                condition1.await();     //线程1进入休息室1等待
                condition1.awaitNanos(10);  //根据纳秒等待
                condition1.awaitUninterruptibly();  // 可打断的等待
                condition1.signal();   //唤醒休息室1特定的一个
                condition1.signalAll();   //唤醒休息室2所有
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        Thread t2=new Thread(()->{
            try {
                condition2.await();     //线程2进入休息室2等待
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

木 木 水.

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

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

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

打赏作者

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

抵扣说明:

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

余额充值