大家在一开始接触多线程的时候,毫无疑问是用Thread的start()来创建启动一个线程。事实上,线程的创建与销毁是非常消耗时间与系统资源的,甚至比实际需求的消耗还要多。
那么问题来了,我应该怎么正确的使用多线程呢?
java.util.concurrent 包下的 Executors 可以帮我们创建一个线程池,只需传入相应的参数即可。线程池的原理其实就是对多线程的一个管理,为了实现异步机制的一种方法,其实就是多个线程执行多个任务,任务执行完毕,释放对线程的使用权,达成线程复用的效果。
线程池的使用
- 先会用,后看原理,是我不知道哪里听来的道理
- java 为我们提供了 5 种类型的线程池:newSingleThreadExecutor、newFixedThreadPool、newCachedThreadPool、newScheduledThreadPool、newWorkStealingPool
1、newSingleThreadExecutor
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class NewSingleThreadExecutorTest {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
for(int i = 0; i < 5; i++) {
executorService.execute(() -> {
System.out.println("当前执行线程是 + \t" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
executorService.shutdown();
}
}
- 可以看到,一直是同一个线程在执行
2、newFixedThreadPool
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class NewFixedThreadPoolTest {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
for(int i = 0; i < 9; i++) {
executorService.execute(() -> {
System.out.println("当前执行线程是 + \t" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
executorService.shutdown();
}
}
- 三个线程在固定切换
3、newCachedThreadPool
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class NewCachedThreadPoolTest {
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
for(int i = 0; i < 5; i++) {
executorService.execute(() -> {
System.out.println("当前执行线程是 + \t" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
Thread.sleep(10000);
System.out.println("================ Cache 复用 ================");
for(int i = 0; i < 5; i++) {
executorService.execute(() -> {
System.out.println("当前执行线程是 + \t" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
executorService.shutdown();
}
}
- 需要多少,创建多少,一定时间内可以复用
4、newScheduledThreadPool
- 周期性的执行任务,那么会存在一个问题,间隔时间与任务执行时间
- scheduleAtFixedRate 当任务执行时间大于间隔时间时,会立刻执行。简单来说,我的间隔时间其实是包含任务执行时间
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class NewScheduledThreadPoolTest {
public static void main(String[] args) throws InterruptedException {
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
executorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "\t " + "每2秒执行一次");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(System.currentTimeMillis());
}
}, 1, 2, TimeUnit.SECONDS);
// executorService.shutdown();
}
}
- scheduleWithFixedDelay:不管任务执行时间多久,我只管任务与任务的间隔时间正确。简单来说,我的间隔时间其实是不包含任务执行时间
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class NewScheduledThreadPoolTest {
public static void main(String[] args) throws InterruptedException {
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
executorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "\t " + "每2秒执行一次");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(System.currentTimeMillis());
}
}, 1, 2, TimeUnit.SECONDS);
// executorService.shutdown();
}
}
5、newWorkStealingPool
- 多个任务分成多个队列完成
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class NewWorkStealingPoolTest {
private static final int threads = 10;
public static void main(String[] args) throws InterruptedException {
// 用于计数线程是否执行完成
CountDownLatch countDownLatch = new CountDownLatch(threads);
System.out.println("---- start ----");
ExecutorService executorService = Executors.newWorkStealingPool(3);
for (int i = 0; i < threads; i++) {
executorService.execute(() -> {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
} catch (Exception e) {
System.out.println(e);
} finally {
countDownLatch.countDown();
}
});
}
countDownLatch.await();
System.out.println("---- end ----");
}
}
总结
- newSingleThreadExecutor:单一线程来执行任务,保证任务是按照指定顺序执行;
- newFixedThreadPool:创建固定大小的线程池,可控制线程最大的并发数,超出的线程会在队列中等待;
- newCachedThreadPool:无限扩大的线程池,需要就创建,一定时间后自动回收;
- newScheduledThreadPool:可以延时启动,执行间隔时间相同的任务;
- newWorkStealingPool:将任务分解成多个任务队列,缓冲线程池的压力;
execute() 和 submit() 的区别
- execute() 是 void 方法,没有返回值
- submit 有三个重载方法
- public Future<?> submit(Runnable task); // 不带返回值使用
- public Future submit(Runnable task, T result); // 在调用方法时,确定返回值
- public Future submit(Callable task); // 在Callable内返回需要的值
public class ExecuteAndSubmit {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1000));
threadPool.execute(() -> {
System.out.println("我是 execute,我没有返回值,需要返回值请使用 submit");
});
Future<?> submit2 = threadPool.submit(() -> {
System.out.println("我是submit,但传入的是 (Runnable)");
});
System.out.println("我是没有类型的返回值" + submit2.get());
Future<Integer> submit = threadPool.submit(() -> {
System.out.println("我是submit,但传入的是 (Callable)");
return 10;
});
System.out.println("Callable 返回值" + submit.get());
Future<Integer> submit1 = threadPool.submit(() -> {
System.out.println("我是submit,但传入的是 (Runnable,T)");
}, 1000);
System.out.println("我是submit,但传入的是 (Runnable),但同时传入了一个返回值" + submit1.get());
threadPool.shutdown();
}
}
线程池方法使用示例
- 这里我们使用自定义的线程池
- allowCoreThreadTimeOut:允许核心线程超时,即线程池不调用shutdown也可以自动关闭
- prestartCoreThread:核心线程预创建,true 表示创建成功,这里只会预创建一个
- prestartAllCoreThreads:预创建设置的核心线程,返回预创建的个数
- getActiveCount:获取当前活跃的线程个数
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class InstanceMethodsTest {
public static void main(String[] args) {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(3, 10,
1L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(2));
int activeCount = threadPool.getActiveCount();
System.out.println("刚创建完线程池的 activeCount" + activeCount);
System.out.println("============== 开始预创建线程 ==============");
/* 线程池核心线程允许超时设置 */
threadPool.allowCoreThreadTimeOut(true);
boolean flag = threadPool.prestartCoreThread();
System.out.println("prestartCoreThread 预创建单个线程是否成功" + flag);
int prestartAllCoreThreads = threadPool.prestartAllCoreThreads();
System.out.println("prestartAllCoreThreads: 创建全部核心线程" + prestartAllCoreThreads);
int activeCount1 = threadPool.getActiveCount();
System.out.println("preCoreThread预创建核心线程的 activeCount1: " + activeCount1);
System.out.println("============== 核心数已满,再来预创建线程 ==============");
boolean flag1 = threadPool.prestartCoreThread();
System.out.println("prestartCoreThread 预创建单个线程是否成功" + flag1);
int prestartAllCoreThreads1 = threadPool.prestartAllCoreThreads();
System.out.println("prestartAllCoreThreads: " + prestartAllCoreThreads1);
int activeCount2 = threadPool.getActiveCount();
System.out.println("preCoreThread预创建核心线程的 activeCount1: " + activeCount2);
}
}
- 监控线程池状态
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class InstanceMethodsTest01 {
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(50, 100,
1L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1000));
for(int i = 1; i <= 1000; i++) {
threadPool.execute(() -> {
try {
Thread.sleep(1000);
System.out.print("0");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
while (true) {
System.out.println();
int queueSize = threadPool.getQueue().size();
System.out.println("当前排队线程数getQueue:" + queueSize);
int activeCount = threadPool.getActiveCount();
System.out.println("当前活动线程数getActiveCount:" + activeCount);
long completedTaskCount = threadPool.getCompletedTaskCount();
System.out.println("执行完成线程数getCompletedTaskCount:" + completedTaskCount);
long taskCount = threadPool.getTaskCount();
System.out.println("总线程数getTaskCount:" + taskCount);
Thread.sleep(3000);
}
}
}
- 输出如下:
- getCompletedTaskCount:获取已完成任务大致总数, 不一定是实时,因为操作完成,记录也需要时间
当前排队线程数getQueue:950
当前活动线程数getActiveCount:50
执行完成线程数getCompletedTaskCount:0
总线程数getTaskCount:1000
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0当前排队线程数getQueue:812
当前活动线程数getActiveCount:50
执行完成线程数getCompletedTaskCount:139
总线程数getTaskCount:1000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
当前排队线程数getQueue:695
当前活动线程数getActiveCount:50
执行完成线程数getCompletedTaskCount:255
总线程数getTaskCount:1000
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
当前排队线程数getQueue:545
当前活动线程数getActiveCount:50
执行完成线程数getCompletedTaskCount:405
总线程数getTaskCount:1000
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
当前排队线程数getQueue:395
当前活动线程数getActiveCount:50
执行完成线程数getCompletedTaskCount:555
总线程数getTaskCount:1000
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
当前排队线程数getQueue:245
当前活动线程数getActiveCount:50
执行完成线程数getCompletedTaskCount:705
总线程数getTaskCount:1000
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
当前排队线程数getQueue:95
当前活动线程数getActiveCount:50
执行完成线程数getCompletedTaskCount:855
总线程数getTaskCount:1000
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
当前排队线程数getQueue:0
当前活动线程数getActiveCount:0
执行完成线程数getCompletedTaskCount:1000
总线程数getTaskCount:1000
当前排队线程数getQueue:0
当前活动线程数getActiveCount:0
执行完成线程数getCompletedTaskCount:1000
总线程数getTaskCount:1000
当前排队线程数getQueue:0
当前活动线程数getActiveCount:0
执行完成线程数getCompletedTaskCount:1000
总线程数getTaskCount:1000
Process finished with exit code -1
- 与线程池状态相关的,在下一篇文章 java 线程池的使用及原理(二):线程池的状态及证明 进行讲解。