一、线程池简介
在使用多线程技术时,通常会创建和销毁大量的线程,占用了很多的系统资源,这对系统的性能造成了很大的影响。
在 JDK 5之后引进了 Executor 框架,也就是线程池技术,通过 Executor 来启动线程比使用 Thread 的 start 方法更好,除了更易管理,效率更好(用线程池实现,节约开销)外,还有关键的一点:有助于避免 this 逃逸问题。
线程池中可以设置线程的初始数量,以及一些线程的操作方法。
- 使用线程池的好处
- 降低资源消耗: 线程池里的每一个线程任务完成后不会直接销毁,而是会回到线程池中,并转换为空闲状态等待下一个对象去调用。
- 提高响应速度: 当任务到达时,任务可以不需要重新创建线程,可以直接调用线程池中的线程执行。
- 提高线程的可管理性: 线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,限制线程的数量,有利于调优和监控。
二、线程池的使用
1、ExecutorService 接口和 Executors 类
ExecutorService 表示一个线程池实例.
Executors 是一个工厂类, 能够创建出几种不同风格的线程池.
ExecutorService 的 submit 方法能够向线程池中提交若干个线程任务.
ExecutorService 的 shutdown() 方法可以关闭线程池.
2、Executors 类创建线程池的几种方式
- public static ExecutorService newFixedThreadPool(int nThreads): 创建一个定长线程池,可以设置线程池中的最大线程数量,超出的线程会在队列中等待。
Demo 代码示例:
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() );
}
}
class ExecutorsDemo {
public static void main(String[] args) {
// 创建一个线程池对象,控制要创建几个线程对象。
ExecutorService pool = Executors.newFixedThreadPool(4);
// 可以执行Runnable对象或者Callable对象代表的线程
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
//结束线程池
pool.shutdown();
}
}
运行结果:通过结果可以看出线程池最大线程数量是 4 ,所以就只创建了四个线程,当执行第五个任务时不继续创建线程而是去线程池中去调用之前创建的线程。
- public static ExecutorService newCachedThreadPool(): 创建一个具有缓存功能的线程池,创建的线程数量可以动态增长。
Demo 代码示例:
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() );
}
}
class ExecutorsDemo {
public static void main(String[] args) {
// 创建一个有缓存功能的线程池对象,并动态创建线程对象。
ExecutorService pool = Executors.newCachedThreadPool();
// 可以执行Runnable对象或者Callable对象代表的线程
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
//结束线程池
pool.shutdown();
}
}
运行结果:通过结果可以看出有多少个线程任务就创建了多少线程对象。但是也不能一概而论,因为线程池只要不关闭,其中的线程就一直存在,所以有时候也可能是继续调用线程池中已经存在的线程。
- public static ExecutorService newSingleThreadExecutor(): 创建一个只有单线程的线程池,相当于将newFixedThreadPool() 方法的参数设置成1。这个线程池更适合用于唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
Demo 代码示例:
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() );
}
}
class ExecutorsDemo {
public static void main(String[] args) {
// 创建一个只有单线程的线程池
ExecutorService pool = Executors.newSingleThreadExecutor();
// 可以执行Runnable对象或者Callable对象代表的线程
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
//结束线程池
pool.shutdown();
}
}
运行结果:通过结果可以看出,线程池中只创建了一个线程对象,五次线程任务调用了同一个线程对象。
三、定时器
在开发中经常需要一些周期性的操作,例如每隔几分钟就进行某一项操作。这时候我们就要去设置个定时器,Java中最方便、最高效的实现方式是用java.util.Timer工具类,再通过调度java.util.TimerTask任务。
Timer是一种工具,线程用其安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行。实际上是个线程,定时调度所拥有的TimerTasks。
TimerTask是一个抽象类,它的子类由 Timer 安排为一次执行或重复执行的任务。实际上就是一个拥有run方法的类,需要定时执行的代码放到run方法体内。
-
Timer:定时器
- public Timer(): 定时器的构造方法
- public void schedule(TimerTask task, long delay): 设置任务启动时间,时间到了启动定时器中的定时任务,执行完成关闭任务。
- public void schedule(TimerTask task,long delay,long period): 设置任务启动时间,和间隔启动时间,时间到了启动定时器中的定时任务,执行完当前任务后不关闭任务,到间隔时间继续执行任务。
-
TimerTask:定时任务
- 创建定时任务有两种方式:
- 通过构造方法创建定时任务抽象类对象
- public TimerTask(): 定时任务的构造方法
- 直接继承 TimerTask 抽象类,将子类定义成定时任务类
- 通过构造方法创建定时任务抽象类对象
- 创建定时任务有两种方式:
-
继承 TimerTask 抽象类实现定时任务
- 单次执行的定时任务 Demo 代码示例:
public class TimerDemo {
public static void main(String[] args) {
// 创建定时器对象
Timer t = new Timer();
// 3秒后执行定时任务,并结束任务
t.schedule(new MyTask(t), 3000);
}
}
// 继承 TimerTask 类,将此类定义为定时任务类
class MyTask extends TimerTask {
private Timer t;
public MyTask(){}
public MyTask(Timer t){
this.t = t;
}
@Override
public void run() {
System.out.println("该学习了孩子");
t.cancel();//取消任务
}
}
- 多次执行的定时任务 Demo 代码示例:
public class TimerDemo2 {
public static void main(String[] args) {
// 创建定时器对象
Timer t = new Timer();
// 3秒后执行定时任务第一次,如果不成功,每隔2秒再继续炸
t.schedule(new MyTask2(), 3000, 2000);
}
}
// 继承 TimerTask 类,将此类定义为定时任务类
class MyTask2 extends TimerTask {
@Override
public void run() {
System.out.println("孩子,该学习了");
}
}
- 通过构造方法创建定时任务
- Demo 代码示例:定时任务在下一分钟00秒时执行一次
public class TimerDemo {
public static void main(String[] args) throws InterruptedException {
// 创建定时器
Timer timer = new Timer("myTimer");
// 创建定时任务
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
System.out.println(LocalDateTime.now() + "孩子~该学习了");
}
};
Date nextMinute = nextMinute();
System.out.println("now:" + new Date() + ", execute:" + nextMinute);
// 添加任务定义时间
timer.schedule(timerTask ,nextMinute);
}
// 获取下一分钟的时间
private static Date nextMinute(){
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.MINUTE, 1);
calendar.add(Calendar.SECOND, -calendar.get(Calendar.SECOND));
return calendar.getTime();
}
}