保护性暂停模式(Guarded Suspension Design Pattern)
- 某个结果需要在多线程之间传递,则可以让这些线程关联到一个对象 GuardedObject
- 但是如果这个结果需要不断的从一个线程到另一个线程那么可以使用消息队列(见生产者/消费
者) - 我们前面前面说的join、future采用的就是这个模式
场景介绍
简单实现
-
package BingFaBianCheng.bingFaBianCheng9.guarded; import lombok.extern.slf4j.Slf4j; @Slf4j(topic = "enjoy") public class GuardedObject { private Object response; Object lock = new Object(); /** * 加锁获取 response的值 如果response 没有值则等待 * @return */ public Object getResponse(){ synchronized (lock) { log.debug("主线程 获取 response 如果为null则wait"); while (response == null) { try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } return response; } /** * t1 给response设置值 * @param response */ public void setResponse(Object response) { synchronized (lock) { this.response = response; //设置完成之后唤醒主线程 lock.notifyAll(); } } }
测试保护性暂停模式
-
package BingFaBianCheng.bingFaBianCheng9.guarded; import lombok.extern.slf4j.Slf4j; @Slf4j(topic = "enjoy") public class Test { public static void main(String[] args) { GuardedObject guardedObject = new GuardedObject(); new Thread(() -> { String result = Operate.dbOprate(); log.debug("t1 set完毕..."); guardedObject.setResponse(result); },"t1").start(); log.debug("主线程等待(wait)t1 set"); //有没有实现超时? Object response = guardedObject.getResponse(); log.debug("response: [{}]",response); } }
TestJoinTimeOut–可以超时
-
package BingFaBianCheng.bingFaBianCheng9.guarded; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.TimeUnit; @Slf4j(topic = "enjoy") public class TestJoinTimeOut { public static void main(String[] args) throws InterruptedException { Object o = new Object(); Thread t1 = new Thread(() -> { try { log.debug("t1"); TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } }, "t1"); t1.start(); //main线程 等待 t1执行完 t1.join(2000); o.wait(); log.debug("man end"); } }
-
join设置了超时,t1.join(2000),如果t1执行两秒还没有执行完,也不会等了,继续往下面执行
GuardedObject优化–也可以超时
-
package BingFaBianCheng.bingFaBianCheng9.guarded; import lombok.extern.slf4j.Slf4j; @Slf4j(topic = "enjoy") public class GuardedObject1 { private Object response; Object lock = new Object(); /** * 加锁获取 response的值 如果response 没有值则等待 * * * millis ==10 5 =10-5 1 =5-1=4 * @return */ public Object getResponse(long millis){ synchronized (lock) { log.debug("主线程 获取 response 如果为null则wait"); while (response == null) { try { lock.wait(millis); //如果我被别人叫醒了? 我要去再次查看结果 如果没有结果我则继续等待 //确定我等了多少时间? 和 millis 对比如果大于了millis 则则可以直接结束 如果 /** * 1、passedTime 经历了多少时间 * 2、开始时间 * 3、结束时间 */ break; } catch (InterruptedException e) { e.printStackTrace(); } } } return response; } /** * t1 给response设置值 * @param response */ public void setResponse(Object response) { synchronized (lock) { this.response = response; //设置完成之后唤醒主线程 lock.notifyAll(); } } }
-
测试优化后的GuardedObject
-
package BingFaBianCheng.bingFaBianCheng9.guarded; import lombok.extern.slf4j.Slf4j; @Slf4j(topic = "enjoy") public class Test { public static void main(String[] args) { GuardedObject1 guardedObject = new GuardedObject1(); new Thread(() -> { String result = Operate.dbOprate(); log.debug("t1 set完毕..."); guardedObject.setResponse(result); },"t1").start(); log.debug("主线程等待(wait)t1 set"); //有没有实现超时? Object response = guardedObject.getResponse(2000); log.debug("response: [{}]",response); } }
-
这里的等是有问题的,如果等的时间长,无法防止自己被别人打断叫醒,结果就没有等足期望等待的时间
-
正确的响应应该是,如果被叫醒了,我应该去看是否有结果,如果没有结果,应该继续等待,所以应该计算剩余时间,如果计算出等待的时间大于等于需要等的时间,则就不需要等了
-
package BingFaBianCheng.bingFaBianCheng9.guarded; import lombok.extern.slf4j.Slf4j; @Slf4j(topic = "enjoy") public class GuardedObjectTimeOut { private Object response; Object lock = new Object(); /** * 加锁获取 response的值 如果response 没有值则等待 * @return */ public Object getResponse(long millis){ synchronized (lock) { //开始时间 long begin = System.currentTimeMillis(); //经历了多少时间 开始肯定是0 long timePassed = 0; //没有次数 我也不知道是不是第一次 while (response == null) { //睡多少时间 long waitTime = millis-timePassed; log.debug("主线程 判断如果没有结果则wait{}毫秒",waitTime); if (waitTime <= 0) { log.debug("超时了 直接结束while 不等了"); break; } try { lock.wait(waitTime);//6s之后被notify 了 1、去经历的时间记录一下 2、看有没有结果 没有则继续等 } catch (InterruptedException e) { e.printStackTrace(); } //如果被别人提前唤醒 先不结束 先計算一下经历时间 6 10 timePassed = System.currentTimeMillis() - begin; log.debug("经历了: {}", timePassed); } } return response; } /** * t1 给response设置值 * @param response */ public void setResponse(Object response) { synchronized (lock) { this.response = response; //设置完成之后唤醒主线程 lock.notifyAll(); } } }
join源码
-
public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (millis == 0) { while (isAlive()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } // 这个wait不是synchronized里面的wait wait(delay); now = System.currentTimeMillis() - base; } } }
死锁
-
package com.shadow.lock; import jdk.nashorn.internal.ir.Block; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.TimeUnit; @Slf4j(topic = "enjoy") public class LockTest { //定义两把锁 static Object x = new Object(); static Object y = new Object(); public static void main(String[] args) { //线程1启动 new Thread(()->{ //获取x的锁 synchronized (x){ log.debug("locked x"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (y){ log.debug("locked x"); log.debug("t1---------"); } } },"t1").start(); new Thread(()->{ synchronized (y){ log.debug("locked y"); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (x){ log.debug("locked x"); log.debug("t2---------"); } } },"t2").start(); } }
-
如果线程需要获取多把锁那么就很可能会发现死锁
活锁—没有锁,只是针对线程而言的
-
package com.shadow.lock; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.TimeUnit; @Slf4j(topic = "enjoy") public class LockTest1 { static volatile int count = 10; static final Object lock = new Object(); public static void main(String[] args) { //t1线程对count一直做减法 直到减为0才结束 new Thread(() -> { while (count > 0) { try { TimeUnit.NANOSECONDS.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } count--; log.debug("count: {}", count); } }, "t1").start(); //t2线程对count一直做加法 直到加为20才结束 new Thread(() -> { while (count < 20) { try { TimeUnit.NANOSECONDS.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } count++; log.debug("count: {}", count); } }, "t2").start(); } }
-
不可避免 但是我可以错开他们的执行时间
-
线程执行sleep的时候,就会让出时间片
-
此处的20还比较小,假设t2得到了时间片,一下就把count++执行完了,所以跳出了循环,是存在这种可能的,尤其20又比较小
总结
- 过程
- 启动一个线程修改共享变量
- 主线程尝试做事,但是共享变量条件不满足,则wait
- 当启动的线程成功修改共享变量后,主线程则继续往下面执行
- 启动的线程可以使用join方法来阻塞主线程,让启动线程充分执行完任务
- 超时的实现,
- wait()和join()的实现作用相似,但是wait()方法有一个缺陷,可以被唤醒再次去获取锁,
- wait()和join()方法加时间相比,则需要确保wait一定等足时间才跳出线程,此时需要计算等待了多长时间
- Thread中的wait方法是让主线程去wait()
- 我们用的wait方法,一般是跟synchrnozed结合使用的,不然会报错
- 而join方法里面调用的wait是不需要的,和synchronized里面调用wait的方式是不一样的
- 死锁
- 一个线程获取多把锁就可能会死锁
- t1获取锁1,睡眠1毫秒,再去获取锁2,t2获取锁2,睡眠2毫秒,再去获取锁1
- 活锁:两个线程的锁释放条件恰好相反,且两者也一直反向操作,可能一直执行不结束,解决方法:错开两者的睡眠时间
ReentranLock的基本使用
发展
- jdk1.6之前synchronized是内置的锁,使用mutex实现,那时没有偏向锁的概念
- Doug Lea 创造引入了juc,其中lock就是用来替换synchronized的,但是使用起来比synchronized更复杂,而且使用ReentrantLock最好用try-catch-finally来操作
特点
-
可打断,可重入(synchronized可重入,不可打断)
-
可以设置超时时间
-
可以设置为公平锁
-
支持多个条件变量
-
支持读写锁(读写互斥,读读不互斥)
-
lock和synchronized只有可重入是相同的,其余的功能synchronized都不具备
lock的重入
-
package BingFaBianCheng.bingFaBianCheng9.lock; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.locks.ReentrantLock; @Slf4j(topic = "enjoy") public class LockTest3 { //首先定义一把锁 static ReentrantLock lock = new ReentrantLock(); public static void main(String[] args) { lock1(); } public static void lock1() { lock.lock(); try { log.debug("执行lock1"); //重入 lock2(); } finally { lock.unlock(); } } public static void lock2() { lock.lock(); try { log.debug("执行lock2"); } finally { lock.unlock(); } } }
-
加try—finally去unlock锁
-
可重入,lock1方法里面加了锁,又调用lock2方法,又加了同一把锁
可打断
-
package BingFaBianCheng.bingFaBianCheng9.lock; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; @Slf4j(topic = "enjoy") public class LockTest4 { public static void main(String[] args) throws InterruptedException { ReentrantLock lock = new ReentrantLock(); //t2首先获取锁 然后阻塞5s new Thread(()->{ try { lock.lock();//获取锁 log.debug("获取锁----"); TimeUnit.SECONDS.sleep(5); log.debug("t2 5s 之后继续执行"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } },"t2").start(); //主要是为了让t2先拿到锁 TimeUnit.SECONDS.sleep(1); //t1加锁失败因为被t2持有 Thread t1 = new Thread(() -> { try { lock.lockInterruptibly(); log.debug("t1 获取了锁--执行代码"); } catch (Exception e) { e.printStackTrace(); log.debug("被打断了没有获取锁"); return; } finally { lock.unlock(); } }, "t1"); t1.start(); //由于t1 可以被打断 故而1s之后打断t1 不在等待t2释放锁了 try { TimeUnit.SECONDS.sleep(2); log.debug("主线程------2s后打断t1"); t1.interrupt(); } catch (InterruptedException e) { e.printStackTrace(); } } }
-
主线程在中间睡眠1秒钟,目的是让t2比t1先执行
-
t1加锁使用的lock.lockInterruptibly(),这样获取的锁是可以被打断的,在当前代码中,t1是会加锁失败的,因为t2已经获取到锁,但是没有释放锁,此时会进入阻塞队列阻塞
-
然后主线程打断t1,不会再继续在阻塞队列里面等待
-
可打断
- lock.lockInterruptibly()方法可以打断
- t1.interrupt()方法打断线程t1,不让线程t1因为获取锁继续阻塞了
- lock.lock()方法则不可打断
-
打断之后是由异常捕获来做进一步处理
超时
-
package BingFaBianCheng.bingFaBianCheng9.lock; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; @Slf4j(topic = "enjoy") public class LockTest5 { public static void main(String[] args) throws InterruptedException { ReentrantLock lock = new ReentrantLock(); Thread t1 = new Thread(() -> { log.debug("t1启动---------"); // 拿到锁返回true,拿不到锁返回false if (!lock.tryLock()) {//进入if标识拿不到锁 log.debug("拿不到鎖,返回"); return; } try { log.debug("获得了锁"); } finally { lock.unlock(); } }, "t1"); //主线程先获取锁 lock.lock(); log.debug("主綫程获得了锁"); t1.start(); try { TimeUnit.SECONDS.sleep(3); } finally { lock.unlock(); } } }
-
tryLock方法如果不加时间,则表明拿不到锁立即失败返回
-
tryLock(2,TimeUnit.SECONDS),采用的aqs的代码,带有超时时间的获取锁
多个条件(多个waitset)
-
package BingFaBianCheng.bingFaBianCheng9.lock; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; @Slf4j(topic = "enjoy") public class LockTest6 { static final ReentrantLock lock = new ReentrantLock(); static boolean isPrettyGril = false; // 女人 static boolean isMoney = false;//工资 //没有女人的 waitSet static Condition waitpg = lock.newCondition(); // 没有钱的waitSet static Condition waitm = lock.newCondition(); public static void main(String[] args) throws InterruptedException { new Thread(() -> { try { lock.lock(); log.debug("有没有女人[{}]", isPrettyGril); while (!isPrettyGril) { log.debug("没有女人!等女人"); try { // 相当于创建了一个waitSet waitpg.await(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("男女搭配干活不累;写完了代码"); }finally { lock.unlock(); } }, "jack").start(); new Thread(() -> { try { lock.lock(); log.debug("有没有工资[{}]", isMoney); while (!isMoney) { log.debug("没有工资!等发工资"); try { // 创建第二个waitSet waitm.await(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("-----卧槽好多钱;写完了代码"); }finally { lock.unlock(); } }, "rose").start(); Thread.sleep(1000); new Thread(() -> { try { lock.lock(); isMoney = true; log.debug("钱来哦了"); isPrettyGril=true; log.debug("桥老师"); waitpg.signal(); waitm.signalAll(); }finally { lock.unlock(); } }, "boss").start(); } }
-
既需要修改while的终止条件,同时需要唤醒因为await阻塞的线程,这样才能使阻塞的线程正常执行
-
signalAll()不同于notifyAll(),是精确唤醒,不会叫醒其他线程
-
Condition c1 = lock.newCondition(),相当于创建了一个waitset(synchronized里面)
-
wait、notify与synchronized结合使用,await、signal与ReentrantLock、Condition结合使用
保证线程的顺序执行–不能使用join
wait、notify
-
package BingFaBianCheng.bingFaBianCheng9.lock; import lombok.extern.slf4j.Slf4j; @Slf4j(topic = "enjoy") public class LockTest7 { static final Object lock = new Object(); // 表示 t2 是否运行过 // 中间变量是用static类型 static boolean flag = false; public static void main(String[] args) { Thread t1 = new Thread(() -> { synchronized (lock) { // t2没运行,一直阻塞t1 while (!flag) { try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("a"); } }, "t1"); Thread t2 = new Thread(() -> { synchronized (lock) { log.debug("b"); flag = true; lock.notify(); } }, "t2"); t1.start(); t2.start(); } }
-
通过wait、notify保证t2比t1先执行
park、unpark
-
package BingFaBianCheng.bingFaBianCheng9.lock; import lombok.extern.slf4j.Slf4j; import org.slf4j.LoggerFactory; import java.util.concurrent.locks.LockSupport; import java.util.logging.Logger; @Slf4j(topic = "enjoy") public class LockTest8 { /** * t1 --a * t2 --b * t3----c * @param args */ public static void main(String[] args) { Thread t1 = new Thread(() -> { LockSupport.park(); log.debug("a"); }, "t1"); t1.start(); new Thread(() -> { log.debug("b"); LockSupport.unpark(t1); },"t2").start(); } }
-
通过park、unpark保证t2比t1先打印
循环连续打印线程a、b、c四次
-
比我自己写的方法更加巧妙精炼,代码也更少
-
package BingFaBianCheng.bingFaBianCheng9.lock; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.locks.LockSupport; @Slf4j(topic = "enjoy") public class LockTest9 { public static void main(String[] args) { waitNofity waitNofity = new waitNofity(); new Thread(() -> { try { waitNofity.print("a",1,2); } catch (InterruptedException e) { e.printStackTrace(); } },"t1").start(); new Thread(() -> { try { waitNofity.print("b",2,3); } catch (InterruptedException e) { e.printStackTrace(); } },"t2").start(); new Thread(() -> { try { waitNofity.print("c",3,1); } catch (InterruptedException e) { e.printStackTrace(); } },"t3").start(); } //et t2 t3 // static class waitNofity{ int flag=1; public void print(String content,int waitFlag,int nextFlag) throws InterruptedException { for (int i = 0; i <4 ; i++) { synchronized (this){ while (flag!= waitFlag){ this.wait(); } System.out.print(content); k++; flag= nextFlag; this.notifyAll(); } if(waitFlag==3){ System.out.print(" "); } } } } }
-
print方法打印,print(String conent,int waitFlag,int nextFlag)