工作总往往会遇到异步去执行某段逻辑, 然后先处理其他事情, 处理完后再把那段逻辑的处理结果进行汇总的场景, 这时候就需要使用线程了.
一个线程启动之后, 是异步的去执行需要执行的内容的, 不会影响主线程的流程, 往往需要让主线程指定后, 等待子线程的完成。并且,主线程是要利用到子线程的返回数据进行处理。这里有2种方式:实现 Callable
接口、join()
方法
1、实现 Callable 接口
class SubThread implements Callable<Integer> {
private Integer num1;
private Integer num2;
public SubThread(Integer num1, Integer num2) {
this.num1 = num1;
this.num2 = num2;
}
@Override
public Integer call() {
System.out.println("执行子线程");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return this.num1 + this.num2;
}
}
-
Callable
接口是一个泛型,实现时需要指定类型,这个类型就是call()
方法执行后返回结果的类型 -
call()
方法有返回值
public class ExampleOne {
public static void main(String[] args) {
System.out.println("主线程开始运行...");
// 1.新建一个线程池
ExecutorService executorService = Executors.newFixedThreadPool(1);
try {
SubThread subThread = new SubThread(1, 1);
// 2.调度线程去执行
Future<Integer> submit = executorService.submit(subThread);
// 3.阻塞,等待子线程结束
Integer integer = submit.get();
System.out.println(integer);
} catch (Exception e) {
e.printStackTrace();
}
// 4.关闭线程池
executorService.shutdown();
System.out.println("主线程结束运行...");
}
}
-
Future
是一个任务执行的结果, 他是一个将来时, 即一个任务执行, 立即异步返回一个Future
对象, 等到任务结束的时候, 会把值返回给这个future
对象里面 -
Future#get()
为一个阻塞方法。主线程执行到这后,如果子线程还没有执行完毕,则主线程会阻塞起来,等待子线程结束后再执行。
2、join()方法
class SubThreadTwo implements Runnable {
private Integer num1;
private Integer num2;
private Integer result;
public SubThreadTwo(Integer num1, Integer num2) {
this.num1 = num1;
this.num2 = num2;
}
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
result = num1 + num2;
}
public Integer getResult() {
return result;
}
public void setResult(Integer result) {
this.result = result;
}
}
- 通过构造方法可以给此线程传参
public class ExampleTwo {
public static void main(String[] args) {
System.out.println("主线程运行开始");
SubThreadTwo subThreadTwo = new SubThreadTwo(1, 1);
Thread thread = new Thread(subThreadTwo);
thread.start();
try {
// 1.让主线程进入阻塞状态
thread.join();
// 2.子线程完成后主线程被唤醒,getResult()方法获得子线程计算的值
System.out.println(subThreadTwo.getResult());
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程运行结束");
}
}
-
join()
:把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程
3、CountDownLanch
上面两种情况在线程数为一两个的时候,还可以,如果需要控制的线程数很多的话,再采取这种方式就有点过意不去了。
第一种方法, 你要调用很多个线程的 join()
,特别是当你的线程不是for循环创建的, 而是一个一个创建的时候.
第二种方法, 要调用很多的 future
的 get()
方法, 同第一种方法
CountDownLanch:
CountDownLanch 是一个倒数计数器, 给一个初始值(>=0), 然后每一次调用 countDown 就会减1,这很符合等待多个子线程结束的场景: 一个线程结束的时候,countDown 一次,直到所有的线程都countDown了,那么所有子线程就都结束了。
await()
: 会阻塞等待计数器减少到0位置. 带参数的await是多了等待时间.
countDown()
: 将当前的计数减1
getCount()
: 返回当前的计数
显而易见, 我们只需要在子线程执行之前, 赋予初始化 countDownLanch,并赋予线程数量为初始值.
每个线程执行完毕的时候, 就 countDown 一下.主线程只需要调用 await()
方法, 可以等待所有子线程执行结束
class SubThreadThree extends Thread {
private CountDownLatch countDownLatch;
public SubThreadThree(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
System.out.println("子线程开始执行");
Thread.sleep(2000);
System.out.println("子线程结束执行");
} catch (Exception e) {
e.printStackTrace();
} finally {
// 线程结束时,将计时器减一
countDownLatch.countDown();
}
}
}
CountDownTest:
CountDownLatch
计数器的用法:
public class CountDownTest {
public static void main(String[] args) {
System.out.println("主线程开始启动");
// 1.定义线程数
int subThreadNum = 3;
// 2.取得一个倒计时器,从3开始
CountDownLatch countDownLatch = new CountDownLatch(subThreadNum);
// 3.依次创建3个线程,并启动
/*for (int i = 0; i < subThreadNum; i++) {
new SubThreadThree(countDownLatch).start();
}*/
Thread t1 = new SubThreadThree(countDownLatch);
// 异步(开启线程)去执行第一个任务
t1.start();
Thread t2 = new SubThreadThree(countDownLatch);
// 异步(开启线程)去执行第二个任务
t2.start();
Thread t3 = new SubThreadThree(countDownLatch);
// 异步(开启线程)去执行第三个任务
t3.start();
try {
Thread.sleep(2000);
// 4.等待所有的子线程结束
countDownLatch.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("主线程开始停止");
}
}
线程 t1
、t2
、t3
并发地异步执行任务,直到这三个任务执行完毕,主线程才会继续往下走。否则,它会阻塞。