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(); } }
我们看看,前面问题的根源在于,我们线程 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,其他的阻塞队列也是可以的。
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(); } }