本文开始,将会以各种多线程相关的题目为基础,逐一进行分析,先开始从一个简单的例子开始。
问题剖析
题目:编号分别为 0 和 1 的线程,按照 ”偶数、奇数、偶数、奇数“ 的顺序输出结果,请严格确保输出顺序、线程能够正确退出。
方案如下,该方案有瑕疵,请谨慎阅读
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class Solution { static class SolutionTask implements Runnable{ static int value = 0; @Override public void run() { while (value <= 100){ synchronized (SolutionTask.class){ System.out.println(Thread.currentThread().getName() + ":" + value++); SolutionTask.class.notify(); try { SolutionTask.class.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } public static void main(String[] args) { new Thread(new SolutionTask(), "偶数").start(); new Thread(new SolutionTask(), "奇数").start(); } }
|
方案分析
该方案其实存在以下的一些问题
- 线程最后不会自动退出。最后一个线程执行 SolutionTask.class.notify() 后再执行 SolutionTask.class.wait(),因而线程一直在等待。
- 输出顺序不一定是 ”偶数、奇数“,有可能是 ”奇数、偶数“ 的情况,涉及到线程切换的问题。
解决方案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| public class Solution { static class SolutionTask implements Runnable{ static int value = 0; int threadNo;
SolutionTask(int threadNo) { this.threadNo = threadNo; }
@Override public void run() { while (value <= 100) { synchronized (SolutionTask.class){ while (value % 2 != threadNo) { if (value > 100) { break; }
try { SolutionTask.class.wait(); } catch (InterruptedException e) { e.printStackTrace(); } }
if (value > 100) { break; }
System.out.println(Thread.currentThread().getName() + ":" + value++); SolutionTask.class.notify(); } } } } public static void main(String[] args) { new Thread(new SolutionTask(0), "偶数").start(); new Thread(new SolutionTask(1), "奇数").start(); } }
|
处理类似问题的时候,也一定要对边界情况进行思考,特别是开始、结束部分,避免出现不符合题意的输出结果,类似于数据中的归纳法证明。
还有另外一点需要注意,线程共享变量 value 没必要使用 volatile 变量,原因是当线程被唤醒后再次通过 sychronized 获取互斥锁,sychronized 已经可以保证变量的可见性了。