Java多线程实战--奇偶数打印

本文开始,将会以各种多线程相关的题目为基础,逐一进行分析,先开始从一个简单的例子开始。

问题剖析

题目:编号分别为 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 已经可以保证变量的可见性了。

#
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×