一个线程协同问题,解法繁多,趁还没被玩坏赶快处理掉他
前言
不知道大家有没有感觉,小学初中读的少量书,看的少量文章,到现在都印象深刻,反倒是高中学的知识,高考后就慢慢消散,直到遗忘。
我想说的是,记得初中学过鲁迅的《藤野先生》,里面有一段话,大意是:久了不联络,有时候想联络,却又无从下笔,到最后就更是不了了之了。
我找了下原文:
将走的前几天,他叫我到他家里去,交给我一张照相,后面写着两个字道:“惜别”,还说希望将我的也送他。但我这时适值没有照相了;他便叮嘱我将来照了寄给他,并且时时通信告诉他此后的状况。
我离开仙台之后,就多年没有照过相,又由于状况也无聊,说起来无非使他失望,便连信也怕敢写了。经过的年月一多,话更无从说起,所以尽管有时想写信,却又难以下笔,这样的一直到现在,竟没有寄过一封信和一张照片。从他那一面看起来,是一去之后,杳无消息了。
其实写文章也是这样的,久了不写更不想写,但是心里又时时记着这么个事情,玩也不是很自在;今天先随意写一下,找下状态吧,由于现在文章可能在博客和公众号发,比方博客,一般来说会随便点,但是公众号的话,一般大家质量要求会高少量,结果就是,为了追求高质量,而非要找到少量很厉害的技术点,或者者自己研究透了才动笔,这样会导致少量想法难产,由于可能觉得很简单,不值得发到公众号,实际上,很多时候都是浮于表面地觉得很简单,一旦深挖,立马就废。
扯这么多,也是给我自己,或者者其余刚开始写技术公众号的同学,也不用觉得非要写的多么多么好才发出来,原本大家都是一步一步来的,各种大佬也不是一下就变成大佬的,把自己的学习过程和成长过程发出来,大家也就知道:哦,大佬原来也这么菜啊,哈哈。
比方最近看到少量算法大佬,一开始也是10道算法题,一律都要看答案的好么。。
扯了不少,言归正传吧,最近在网上看到一个面试题目,感觉挺有意思的,大意如下:

ok,大家看到这个题,可以先了解下,这里启动了两个线程,a和b,但是尽管说a在b之前start,不肯定即可以保证线程a的逻辑,可以先于线程b执行,所以,这里的意思是,线程a和b,执行顺序互不干扰,我们不应该假定其中一个线程可以先于另外一个执行。
另外,既然是面试题,那常规做法自然是不用上了,比方让b先sleep几秒钟之类的,假如真这么答,那可能面试就结束了吧。
ok,我们下面开始分析解法。
可见性保证
程序里定义了一个全局变量,var = 1;线程a会修改这个变量为2,线程b则在变量为2时,执行自己的业务逻辑。
那么,这里首先,我们要做的是,先讲var使用volatile修饰,保证多线程操作时的可见性。
public static volatile int var = 1;解法分析
经过前面的可见性保证的分析,我们知道,要想达到目的,其实就是要保证:
a中的对var+1的操作,需要先于b执行。
但是,现在的问题是,两个线程同时启动,不知道谁先谁后,怎样保证a先执行,b后执行呢?
让线程b先不执行,大概有两种思路,一种是阻塞该线程,一种是不阻塞该线程,阻塞的话,我们可以想想,怎样阻塞一个线程。
大概有:
- synchronized,取不到锁时,阻塞
- java.util.concurrent.locks.ReentrantLock#lock,取不到锁时,阻塞
- object.wait,取到synchronized了,但是由于少量条件不满足,执行不下去,调用wait,将释放锁,并进入等待队列,线程暂停运行
- java.util.concurrent.locks.Condition.await,和object.wait相似,只不过object.wait在jvm层面,使用c++实现,Condition.await在jdk层面使用java语言实现
- threadA.join(),等待对应的线程threadA执行完成后,本线程再继续运行;threadA没结束,则当前线程阻塞;
- CountDownLatch#await,在对应的state不为0时,阻塞
- Semaphore#acquire(),在state为0时(即剩余令牌为0时),阻塞
- 其余阻塞队列、FutureTask等等
假如不让线程进入阻塞,则一般可以让线程进入一个while循环,循环的退出条件,可以由线程a来修改,线程a修改后,线程b跳出循环。
比方:
volatile boolean stop = false;while (!stop){ ...}上面也说了这么多了,我们实际上手写一写吧。
错误会法1–基于wait
下面的思路是基于wait、notify;线程b直接wait,线程a在修改了变量后,进行notify。
public class Global1 { public static volatile int var = 1; public static final Object monitor = new Object(); public static void main(String[] args) { Thread a = new Thread(() -> { // 1 Global1.var++; // 2 synchronized (monitor) { monitor.notify(); } }); Thread b = new Thread(() -> { // 3 synchronized (monitor) { try { monitor.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 4 if (Global1.var == 2) { //do something; System.out.println(Thread.currentThread().getName() + " good job"); } }); a.start(); b.start(); }}大家觉得这个代码能行吗?实际是不行的。由于实际的顺序可能是:
线程a--1线程a--2线程b--1线程b--2在线程a-2时,线程a去notify,但是此时线程b还没开始wait,所以此时的notify是没有任何效果的:没人在等,notify个锤子。
怎样修改,本方案才行得通呢?
那就是,修改线程a的代码,不要急着notify,先等等。
Thread a = new Thread(() -> { Global1.var++; try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (monitor) { monitor.notify(); } });但是这样的话,显著不合适,有作弊嫌疑,也不优雅。
错误会法2–基于condition的signal
import java.util.concurrent.TimeUnit;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.ReentrantLock;public class Global1 { public static volatile int var = 1; public static final ReentrantLock reentrantLock = new ReentrantLock(); public static final Condition condition = reentrantLock.newCondition(); public static void main(String[] args) { Thread a = new Thread(() -> { Global1.var++; final ReentrantLock lock = reentrantLock; lock.lock(); try { condition.signal(); } finally { lock.unlock(); } }); Thread b = new Thread(() -> { final ReentrantLock lock = reentrantLock; lock.lock(); try { condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } if (Global1.var == 2) { //do something; System.out.println(Thread.currentThread().getName() + " good job"); } }); a.start(); b.start(); }}这个方案使用了Condition对象来实现object的notify、wait效果。当然,这个也有同样的问题。
正确解法1–基于错误会法2进行改进
我们看看,前面问题的根源在于,我们线程a,在去通知线程b的时候,有可能线程b还没开始wait,所以此时通知失效。
那么,我们是不是可以先等等,等线程b开始wait了,再去通知呢?
Thread a = new Thread(() -> { Global1.var++; final ReentrantLock lock = reentrantLock; lock.lock(); try { // 1 while (!reentrantLock.hasWaiters(condition)) { Thread.yield(); } condition.signal(); } finally { lock.unlock(); } });1处代码,就是这个思想,在signal之前,判断当前condition上能否有waiter线程,假如没有,就死循环;假如有,才去执行signal。
这个方法实测是可行的。
正确解法2
对正确解法1,换一个api,就变成了正确解法2.
Thread a = new Thread(() -> { Global1.var++; final ReentrantLock lock = reentrantLock; lock.lock(); try { // 1 while (reentrantLock.getWaitQueueLength(condition) == 0) { Thread.yield(); } condition.signal(); } finally { lock.unlock(); }});1这里,获取condition上等待队列的长度,假如为0,说明没有等待者,则死循环。
正确解法3–基于Semaphore
刚开始,我们初始化一个信号量,state为0. 线程b去获取信号量的时候,就会阻塞。
而后我们线程a再去释放一个信号量,此时线程b即可以继续执行。
public class Global1 { public static volatile int var = 1; public static final Semaphore semaphore = new Semaphore(0); public static void main(String[] args) { Thread a = new Thread(() -> { Global1.var++; semaphore.release(); }); a.setName("thread a"); Thread b = new Thread(() -> { try { semaphore.acquire(); } catch (InterruptedException e) { e.printStackTrace(); } if (Global1.var == 2) { //do something; System.out.println(Thread.currentThread().getName() + " good job"); } }); b.setName("thread b"); a.start(); b.start(); }}正确解法4–基于CountDownLatch
public class Global1 { public static volatile int var = 1; public static final CountDownLatch countDownLatch = new CountDownLatch(1); public static void main(String[] args) { Thread a = new Thread(() -> { Global1.var++; countDownLatch.countDown(); }); a.setName("thread a"); Thread b = new Thread(() -> { try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } if (Global1.var == 2) { //do something; System.out.println(Thread.currentThread().getName() + " good job"); } }); b.setName("thread b"); a.start(); b.start(); }}正确解法5–基于BlockingQueue
这里使用了ArrayBlockingQueue,其余的阻塞队列也是可以的。
import countdown.CountdownTest;public class Global1 { public static volatile int var = 1; public static final ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<Object>(1); public static void main(String[] args) { Thread a = new Thread(() -> { Global1.var++; arrayBlockingQueue.offer(new Object()); }); a.setName("thread a"); Thread b = new Thread(() -> { try { arrayBlockingQueue.take(); } catch (InterruptedException e) { e.printStackTrace(); } if (Global1.var == 2) { //do something; System.out.println(Thread.currentThread().getName() + " good job"); } }); b.setName("thread b"); a.start(); b.start(); }}正确解法6–基于FutureTask
我们也可以让线程b等待一个task的执行结果;而线程a在执行完修改var为2后,执行该任务,任务执行完成后,线程b就会被通知继续执行。
public class Global1 { public static volatile int var = 1; public static final FutureTask futureTask = new FutureTask<Object>(new Callable<Object>() { @Override public Object call() throws Exception { System.out.println("callable task "); return null; } }); public static void main(String[] args) { Thread a = new Thread(() -> { Global1.var++; futureTask.run(); }); a.setName("thread a"); Thread b = new Thread(() -> { try { futureTask.get(); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } if (Global1.var == 2) { //do something; System.out.println(Thread.currentThread().getName() + " good job"); } }); b.setName("thread b"); a.start(); b.start(); }}正确解法7–基于join
这个可能是最简洁直观的,哈哈。也是群里同学们提供的解法,真的有才!
public class Global1 { public static volatile int var = 1; public static void main(String[] args) { Thread a = new Thread(() -> { Global1.var++; }); a.setName("thread a"); Thread b = new Thread(() -> { try { a.join(); } catch (InterruptedException e) { e.printStackTrace(); } if (Global1.var == 2) { //do something; System.out.println(Thread.currentThread().getName() + " good job"); } }); b.setName("thread b"); a.start(); b.start(); }}正确解法8–基于CompletableFuture
这个和第6种相似。都是基于future。
public class Global1 { public static volatile int var = 1; public static final CompletableFuture<Object> completableFuture = new CompletableFuture<Object>(); public static void main(String[] args) { Thread a = new Thread(() -> { Global1.var++; completableFuture.complete(new Object()); }); a.setName("thread a"); Thread b = new Thread(() -> { try { completableFuture.get(); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } if (Global1.var == 2) { //do something; System.out.println(Thread.currentThread().getName() + " good job"); } }); b.setName("thread b"); a.start(); b.start(); }}非阻塞–正确解法9–忙等待
这种代码量也少,只需线程b在变量为1时,死循环就行了。
public class Global1 { public static volatile int var = 1; public static void main(String[] args) { Thread a = new Thread(() -> { Global1.var++; }); a.setName("thread a"); Thread b = new Thread(() -> { while (var == 1) { Thread.yield(); } if (Global1.var == 2) { //do something; System.out.println(Thread.currentThread().getName() + " good job"); } }); b.setName("thread b"); a.start(); b.start(); }}非阻塞–正确解法10–忙等待
忙等待的方案很多,反正就是某个条件不满足时,不阻塞自己,阻塞了会释放cpu,我们就是不希望释放cpu的。
比方像下面这样也可以。
public class Global1 { public static volatile int var = 1; public static final AtomicInteger atomicInteger = new AtomicInteger(1); public static void main(String[] args) { Thread a = new Thread(() -> { Global1.var++; atomicInteger.set(2); }); a.setName("thread a"); Thread b = new Thread(() -> { while (true) { boolean success = atomicInteger.compareAndSet(2, 1); if (success) { break; } else { Thread.yield(); } } if (Global1.var == 2) { //do something; System.out.println(Thread.currentThread().getName() + " good job"); } }); b.setName("thread b"); a.start(); b.start(); }}小结
暂时想了这么写,方案还是比较多的,大家可以开动脑筋,头脑风暴吧!
最后
大家看完有什么不懂的可以在下方留言探讨.
谢谢你的观看。
觉得文章对你有帮助的话记得关注我点个赞支持一下!
作者:逐日
原文链接:https://www.cnblogs.com/grey-wolf/p/13737109.html
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 一个线程协同问题,解法繁多,趁还没被玩坏赶快处理掉他