记得读大学时,老师教我们创建一个Java线程有两种方式,这里我们先来简单温习一下在Java中如何创建一个线程,方便帮助小白扫盲:
- 通过继承Thread类,然后通过重写父类中的run方法,实现我们的线程业务逻缉,示例代码如下:
1
2
3
4
5
6
7public class MyThread extends Thread{
public void run() {
System.out.println("hello,I'm a thread,my thread id is "+Thread.currentThread().getId());
}
} - 通过实现Runable接口,然后实现Runable接口中的run方法,实现我们的线程业务逻缉,示例代码如下:我们之前使用线程的时候都是使用new Thread的方式来进行线程的实例化创建,但是这样会有一些问题。如:
1
2
3
4
5
6
7public class MyThread2 implements Runnable{
public void run() {
System.out.println("hello,I'm a thread,my thread id is "+Thread.currentThread().getId());
}
}
- 每次new Thread新建对象性能差。
- 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
- 缺乏更多功能,如定时执行、定期执行、线程中断。
那么,有没有更好的方式来创建线程,答案当然有。Java为我们提供了四种线程池来帮助我们规范且更好的使用线程,相比new Thread,线程池的好处有:
- 重用存在的线程,减少对象创建、消亡的开销,性能佳。
- 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
- 提供定时执行、定期执行、单线程、并发数控制等功能。
下面就让我们来了解一下Java提供的四种线程池,在Java中线程池是通过ExecutorService这个顶级接口来定义,Executors分别提供了四种线程池的创建方法,而四种线程池分别为:
- CachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
- FixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
- ScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
- SingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
CachedThreadPool示例代码
1 | ExecutorService executorService = Executors.newCachedThreadPool(); |
运行结果如下,可以看到线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。
1 | my thread id is 13 |
FixedThreadPool示例代码
1 | ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3); |
运行结果如下,因为线程池大小为3,每个任务输出线程ID后sleep 1秒,所以每1秒打印3个线程ID。
定长线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()。可参考PreloadDataCache。
1 | my thread id is 13 |
ScheduledThreadPool示例代码
延迟3秒执行一次
1 | ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5); |
延迟1秒后每3秒执行一次
1 | ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5); |
SingleThreadExecutor示例代码
1 | ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); |
运行结果如下,可以看到每执行一次线程,sleep1秒,只有一个线程来完成任务的执行。
1 | my thread id is 13 |
