likes
comments
collection
share

死锁/活锁/饥饿/ReentrantLock锁

作者站长头像
站长
· 阅读数 29

多把锁

在有些场景中,我们需要写一下双重锁的操作:

synchronized(objA){
    synchronized(objB){
        ...
    }
}

简单理解就是一种尝试性的操作:拥有一把锁了,再去拿一把锁。

死锁

在并发中,多把锁的情况下,容易产生死锁,例如:各自拥有一把锁的情况下,还需要拿别人正在使用的锁,而别人也在等着你释放锁。

小示例:

public class Demo {
    static final Object left = new Object();
    static final Object right = new Object();
    public static void main(String[] args) {
        Thread thread1 = new Thread(()->{
            // 锁住left
           synchronized (left){
               // 睡一会
               try {
                   TimeUnit.SECONDS.sleep(1);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               // 拿另外一个锁
               synchronized (right){
                   System.out.println("SUCCESS");
               }
           }
        });

        Thread thread2 = new Thread(()->{
            // 锁住right
            synchronized (right){
                // 睡一会
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 拿另外一个锁
                synchronized (left){
                    System.out.println("SUCCESS");
                }
            }
        });
        thread1.start();
        thread2.start();
    }
}

定位死锁的方法

  1. jconsole工具;(在搜索中搜索jconsole即可)
  2. 使用jps定位进程id,再用jstack定位死锁。(如果有死锁会提示:Found one Java-level deadlock

使用方法一,检测之前代码中的死锁:

死锁/活锁/饥饿/ReentrantLock锁

哲学家就餐问题

问题描述:有五位哲学家,围坐在圆桌旁。

  1. 他们只做两件事,思考和吃饭,思考一会后就吃饭,吃完饭就思考;
  2. 吃饭需要以两根筷子吃饭,桌上一共五根筷子;
  3. 如果筷子被身边的人拥有,自己就需要等待。

死锁产生情况

每个哲学都同时占有一把筷子还需要等别人释放筷子,则发生死锁。

活锁

线程在某些条件下不断尝试重新执行某个操作,但由于条件没有改变或其他原因导致操作不成功,线程无法取得进展。与死锁不同,线程在活锁中并不被阻塞,它们可以继续执行,但是执行却是一种循环无法终止。

饥饿

部分线程一直拿不到锁(可能是优先级太低或者是同步加锁一直取不到锁),得不到CPU去调度,也无法结束该线程,称之为饥饿。


以上的问题可以使用ReentrantLock锁解决。

ReentrantLock锁

对比synchronized:

  1. 可中断(避免死锁,被动的方式)
  2. 可以设置抢锁的超时时间(主动的方式)
  3. 可以设置为公平锁
  4. 支持多个条件变量(根据条件可进入不同的waitSet中,而synchronized只有一个)
  5. 支持可重入(反复对同一个对象加锁)

语法:

private static ReentrantLock reentrantLock = new ReentrantLock();

// 加锁
reentrantLock.lock();

try{
    // ...临界区资源操作
} finally{
    // 释放锁
    reentrantLock.unlock();
}

可以被打断

  1. 加锁的时候使用:reentrantLock.lockInterruptibly();设置可打断锁
  2. 其它线程调用:thread.interrupt();打断thread的等待
public class Demo {
    private static ReentrantLock reentrantLock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            try {
                //reentrantLock.lock(); // 无法被打断
                // 如果没有竞争那么此方法就会获取 lock 对象锁
                // 如果有竞争就进入阻塞队列,可以被其它线程用interrupt方法打断
                reentrantLock.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println("没有获得锁");
                return; // 没有获得锁
            }
            try {
                System.out.println("获取到锁");
            } finally {
                reentrantLock.unlock();
            }
        }, "thread1");
        reentrantLock.lock(); // 主线程先锁起来
        thread.start(); // 此时其它线程无法获得锁,进入阻塞队列,但是lockInterruptibly是可以被打断的
        TimeUnit.SECONDS.sleep(1);
        thread.interrupt(); // 打断
    }
}

锁超时

同时也支持其它线程被动打断

  • 尝试获取锁:reentrantLock.tryLock(),获取不到锁立刻结束等待
    • 返回值:true 获得到锁
    • 返回值:false 没有获得到锁

根据返回值可以再做逻辑判断,比如说:return;让其直接返回。

  • 带参数的:reetrantLock.tryLock(long,TimeUnit),尝试获取锁(在long,单位TimeUnit中)

公平锁

synchronized是一种不公平的锁,当其它线程抢不到锁时会进入waitSet中,锁释放时,不会根据进入waitSet的先后而拿锁,是一种随机抢锁的过程,故称为:不公平锁。

ReentrantLock 默认也是不公平锁

构造方法中,传入一个布尔值,改变其公平性。

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

按先入先得的方式获得锁(会降低并发度,影响性能)。

条件变量

形象的比喻:ReentrantLock拥有多个休息室。

// 创建一个休息室(waitSet)
Condition condition = reentrantLock.newCondition();

// 必须先获得锁
reentrantLock.lock();

// 进入休息室
condition.await(); // 会释放锁

其它线程唤醒:

// 先获得锁

// 对标 notify
condition.signal();

// 对标 notifyAll
confotion.signalAll();

使用方式

使用synchronized时,通常是对一个普通对象进行加锁,而使用ReentrantLock锁时,可以让普通对象继承ReentrantLock类。

应用案例:

  • 解决哲学家就餐问题
转载自:https://juejin.cn/post/7238917665863139387
评论
请登录