Java–线程池
一系列和锁相关的工具:
- synchronized、ReentrantLock、AtomicXXX类,
之前我们也提到了内核线程模型,Hotspot虚拟机线程的创建都是通过内核线程提供的轻量级进程API创建的,反复的创建线程会不断的由OS线程调度,这也会消耗大量CPU性能
争对创建线程,除了使用Thread之外,Java还提供了线程池类,
- 它们最终都实现的Executor接口,JDK1.7中的继承实现关系如下:
一、线程池与Thread
使用Thread创建10000个线程,并运行,记录时间:
//当前时间
long startTime = System.currentTimeMillis();
//创建 10000 一万个 线程,存储
List<Thread> threads = new ArrayList<>(10000);
for (int i = 0; i < 10000; i++) {
Thread thread = new Thread();
threads.add(thread);
thread.start();
}
for(Thread t:threads){
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("ms:"+(System.currentTimeMillis() - startTime));
Thread-0
Thread-1
Thread-2 #线程挨个执行。这是加入 t.getName()打印的
ms:1
ms:612 # 1万个
- countDownLatch这个类使一个线程等待其他线程各自执行完毕后再执行。
- 是通过一个计数器来实现的,计数器的初始值是线程的数量。
- 每当一个线程执行完毕后,计数器的值就-1,
- 当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。
使用线程池:
//创建只有一个线程的线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(() -> {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
});
executorService.shutdown();
System.out.println("我是下面的执行");
我是下面的执行
0
1
public static void main(String[] args) {
//当前时间
long startTime = System.currentTimeMillis();
//线程数量
int size = 10000;
//创建只有一个线程的线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
final CountDownLatch countDownLatch = new CountDownLatch(size);
for (int i = 0; i < size; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
//进行减,每次执行完毕,就 --1
countDownLatch.countDown();
}
});
}
try {
//如:7304,说明才 执行2700个线程
System.out.println(countDownLatch.getCount());
countDownLatch.await();//等待10000计数结束
} catch (InterruptedException e) {
e.printStackTrace();
}
//关闭线程
executorService.shutdown();
System.out.println("ms:" + (System.currentTimeMillis() - startTime));
}
ms:12
两者相差的时间为100倍以上,这边使用的线程池回头再来看,由这个例子可以看出,线程的创建到运行过程,会花费大量的cpu时间,争对需要大量并发的操作,使用线程池可以大大提高程序性能
二、ThreadPoolExecutor
1.ThreadPoolExecutor执行任务图解
Executors是个帮助类,内部实际使用的是ThreadPoolExecutor,构造函数如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
}
ThreadPoolExecutor大致的执行场景如下:
2.ThreadPoolExecutor参数
再来看构造函数中各个参数的含义:
-
corePoolSize:核心线程最大数,当有新任务时,
- 如果小于该值,那么会直接创建线程或使用空闲的核心线程执行任务
-
maximumPoolSize:最大线程数,需大于核心线程数,
- 非核心线程最大数 = 最大线程数 - 核心线程最大数,当核心线程全部创建并都在执行任务,那么新任务使用非核心线程执行,同样使用核心线程创建机制:创建线程或使用空闲的线程,非核心线程可以指定死亡时间,相当于临时工
-
keepAliveTime:非核心线程的保持存活时间,当一个非核心线程持续keepAliveTime时间都处于空闲状态,那么使它消亡
-
unit:保持存活时间的时间单位
-
workQueue:任务队列,当线程总数到达最大,并且所有线程都有任务在执行,那么新任务可以进入该队列,等待有线程空闲时执行队列中的任务。
- 通过Executors创建的任务队列,都没有最大值,所以当遇到服务器并发量大的情况,容易OOM,安卓开发中不需要考虑这个问题
-
threadFactory:创建线程工厂,一般使用默认的即可
-
handler:拒绝策略,当触发拒绝时,执行该对象策略
Excutors 创建线程池的方法最终都是通过 ThreadPoolExcutor 创建出来的。
- corePoolSize 线程池始终活跃的线程数。
- maximumPoolSize 线程池最大能创建的线程数。
- keepAliveTime 始终活跃线程之外的 线程空闲之后存活的最长时间。
- unit 存活时间单位。
- workQueue 当线程池工作饱和时,新来的任务存放的队列。
Excutors 线程池的参数 2
-
创建指定大小的线程池
public static ExecutorService newFixedThreadPool(int nThreads)
-
创建无限大小的线程池,初始大小为0
public static ExecutorService newCachedThreadPool()
-
创建无限大小的线程池,初始大小指定
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
-
创建只有一个线程的线程池
public static ExecutorService newSingleThreadExecutor()
3.任务队列
任务队列实现BlockingQueue接口,JDK提供了以下4种:
- ArrayBlockingQueue: 是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
- LinkedBlockingQueue: 一个基于链表结构的阻塞队列,此队列按FIFO(先进先出)排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
- SynchronousQueue: 一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
- PriorityBlockingQueue: 一个具有优先级的无限阻塞队列。
4.拒绝策略
- DiscardPolicy:丢弃当前将要加入队列的任务本身。
- DiscardOldestPolicy:丢弃任务队列中最旧任务。(丢弃最旧任务也不是简单的丢弃最旧的任务,而是有一些额外的处理)
- AbortPolicy:抛出异常的方式。
- CallerRunsPolicy:不进入线程池执行,由调用者线程执行。
三、Executors
public static void main(String[] args) {
//一下执行很多线程
ExecutorService executorService1 = Executors.newCachedThreadPool();
//这里是 2个2个的执行
ExecutorService executorService2 = Executors.newFixedThreadPool(2);
//一个一个的执行
ExecutorService executorService3 = Executors.newSingleThreadExecutor();
for (int i = 1; i <= 100; i++) {
//executorService1.execute(new TestRunnable(i));
executorService2.execute(new TestRunnable(i));
// executorService3.execute(new TestRunnable(i));
}
}
static class TestRunnable implements Runnable {
int i = 0;
public TestRunnable(int i) {
this.i = i;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
1.newCachedThreadPool
newCachedThreadPool方法如下:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
它和直接创建线程类似,可以创建无限的线程
2.newFixedThreadPool
newFixedThreadPool方法如下:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
它拥有指定大小的核心线程数,并且只有核心线程,以及一个无限大小的任务队列
3.newSingleThreadExecutor
newSingleThreadExecutor方法如下:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
它仅有一个核心线程,以及一个无限大小的任务队列,所以相当于单线程执行任务队列中的任务