ReentrantLock

目录

ReentrantLock

ReentrantLock语法

ReentrantLock可重入

ReentrantLock可打断

ReentrantLock锁超时

ReentrantLock解决哲学家就餐问题

ReentrantLock公平锁

ReentrantLock条件变量


ReentrantLock

ReentrantLock 相比于synchronized的特点 :

  • 可中断:比如A线程拥有锁,B线程能给他取消掉
  • 可以设置超时时间,规定一段时间竞争锁,如果获取不到锁,那么就不竞争了做一些其他的逻辑.
  • 可以设置为公平锁 ,防止线程饥饿的情况,大家都在排队等,先到的先得到锁.
  • 支持多个条件变量 ,相当于waitSet(条件不满足等待的地方),支持多个WaitSet里面等

相当于一个房子有不同的休息室,对其进行细分.

相同点 : 都是支持可重入的.同一个线程可以对同一个对象反复加锁.

ReentrantLock语法

//创建一个ReentrantLock对象
private static ReentrantLock lock =new ReentrantLock();
public static void main(String[] args) {
    //调用lock方法加锁
    lock.lock();
    try{
        //临界区的代码
    }finally {
        //必须对其进行解锁,所以放在finally块
        lock.unlock();
    }
}

ReentrantLock可重入

Synchronized和ReentrantLock都是可重入锁.

可重入锁也就是同一个线程首次获得了这把锁,也就是称为这把锁的拥有者,因此有权利再次获取这把锁,也就是同一个线程可以对同一个对象反复加锁

相反之,就有不可重入锁.

不可重入锁就是当第二次获取锁的时候,就会被锁挡住.

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.ReentrantLock;

@Slf4j(topic = "c.ReentrantFeatures")
public class ReentrantFeatures {
    /**
     * ReentrantLock可重入特性演示
     * @param args
     */
    private static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        lock.lock();
        try {
            log.debug("enter main");
           m1();
        }finally {
            lock.unlock();
        }
    }
    public static void m1() {
        lock.lock();
        try {
            log.debug("enter m1");
            m2();
        }finally {
            lock.unlock();
        }
    }
    public static void m2() {
        lock.lock();
        try {
            log.debug("enter m2");
        }finally {
            lock.unlock();
        }
    }
}

如果是不可重入锁,第二次获取锁的时候就会被挡住

ReentrantLock可打断

ReentrantLock可打断支持可打断的特性,通过调用ReentrantLock对象的lockinterruptibly()方法,其他线程就可以使用interrupt()方法对其打断可以防止无限制的等待下去,避免死锁.

在有竞争的情况下,如果该lockinterruptibly()方法没有被其他线程打断,那么和lock方法一样会死等线程释放锁.知道获取到锁

@Slf4j(topic = "c.LockInterruptiblyFeatures")
public class LockInterruptiblyFeatures {
    /**
     * 演示ReentrantLock可打断的特性
     */
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            try {
                log.debug("尝试获取锁");
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.debug("没有获取到锁,被打断,返回");
                return;
            }
            try {
                log.debug("获取到锁");
            }finally {
                lock.unlock();
            }
        });
        log.debug("获取到锁");
        lock.lock();
        t1.start();
        try{
            log.debug("1s后打断t1");
            //主线程1s后打断t1线程
            Thread.sleep(1000);
            t1.interrupt();
        }finally {
            lock.unlock();
        }
    }
}

通过调用ReentrantLock对象的lockinterruptibly()方法,其他线程就可以使用interrupt()方法对其打断可以防止无限制的等待下去,避免死锁.

ReentrantLock锁超时

可打断的这个特点是被动地避免死等,由其他线程调用interrupt方法让其不要继续死等.

锁超时是主动地避免死等,在获取锁的过程中,如果其他线程持有者锁一直没有释放,尝试获取锁的线程也不会死等,会设置一个超时时间,如果超过了这个超时时间,如果其他线程仍然没有释放锁,那么当前线程获取锁失败,避免无限制的等待下去,也就避免了死锁.

通过tryLock()方法可以实现锁超时

tryLock()方法不带参数 :-->没有设置超时时间

  • 获取锁成功返回true,获取锁失败返回false

tryLock()方法带参数 :-->表示可以设置超时时间

  • 获取锁成功返回true,如果超过超时的时间还没有获取到锁,就获取锁失败,返回false
@Slf4j(topic = "c.TryLock")
public class TryLock {
    /**
     * 演示ReentrantLock锁超时的情况
     */
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            try {
                if(!lock.tryLock(1, TimeUnit.SECONDS)) {
                    log.debug("没有获取到锁,立即返回");
                    return;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                //tryLock也可以被打断,没有获取到锁
                log.debug("被打断,没有获取到锁,立即返回");
                return;
            }
            log.debug("获取到锁");
            try {
                //执行临界区代码
                log.debug("执行临界区代码");
            }finally {
                //解锁
                lock.unlock();
            }
        },"t1");
        log.debug("获取锁");
        lock.lock();
        t1.start();
        try{
            Thread.sleep(2000);
        }finally {
            log.debug("释放锁");
            lock.unlock();
        }
    }
}

没有超过超时时间获取到锁

超过超时时间没有获取到锁

ReentrantLock解决哲学家就餐问题

package com.example.demo.Controller.DiningPhilosophersProblem;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Philosophers")
public class Philosophers extends Thread{
    private Chopstick left;//左手筷子
    private Chopstick right;//右手筷子

    public Philosophers(String name, Chopstick left, Chopstick right) {
        super(name);
        this.left = left;
        this.right = right;
    }

    @Override
    public void run() {
        while(true) {
            if(left.tryLock()) {//尝试获取左手筷子
                try{
                    if(right.tryLock()) {//尝试获取到右手筷子
                        try{
                            //既获得了左手筷子,又获得了右手筷子.就可以吃饭了
                            eat();
                        }finally {
                            right.unlock();
                        }
                    }
                    //如果哲学家获取到了左手筷子,但是没有获取到右手筷子
                    //那么哲学家同时也会释放左手筷子
                }finally {
                    left.unlock();
                }
            }
        }
    }

    private void eat() {
        log.debug("eating...");
        try {
            Thread.sleep(200);//思考1s钟
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

分析一下,为啥这样能解决死锁,对于synchronized,也就是没有获取到,就一直死等

而对于ReentrantLock来说,如果没有获取到锁就不等了,防止无限制等待.如果获取到了左手筷子,但没有获取到右手筷子,那么他也会同时释放左手筷子,让其他哲学家先吃

ReentrantLock公平锁

所谓公平锁,也就是先到先得. 如果多个线程获取锁的时候,就会去等待队列EntryList中去排队等待,如果是公平锁也就是排在前面的线程先获取到锁,排在后面的后获取到锁.

而非公平锁,并不是先到先得,而是谁竞争到了谁就先获取到锁.

ReentrantLock条件变量

对于条件变量我们可以理解为 synchronized的WaitSet,也就是条件不满足的时候就调用wait方法去WaitSet中等待,这个WaitSet就相当于条件变量.

对于ReentrantLock来说 ReentrantLock支持多个条件变量,将其条件更加细分

就相当于 :

  • synchronized是那些不满足线程都在一间休息室(同一个WaitSet)中等待.
  • 对于ReentrantLock来说支持多间休息室,有专门等外卖的休息室,有专门等待烟的休息室,唤醒也是按照休息室来唤醒的.

使用要点:

  • await 前需要获得锁
  • await 执行后,会释放锁,进入 conditionObject 等待
  • await 的线程被唤醒(或打断、或超时)取重新竞争 lock 锁
  • 竞争 lock 锁成功后,从 await 后继续执行
@Slf4j(topic = "c.TestDemo1")
public class TestDemo1 {
    static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) throws InterruptedException {
        //要想进入条件变量(消息室) 必须获取到锁
        lock.lock();
        //一个锁对象可以由多个条件变量(多个休息室) 将其细分
        Condition condition1 = lock.newCondition();
        Condition condition2 = lock.newCondition();
        try{
            //去休息室1 里面等待
            condition1.await();
            
        }finally {
            lock.unlock();
        }
        //唤醒条件变量1(休息室1)的线程
        condition1.signal();
        //唤醒条件变量2(休息室2)所有的线程
        condition1.signalAll();
    }
}

Condition代码练习

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j(topic = "c.TestWaitNotify3")
public class TestCondition {
    private static boolean isCigarette = false;
    private static boolean isTakeOut = false;
    //每一个人不用去同一间休息室等了,而是去不同的休息室等待,这样就避免了通一间休息室全部唤醒的问题->虚假唤醒
    private static ReentrantLock ROOM = new ReentrantLock();//一个大房间有多间休息室
    private  static Condition cigaretteWaitSet = ROOM.newCondition();
    private  static Condition takeOutWaitSet = ROOM.newCondition();

    public static void main(String[] args) throws InterruptedException {
        //小南等烟
        new Thread(()->{
            ROOM.lock();
            try{
                log.debug("是否有香烟 : " +isCigarette);
                if(!isCigarette){
                    log.debug("没有香烟,干不了活,等一会");
                    try {
                        cigaretteWaitSet.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("小南开始干活");
            }finally {
                ROOM.unlock();
            }
        },"小南").start();

        //小女等外卖
        new Thread(()->{
            ROOM.lock();
            try{
                log.debug("是否有外卖 : " +isTakeOut);
                if(!isTakeOut){
                    log.debug("没有外卖,干不了活,等一会");
                    try {
                        takeOutWaitSet.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("小女干活");
            }finally {
                ROOM.unlock();
            }
        },"小女").start();

        Thread.sleep(1000);
        new Thread(()->{
            ROOM.lock();
            try {
                isTakeOut = true;
                log.debug("外卖到了");
                takeOutWaitSet.signal();
            }finally {
                ROOM.unlock();
            }
        },"送外卖的").start();
        Thread.sleep(1000);
        new Thread(()->{
            ROOM.lock();
            try {
                isCigarette = true;
                log.debug("烟到了");
                cigaretteWaitSet.signal();
            }finally {
                ROOM.unlock();
            }
        },"送烟的").start();
    }
}

对之前的代码做出改进 使用ReentrantLock,这样两个人就不用去同一间休息室等待了,可以去同一个房间的不同休息室等待,这样当唤醒时候,不会把所有人都唤醒.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值