目录
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,这样两个人就不用去同一间休息室等待了,可以去同一个房间的不同休息室等待,这样当唤醒时候,不会把所有人都唤醒.