线程池
1. 简介
- 传统多线程带来的问题:
- 多线程运行期间,线程不断的启动和关闭新线程,会过度消耗系统资源。
- 过度切换线程的危险,从而可能导致系统资源的崩溃。
(不推荐使用直接创建线程)
池化技术:
不直接创建具体的资源,而是创建一个池,在池里面创建具体的资源。
以前是直接把任务交给具体的资源,而现在把资源交给池,池就会让空闲的资源去执行任务,任务执行完了以后,资源并不会销毁,而是停留在池里面,等待下一个任务来执行。
线程池好处:
- 降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗
- 方便线程并发时的管控
- 提供更强大的功能,延时定时线程池(可以让任务每几秒执行一次)
2. 工作原理
在线程池的编程模式下,任务是提交给整个线程池,而不是直接提交给某个线程,线程池在拿到任务后,就在内部寻找是否有空闲的线程,如果有,则将任务交给某个空闲的线程。
一个Runnable就是一个任务
一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务
线程池工作的本质就是将要执行的任务添加到队列中,然后线程池寻求空闲的线程来执行队列里面的任务。
注:队列:先进先出
线程池里面的线程不会自动销毁
3. 线程池介绍
3.1 工作队列
线程池都是将要执行的任务存入到工作队列中,然后在线程池里面寻找空闲的线程去执行队列里面的任务。java中主要采用BlockingQueue
去存储任务。
3.1.1 BlockingQueue(阻塞队列)
双缓冲队列,内部使用两条队列,允许两个线程同时向队列一个存储,一个取出操作。在保证并发安全的同时,提高了队列的存取效率。
BlockingQueue
阻塞队列,继承了Queue
队列,遵循先进先出原则(FIFO)
队列提供几种基本操作:
- 添加元素(队尾)
- 移除元素(队头)
- 取出队头元素(不移除)
每种操作都有两个方法,一种有可能抛异常,一种返回操作成功或失败。
3.1.2 BlockingQueue实现类
- ArrayBlockingQueue
采用数组实现,规定大小的BlockingQueue,其构造必须指定大小。其所含的对象是FIFO顺序排序的。
public ArrayBlockingQueue(int capacity) {
this(capacity, false);
}
- LinkedBlockingQueue
采用双向链表实现,大小不固定,若其构造时指定大小,生成的BlockingQueue有大小限制,不指定大小时,其大小由Integer.MAX_VALUE来决定。其所含的对象是FIFO顺序排序的。
public LinkedBlockingDeque() {
this(Integer.MAX_VALUE);
}
public LinkedBlockingDeque(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
}
- PriorityBlockingQueue
(排序队列)类似LinkedBlockingQueue,但是其所含对象的排序不是FIFO,而是依据对象的自然排序或者比较器排序(Comparator)
public PriorityBlockingQueue() {
this(DEFAULT_INITIAL_CAPACITY, null);
}
- SynchronousQueue *
是特殊的BlockingQueue
每次删除操作都要等待插入操作
每次插入操作都要等待删除操作
一个元素,一旦有了插入线程和移除线程,那么很快由插入线程移交给移除线程,这个容器相当于通道,本身不存储元素
在多任务队列,是最快的处理任务方式
(推荐使用)
public SynchronousQueue() {
this(false);
}
3.2 创建线程池
Java中使用ThreadPoolExecutor
来表示线程池,创建该类对象来表示创建一个线程池。
创建:
ThreadPoolExecutor executor =
new ThreadPoolExecutor(
corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
corePoolSize:核心线程数(核心线程不用的时候也不会被回收)
maximumPoolSize:线程池中可以容纳的最大线程数量
keepAliveTime:线程池中除了核心线程之外的其他线程的最长可以保留的时间,除了核心线程及时在无任务的情况下也不能被清除,其余的都是有存活时间的,意思就是非核心线程可以保留的最长的空闲时间。
unit:时间单位(使用枚举类型TimeUnit)
workQueue:工作队列
例:
//创建一个线程池,其核心线程有两个,最多可容纳6个线程,非核心线程可以保留的最长空闲时间为2秒,使用了SynchronousQueue工作队列
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 6, 2, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
3.3 使用线程池
package com.test.demo1;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class PoolTest {
public static void main(String[] args) {
// 创建线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 6, 2, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
// 提交任务去执行
// 一个Runnable就是一个任务
Runnable t1 = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
};
Runnable t2 = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
};
Runnable t3 = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
};
// 以前的方法:
// new Thread(t1).start();
// 线程池执行任务,任务执行完毕,JVM并不会关闭,因为里面的核心线程没有结束掉
executor.execute(t1);
executor.execute(t2);
executor.execute(t3);
// 关闭线程池。他需要等到里面所有的线程空闲了才会关闭,
// 如果有线程正在执行,那就等待执行完毕再关闭
executor.shutdown();
}
}
关闭线程池
shutdown()
,需要等到里面所有的线程空闲了才会关闭, 如果有线程正在执行,那就等待执行完毕再关闭
4. 常用线程池
java.util.concurrent.Executors
工具类
java通过Executors提供四中线程池:
newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。newFixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。newSingleThreadExecutor
创建一个单线程化的线程池,他只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
4.1 newCachedThreadPool
通过源码可知,该线程池,无核心线程,创建的都是非核心线程,最大线程数为Integer.MAX_VALUE,空闲线程存活时间为1分钟。
注意:该方法返回类型为ExecutorService
原理:先查看池中是否有以前建立的线程,如果有,就直接使用。如果没有,就建一个新的线程加入池中,缓存型池子通常用于执行一些生存期很短的异步型任务
- 特点:重复使用线程,一旦线程空闲就不会创建,而是使用上一个任务遗留的线程去执行
- 能不创建就不创建,只有迫不得已才会创建
例:
package com.test.demo1;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class newCachedThreadPool {
public static void main(String[] args) throws InterruptedException {
/*
* 该线程池的优点:重复使用线程,一旦线程空间就不创建 而是使用上一个任务遗留的线程去执行
* (能不创建就不创建,只有迫不得已才会创建,最多创建0x7ffffff)
*/
// 工具类
ExecutorService exe = Executors.newCachedThreadPool();
for (int i = 0; i < 20; i++) {
Thread.sleep(2000);
exe.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
}
}
4.2 newFixedThreadPool
通过源码可知,该线程池的核心线程数和最大线程数一样(都是自定义的),空闲线程存活时间为0,使用了
LinkedBlockingQueue
如果队列里面有线程任务的话就从队列里面取出线程,然后开启一个新的线程开始执行。如果没有就会进行等待,有空闲线程再使用。这个线程池始终只有固定数量的线程在运行,大小固定,难以扩展。
所以定义线程时,最好是根据线程池大小需要根据资源配置进行设置:Runtime.getRuntime().availableProcessors()
即:查看自己电脑的线程数 System.out.println(Runtime.getRuntime().availableProcessors());
Runtime.getRuntime()//获取运行时环境
Runtime.getRuntime().availableProcessors//获取当前计算机最多可并行的线程数
例:
package com.test.demo1;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class NewFixedThreadPoolTest {
public static void main(String[] args) {
/*
* 线程池一旦创建就会创建执行指定size的核心线程 JVM不会关闭
* 需要指定核心线程个数
* 它全都是核心线程
*/
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 20; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
}
}
4.3 newScheduledThreadPool
通过源码可知,该线程池需要指定核心线程个数,最大线程个数为Integer.MAX_VALUE,空闲线程存活时间为0,使用的是
SelayedWorkQueue
工作队列。
package com.test.demo1;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class NewScheduledThreadPoolTest2 {
public static void main(String[] args) {
// 有5个核心线程
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
// 延迟执行
// 3秒后执行
executorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}, 3, TimeUnit.SECONDS);
// 定时器
// 延迟一秒执行,之后每隔两秒执行一次
executorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}, 1, 2, TimeUnit.SECONDS);
}
}
4.4 newSingleThreadExecutor
通过源码可知,该线程池只创建了一个核心线程,且最大线程数为1,空闲线程存活时间为0,使用了
LinkedBlockingQueue
。
它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行。
package com.test.demo1;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class newSingleThreadExecutor {
public static void main(String[] args) throws InterruptedException {
/*
* 线程池里面固定只创建了一个线程,
* 这样就可以保证按照顺序去执行
*/
ExecutorService exe = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
exe.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
}
}