java是一门支持多线程编程的语言,通常在线程内部,业务是按照我们编写的代码顺序执行的,但是当一个业务的处理分布在多个线程内的时候,代码的执行顺序是不可预知的,这时候就需要做一些特殊处理。
以下是三个demo,展示了如何在多线程环境中控制执行顺序。
demo1:
Step1Thread.java
package com.jiayq.demo.mthread;
public class Step1Thread implements Runnable {
private int index;
public Step1Thread(int index) {
super();
this.index = index;
}
@Override
public void run() {
System.out.println("正在处理第一步:"+index);
}
}
Test.java
package com.jiayq.demo.mthread;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
public class Test {
/**
* 不考虑第一步和第二步的先后顺序的场景
*/
@org.junit.Test
public void testThread(){
Executor exe = Executors.newFixedThreadPool(500);
//模拟处理第一步
for(int i=0; i<500; i++){
exe.execute(new Step1Thread(i));
}
System.out.println("================================================");
//模拟处理第二步
System.out.println("正在处理第二步");
}
}
打印输出:
demo1结果分析:
如果多线程环境下不做任何处理,业务逻辑的第二步无法保证在第一步全部执行完后执行。
为什么第二步的执行顺序无法保证呢?因为我们在第二步的执行线程中无法获取第一步的所有执行结果,假如我们可以获取第二步的执行结果,是否就可以控制第二步的流程呢?
demo2:
Step1Thread.java:
package com.jiayq.demo.call;
import java.util.concurrent.Callable;
public class Step1Thread implements Callable<Boolean> {
private int index;
public Step1Thread(int index) {
super();
this.index = index;
}
@Override
public Boolean call() throws Exception {
// TODO Auto-generated method stub
System.out.println("正在处理第一步:"+index);
return true;
}
}
Test.java:
package com.jiayq.demo.call;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Test {
/**
* 有返回值的多线程程序,通过监控返回值可以做到并发
*/
@org.junit.Test
public void testCallable(){
ExecutorService exe = Executors.newFixedThreadPool(100);
//执行第一步
List<Future<Boolean>> step1list = new ArrayList<Future<Boolean>>();
for(int i=0; i<100; i++){
step1list.add(exe.submit(new Step1Thread(i)));
}
//监控第一步的执行结果,如果全为true才执行下一步
boolean loop = true;
while(loop){
boolean r = listener(step1list);
if(!r){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else{
break;
}
}
System.out.println("================================================");
//执行第二步
System.out.println("正在处理第二步");
}
/**
* 监控Future列表,如果全为true才返回true
* @param list
* @return true:完成
*/
private boolean listener(List<Future<Boolean>> list){
boolean result = true;
for(int i=0; i<list.size(); i++){
if(!list.get(i).isDone()){
result = false;
break;
}
}
return result;
}
}
说明:
在第二个demo中我们创建线程使用了Callable接口,Callable接口和Runnable接口的最大区别是Callable接口提供了对返回值的支持(参考JDK中Callable接口的定义),
线程池类型我们换成了ExecutorService,该类型提供了submit方法,可以获取执行结果。拿到执行结果后循环监听结果集合,如果所有的线程任务都完成后,再执行第 二步,否则继续监听。
该实现基本上已经达到了流程控制的目的,但是监听的那段代码看起来有点丑陋,有没有更优雅的实现呢?
demo3:
Step1Thread.java
package com.jiayq.demo.countdown;
import java.util.concurrent.CountDownLatch;
public class Step1Thread implements Runnable {
private CountDownLatch cdl;
private int index;
public Step1Thread(CountDownLatch cdl,int index) {
super();
this.cdl = cdl;
this.index = index;
}
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("正在处理第一步:"+index);
cdl.countDown();
}
}
Test.java
package com.jiayq.demo.countdown;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
public class Test {
@org.junit.Test
public void testCountDown() throws InterruptedException{
CountDownLatch cdl = new CountDownLatch(100);
Executor exe = Executors.newFixedThreadPool(50);
for(int i=0; i<100; i++){
exe.execute(new Step1Thread(cdl, i));
}
cdl.await();
System.out.println("================================================");
System.out.println("正在处理第二步");
}
}
说明:
demo3也实现了第一步和第二步的流程控制,但是相比demo2实现更简洁,因为我们引入了新的类CountDownLatch.
CountDownLatch用法:
1,初始化计数器,应该和要接受控制的线程数量一致
2,每一个线程完成任务的时候,调用countdown()方法,计数器减一
3,使用await方法阻塞主线程,等待第一步所有任务完成,当计数器为0的时候再放行。
执行结果:
执行结果: