假设在流水线工厂中,生产一个产品需要依次执行三个任务,如何设计这样的流水线完成一批产品的生产?
我们可以启动三个线程,分别执行这三个任务。
第一步:定义产品类(任务类Task),同时定义方法Task1~3,指定每个任务做什么。
public class Task {
public int num;
public void task1() {
num = 20;
}
public void task2() {
num *= 10;
}
public void task3() {
num *= num;
}
}
第二步:定义三个线程Thread1~3,调取数组中的任务对象并执行任务。
public class Thread1 extends Thread{
Task[] tasks;
public Thread1(Task[] tasks) {
this.tasks = tasks;
}
@Override
public void run() {
for (Task task : tasks) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
task.task1();
}
}
}
public class Thread2 extends Thread{
Task[] tasks;
public Thread2(Task[] tasks) {
this.tasks = tasks;
}
@Override
public void run() {
for (Task task : tasks) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
task.task2();
}
}
}
public class Thread3 extends Thread{
Task[] tasks;
public Thread3(Task[] tasks) {
this.tasks = tasks;
}
@Override
public void run() {
for (Task task : tasks) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
task.task3();
}
}
}
第三步:在主线程中,先后启动线程Thread1~3,测试一下。
public class Main {
public static void main(String[] args) throws InterruptedException {
Task[] tasks = new Task[20];
for (int i = 0; i < tasks.length; i++) {
tasks[i] = new Task();
}
Thread1 t1 = new Thread1(tasks);
Thread2 t2 = new Thread2(tasks);
Thread3 t3 = new Thread3(tasks);
t1.start();
t2.start();
t3.start();
// join: 等待线程运行结束
t1.join();
t2.join();
t3.join();
for (Task task : tasks) {
System.out.println(task.num);
}
}
}
测试结果(按照顺序依次执行三个任务的正确结果应为40000):
40000
0
4000
400
200
40000
0
0
20
20
40000
4000
0
0
20
400
0
200
40000
40000
问题:为什么出现了错误的结果?
原因:并不是哪个线程先启动,哪个线程就先执行。虽然线程按照1~3的顺序先后启动,但是某些产品也可能出现后续任务先被执行的情况。
解决:面对这个问题,我们做了如下尝试。
第一次尝试:加启动间隔,两个线程之间间隔1000ms。
public class Main {
public static void main(String[] args) throws InterruptedException {
Task[] tasks = new Task[20];
for (int i = 0; i < tasks.length; i++) {
tasks[i] = new Task();
}
Thread1 t1 = new Thread1(tasks);
Thread2 t2 = new Thread2(tasks);
Thread3 t3 = new Thread3(tasks);
t1.start();
Thread.sleep(1000);
t2.start();
Thread.sleep(1000);
t3.start();
// join: 等待线程运行结束
t1.join();
t2.join();
t3.join();
for (Task task : tasks) {
System.out.println(task.num);
}
}
}
测试结果:每个结果都是40000。
但这样的解决方案仍然存在缺陷。如果生产的是成千上万个任务,1000ms的间隔就不够用了,为了得到正确结果我们不得不设置更长的间隔,从而导致效率低下。
第二次尝试:完全不考虑任务执行的先后顺序,而是在执行完任务后贴上相应的标签——后序的任务,如果遇到没有贴上代表前序任务已经完成的标签的产品,就不执行本次任务,到下一次遍历时再检查标签情况。用这样的方式可以实现任务线程之间的信息交流。
public class Task {
public int num;
boolean flag1 = false;
boolean flag2 = false;
boolean flag3 = false;
public void task1() {
if (!flag1) {
num = 20;
flag1 = true;
}
}
public void task2() {
if (flag1 && !flag2) {
num *= 10;
flag2 = true;
}
}
public void task3() {
if (flag1 && flag2 && !flag3) {
num *= num;
flag3 = true;
}
}
}
在任务线程Thread1中,反复遍历产品数组,直到所有产品完成Task1为止。(2、3同理)
public class Thread1 extends Thread {
Task[] tasks;
public Thread1(Task[] tasks) {
this.tasks = tasks;
}
@Override
public void run() {
while (true) {
int count = 0;
for (Task task : tasks) {
task.task1();
if (task.flag1) {
count++;
}
}
if (count == 20) {
break;
}
}
}
}
测试结果:每个结果都是40000。
但这样的做法依然存在缺陷,有些任务可能需要遍历数组很多次。我们用下面的代码测试一下。
我们再设置一个变量,用于记录所在任务遍历数组的次数。
@Override
public void run() {
int times = 0;
while (true) {
times++;
int count = 0;
for (Task task : tasks) {
task.task1();
if (task.flag1) {
count++;
}
}
if (count == 5000) {
break;
}
}
System.out.println("Task1 - " + times);
}
测试结果:
Task1 - 1
Task2 - 2
Task3 - 2
这表明Task2和Task3都遍历了两次才完成任务。