举个场景,用户表t_user根据userid做了分库分表,数据散列在了三个库表里t_user1、t_user2、t_user3,此时的count(t_user)需要count(t_user1) + count(t_user2) + count(t_user3)。我们为了提高性能,减少count(t_user)的响应时长,可以通过多线程并行执行各个子count,然后在主线程里将各个子任务的结果集进行合并。
如何让主线程等待子线程执行完成,本文介绍三种方式,并分析利弊。
1.thread.join()
thread.join()的作用是让当前线程(调用该方法所在的线程)阻塞住直到该thread线程终止。
package com.fanfan.concurrent.threadjoin;
/**
* Created by fanfan on 2017/3/19.
*/
public class UserServiceWithNoThreadPool {
public int count() throws Exception {
int[] counts = {0, 0, 0};
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
counts[0] = count1();
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
counts[1] = count2();
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
counts[2] = count3();
}
});
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
return counts[0] + counts[1] + counts[2];
}
private int count1() {
try {Thread.sleep(3000L);} catch (InterruptedException e) {e.printStackTrace();}
return 10;
}
private int count2() {
try {Thread.sleep(3000L);} catch (InterruptedException e) {e.printStackTrace();}
return 100;
}
private int count3() {
try {Thread.sleep(3000L);} catch (InterruptedException e) {e.printStackTrace();}
return 1000;
}
public static void main(String[] args) throws Exception {
UserServiceWithNoThreadPool userService = new UserServiceWithNoThreadPool();
System.out.println(userService.count());
}
}
2.使用线程池
在使用线程池的时候,我们就不能直接操作线程对象了,也就拿不到线程的句柄。另外,线程池里的线程处理完任务之后不是立即终止而是阻塞等待其它任务的执行,而thread.join()是要求线程终止,所以本质上thread.join()不适合应用在使用线程池的情况。
2.CountDownLatch
调用thread.join()方法必须等thread执行完毕,当前线程才能继续往下执行,虽然能够达到我们的目的,但是,还不是特别灵活。比如,在使用线程池的情况下,就无法使用。
JDK1.5开始提供了CountDownLatch这个类,通过计数器提供了更加灵活的控制,只要检测到计数器为0,当前线程就可以往下执行而不用管相应的thread是否执行完毕。
CountDownLatch的原理是这样的:比如,我们创建一个计数器为3的实例,让各个线程任务持有这个CountDownLatch实例,当每个任务完成自己的工作后,调用countDownLatch.countDown()方法将计数器减1。countDownLatch.await()方法会一直阻塞直到计数器为0,主线程才会继续往下执行。
package com.fanfan.concurrent.countdownlatch;
import java.util.concurrent.CountDownLatch;
/**
* Created by fanfan on 2017/3/19.
*/
public class UserServiceWithNoThreadPool {
public int count() throws Exception {
final CountDownLatch latch = new CountDownLatch(3);
int[] counts = {0, 0, 0};
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
counts[0] = count1();
latch.countDown();
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
counts[1] = count2();
latch.countDown();
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
counts[2] = count3();
latch.countDown();
}
});
t1.start();
t2.start();
t3.start();
latch.await();
return counts[0] + counts[1] + counts[2];
}
private int count1() {
try {Thread.sleep(3000L);} catch (InterruptedException e) {e.printStackTrace();}
return 10;
}
private int count2() {
try {Thread.sleep(3000L);} catch (InterruptedException e) {e.printStackTrace();}
return 100;
}
private int count3() {
try {Thread.sleep(3000L);} catch (InterruptedException e) {e.printStackTrace();}
return 1000;
}
public static void main(String[] args) throws Exception {
UserServiceWithNoThreadPool userService = new UserServiceWithNoThreadPool();
System.out.println(userService.count());
}
}
2.使用线程池
package com.fanfan.concurrent.countdownlatch;
import java.util.concurrent.*;
/**
* Created by fanfan on 2017/3/19.
*/
public class UserServiceWithThreadPool {
private ExecutorService executorService;
public int count() throws Exception {
final CountDownLatch latch = new CountDownLatch(3);
int[] counts = {0, 0, 0};
executorService.submit(new Runnable() {
@Override
public void run() {
counts[0] = count1();
latch.countDown();
}
});
executorService.submit(new Runnable() {
@Override
public void run() {
counts[1] = count2();
latch.countDown();
}
});
executorService.submit(new Runnable() {
@Override
public void run() {
counts[2] = count3();
latch.countDown();
}
});
latch.await();
return counts[0] + counts[1] + counts[2];
}
private int count1() {
try {Thread.sleep(3000L);} catch (InterruptedException e) {e.printStackTrace();}
return 10;
}
private int count2() {
try {Thread.sleep(3000L);} catch (InterruptedException e) {e.printStackTrace();}
return 100;
}
private int count3() {
try {Thread.sleep(3000L);} catch (InterruptedException e) {e.printStackTrace();}
return 1000;
}
public ExecutorService getExecutorService() {
return executorService;
}
public void setExecutorService(ExecutorService executorService) {
this.executorService = executorService;
}
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newFixedThreadPool(3);
com.fanfan.concurrent.callable.UserServiceWithThreadPool userService = new com.fanfan.concurrent.callable.UserServiceWithThreadPool();
userService.setExecutorService(executorService);
System.out.println(userService.count());
executorService.shutdown();
}
}
3.Feture、FetureTask和Callable
上面两种方法都有一个缺陷:在任务执行完毕之后无法获取执行结果,如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,使用起来比较麻烦。
而自JDK1.5开始,就提供了Future和Callable,通过它们可以在任务执行完毕之后得到任务执行结果。
package com.fanfan.concurrent.callable;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
* Created by fanfan on 2017/3/19.
*/
public class UserServiceWithNoThreadPool {
public int count() throws Exception {
FutureTask<Integer> futureTask1 = new FutureTask<Integer>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return count1();
}
});
FutureTask<Integer> futureTask2 = new FutureTask<Integer>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return count2();
}
});
FutureTask<Integer> futureTask3 = new FutureTask<Integer>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return count3();
}
});
new Thread(futureTask1).start();
new Thread(futureTask2).start();
new Thread(futureTask3).start();
return futureTask1.get() + futureTask2.get() + futureTask3.get();
}
private int count1() {
try {Thread.sleep(3000L);} catch (InterruptedException e) {e.printStackTrace();}
return 10;
}
private int count2() {
try {Thread.sleep(3000L);} catch (InterruptedException e) {e.printStackTrace();}
return 100;
}
private int count3() {
try {Thread.sleep(3000L);} catch (InterruptedException e) {e.printStackTrace();}
return 1000;
}
public static void main(String[] args) throws Exception {
UserServiceWithNoThreadPool userService = new UserServiceWithNoThreadPool();
System.out.println(userService.count());
}
}
2.使用线程池
package com.fanfan.concurrent.callable;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
* Created by fanfan on 2017/3/19.
*/
public class UserServiceWithThreadPool {
private ExecutorService executorService;
public int count() throws Exception {
Future<Integer> future1 = executorService.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return count1();
}
});
Future<Integer> future2 = executorService.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return count2();
}
});
Future<Integer> future3 = executorService.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return count3();
}
});
return future1.get() + future2.get() + future3.get();
}
private int count1() {
try {Thread.sleep(3000L);} catch (InterruptedException e) {e.printStackTrace();}
return 10;
}
private int count2() {
try {Thread.sleep(3000L);} catch (InterruptedException e) {e.printStackTrace();}
return 100;
}
private int count3() {
try {Thread.sleep(3000L);} catch (InterruptedException e) {e.printStackTrace();}
return 1000;
}
public ExecutorService getExecutorService() {
return executorService;
}
public void setExecutorService(ExecutorService executorService) {
this.executorService = executorService;
}
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newFixedThreadPool(3);
UserServiceWithThreadPool userService = new UserServiceWithThreadPool();
userService.setExecutorService(executorService);
System.out.println(userService.count());
executorService.shutdown();
}
}
总结
- 多线程的场景里,我们尽可能的使用线程池,而不要直接new Thread()。因为直接new Thread()除了创建线程耗时之外,更严重的问题是会造成线程数量不可控,成百上千的线程甚至更高,很容易占满内存,占满CPU。
- thread.join必须等待子线程执行完毕,主线程才能继续,灵活性不足,不能在线程池场景里面使用。
- CountDownLatch是JDK1.5开始提供的,弥补了thread.join的不足,使用起来更加灵活。可以适应于一些更加灵活的场合,比如不要求线程任务全部执行完毕,而只是在线程任务执行到某个阶段,主线程就停止阻塞继续向下执行。
- 为了方便获取子线程的执行结果,JDK1.5开始,提供了Future和Callable。如果遇到此类场景,优先使用Future和Callable。