ReentrantLock

一、简介

        相对于 synchronized 来说它具有可中断、可以设置超时时间、可以设置公平锁、支持多个条件变量等特点。

        并且和 synchronized 一样,都支持可重入。

        可中断的意思是:ReentrantLock 可以被其他线程中断,而synchronized 加上锁之后是不能中断的,即不能使用其他的线程或者语法给这锁给中断掉。

        可以设置超时时间的意思是:ReentrantLock 支持等待一段时间后就放弃锁的争抢,而 synchronized 获取锁时如果对方持有锁,那它就会到 entryList 里面等待去了。

        可以设置公平锁的意思是:ReentrantLock 支持先到先得,而 synchronized 不支持先到先得。

        可以支持多个条件变量的意思是:条件变量就相当于 synchronized 的 Monitor 中的 waitSet,即当条件不满足时线程需要到 waitSet 中等待,即条件不满足时线程呆的一个地方。ReentrantLock  支持多个条件变量的意思就是有好多个 waitSet,即不满足条件一的线程到 waitSet1 中等待,不满足条件二的到线程到 waitSet2 中等待。而 synchronized 只支持一个条件变量。

二、语法

         ReentrantLock 的语法如下:

// 获取锁
reentrantLock.lock();
try {
	// 临界区
} finally {
	// 释放锁
	reentrantLock.unlock();
}

三、特性演示

3.1 可重入

        可重入是指同一个线程如果首次获得了这把锁,那么它就是这把锁的拥有者,它就有权利再次获取这把锁,如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住。如下代码:

public class MyServlet {

    static Lock lock = new ReentrantLock();
    public static void main(String[] args) {
        lock.lock();
        try {
            System.out.println("execute main");
            m1();
        } finally {
            lock.unlock();
        }
    }
    public static void m1() {
        lock.lock();
        try {
            System.out.println("execute m1");
            m2();
        } finally {
            lock.unlock();
        }
    }
    public static void m2() {
        lock.lock();
        try {
            System.out.println("execute m2");
        } finally {
            lock.unlock();
        }
    }
}

3.2 可打断

        线程在等待锁释放的过程中,可以被其他的线程打断,这种可打断锁存在的意义就是防止线程无限制的等待下去,也是一种避免死锁的做法。测试代码如下:

public class MyServlet {

    static Lock lock = new ReentrantLock();
    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() ->{
            try{
                // 可打断需要调用 lock 的 lockInterruptibly() 方法
                // 如果没有竞争那么此方法会获取 lock 对象
                // 如果有竞争就进入阻塞队列,可以被其他线程调用 interrupt() 方法打断
                System.out.println("t1 线程尝试获取锁...");
                lock.lockInterruptibly();
            }catch (InterruptedException e){
                e.printStackTrace();
                System.out.println("t1 线程没有获取到锁,返回");
                return;
            }
            try{
                System.out.println("t1线程获取到锁");
            }finally {
                lock.unlock();
            }
        },"t1");
        lock.lock();
        System.out.println("主线程获得了锁");
        t1.start();
        try {
            Thread.sleep(1);
            t1.interrupt();
            System.out.println("执行打断");
        } finally {
            lock.unlock();
        }
    }
}

3.3 超时释放

        上面讲的可打断特性是被动的,是由其他线程调用 interrupt() 方法让线程不要死等下去了。而锁超时是以主动的方式让线程不再死等下去。

        第一种使用方式,只尝试一次,失败立刻返回,代码如下:

@Slf4j(topic = "c.test")
public class Main {
    static Lock lock = new ReentrantLock();
    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -> {
            log.debug("启动...");
            if (!lock.tryLock()) {
                log.debug("获取立刻失败,返回");
                return;
            }
            try {
                log.debug("获得了锁");
            } finally {
                lock.unlock();
            }
        }, "t1");
        lock.lock();
        log.debug("获得了锁");
        t1.start();
        try {
            Thread.sleep(2);
        } finally {
            lock.unlock();
        }
    }
}

         第二种方式,可以设置等待的时间,当超过等待的时间没有获取到锁时,也是失败返回。代码如下:

@Slf4j(topic = "c.test")
public class Main {
    static Lock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -> {
            log.debug("启动...");
            try {
                if (!lock.tryLock(1, TimeUnit.SECONDS)) {
                    log.debug("获取等待 1s 后失败,返回");
                    return;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                log.debug("获得了锁");
            } finally {
                lock.unlock();
            }
        }, "t1");
        lock.lock();
        log.debug("获得了锁");
        t1.start();
        try {
            Thread.sleep(2000);
        } finally {
            lock.unlock();
        }
    }
}

3.4 公平锁

        ReentrantLock 默认是不公平的,其实没有必要设置公平锁,因为设置成公平锁会降低并发度,设置成公平锁的语法如下:

 Lock lock = new ReentrantLock(true);

四、条件变量

        synchronized 中也有条件变量,就是我们以前说过的 Monitor 中的 waitSet,当条件不满足时进入到 waitSet 等待,它相当于一个休息室。而 ReentrantLock synchronized 强的地方在于,它可以支持多个条件变量,即拥有多个休息室。

        举例来说,那些不满足条件的线程在 synchronized 中只有一间吸烟的休息室。而在 ReentrantLock 中有吸烟的休息室还有洗澡的休息室。

        创建条件变量的语法如下,需要注意的是调用 await() 时需要先获取到锁,调用完 await() 方法后会释放锁,当前线程进入到休息室去等待,当处于休息室的线程被唤醒后会重新竞争 lock 锁。竞争成功之后,会从 await() 后继续执行。

@Slf4j(topic = "c.test")
public class Main {
    static Lock lock = new ReentrantLock();
    public static void main(String[] args) throws InterruptedException {

        // 创建一个香烟条件变量(休息室 waitSet)
        Condition cigarette_condition = lock.newCondition();
        // 创建一个洗澡条件变量(休息室 waitSet)
        Condition bathe_condition = lock.newCondition();

        lock.lock();
        // 进入到香烟休息室等候
        cigarette_condition.await();
        
        // 随机唤醒休息室里面的一条线程
        cigarette_condition.signal();
        // 唤醒休息室里面的所有线程
        cigarette_condition.signalAll();
    }
}

五、Lock 类常用方法

# 查询当前线程保持此锁定的个数,也就是调用 lock() 方法的次数。
int getHoldCount()

# 返回正等待获取此锁定的线程估计数。
int getQueueLength()

# 返回等待与此锁定相关的给定条件 Condition 的线程估计数。
int getWaitQueueLength(Condition condition)

# 查询指定的线程是否在等待获取此锁定。
boolean hasQueuedThread(Thread thread)

# 查询是否有线程正在等在与此锁有关的 condition 条件。
boolean hasWaiters(Condition condition)

# 判断是不是公平锁,默认情况下 ReentrantLock 是非公平锁。
boolean isFair()

# 查询当前线程是否保持此锁定。
boolean isHeldByCurrentThread()

# 查询此锁是是否由任意线程保持。
boolean isLocked()

# :如果当前线程未被中断,则获取锁定,如果已经被中断则出现异常。
void lockInterruptibly()

# 仅在调用时锁定未被另一个线程保持的情况下,才获取该锁定。
boolean tryLock()

# 如果锁定在给定等待时间内没有被另外一个线程保持,且当当前线程未被中断,则获取该锁定。
boolean tryLock(longtimeout,TimeUnit unit)

六、实现生产者和消费者

        接下来我们使用 Lock Condition 来实现生产者和消费者的模型。

// 线程通信的前提一定是保证线程安全,否则线程通信没有任何意义
// 锁是可以夸方法的,底下的 put 和 get 方法用的就是同一把锁(桌子对象),这个锁可以锁住底下的 5 个线程
// 每次只有一个线程可以抢到桌子对象,让一个线程访问 put 或者 get 方法,这样就控制了 5 个线程访问桌子对象是线程安全的
public class Desk {
    // 创建的五个线程都会抢同一个桌子对象
    private List<String> list = new ArrayList<>();
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    // 放一个包子的方法
    // 厨师1、厨师、厨师3都会来竞争的做包子
    public  void put() {
        try {
            String name = Thread.currentThread().getName();
            lock.lock();
            if(list.size() == 0){
                list.add(name+"做的肉包子");
                System.out.println(name+"做了一个肉包子");
                Thread.sleep(2000);
                // 唤醒别人,自己等待
                condition.signalAll();
            }else{
                // 有包子,不做了
                condition.signalAll();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    // 取一个包子的方法
    // 吃货1、吃货2都会来竞争的吃包子
    public synchronized  void get(){
        try {
            String name = Thread.currentThread().getName();
            lock.lock();
            if(list.size() ==1){
                // 有包子,吃了
                System.out.println(name+"吃了:"+list.get(0));
                list.clear();
                Thread.sleep(1000);
                // 唤醒别人,自己等待
                condition.signalAll();
            }else{
                // 没有包子,自己等待
                condition.signalAll();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}
public class ThreadTest {
    public static void main(String[] args) {
        Desk desk = new Desk();
        // 创建三个生产者线程(3个厨师)
        new Thread(() ->{
            while (true){
                desk.put();
            }
        },"厨师1").start();
        new Thread(() ->{
            while (true){
                desk.put();
            }
        },"厨师2").start();
        new Thread(() ->{
            while (true){
                desk.put();
            }
        },"厨师3").start();
        // 创建两个消费者线程(2个吃货)
        new Thread(() ->{
            while (true){
                desk.get();
            }
        },"吃货1").start();
        new Thread(() ->{
            while (true){
                desk.get();
            }
        },"吃货2").start();
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

快乐的小三菊

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

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

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

打赏作者

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

抵扣说明:

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

余额充值