线程池的一些好处
通过重用线程池中的线程,来减少每个线程创建和销毁的性能开销。
对线程进行一些维护和管理,比如定时开始,周期执行,并发数控制等等。
Executor框架
用于任务执行,接口有一个execute
方法,用于提交任务。
public interface Executor {
void execute(Runnable command);
}
Java的四种线程池
newFixedThreadPool
newFixedThreadPool将创建一个固定长度的线程池,每当提交一个任务时就创建一个线程,直到达到线程池的最大数量。如果在满了的情况下继续提交任务,则会进入队列等待。
public class PoolTest {
public static void main(String[] args) {
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(4);
for (int i = 0; i < 8; i++) {
int index = i;
fixedThreadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + ": " +index);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
}
/*
pool-1-thread-1: 0
pool-1-thread-3: 2
pool-1-thread-2: 1
pool-1-thread-4: 3
pool-1-thread-1: 4
pool-1-thread-4: 5
pool-1-thread-2: 6
pool-1-thread-3: 7
*/
newCachedThreadPool
newCachedThreadPool创建一个可缓存的线程池,线程长度如果大于任务数,可以回收,线程池的大小上限为Integer.MAX_VALUE。
public class PoolTest {
public static void main(String[] args) {
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 8; i++) {
int index = i;
cachedThreadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + ": " +index);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
}
/*
pool-1-thread-1: 0
pool-1-thread-2: 1
pool-1-thread-3: 2
pool-1-thread-5: 4
pool-1-thread-4: 3
pool-1-thread-6: 5
pool-1-thread-7: 6
pool-1-thread-8: 7
*/
newSingleThreadExecutor
newSingleThreadExecutor是一个单线程的线程池,如果线程异常结束,会创建另一个线程来替代,能确保依照任务在队列中的顺序来串行执行。
public class PoolTest {
public static void main(String[] args) {
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 8; i++) {
int index = i;
newSingleThreadExecutor.execute(() -> {
System.out.println(Thread.currentThread().getName() + ": " +index);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
}
/*
pool-1-thread-1: 0
pool-1-thread-1: 1
pool-1-thread-1: 2
pool-1-thread-1: 3
pool-1-thread-1: 4
pool-1-thread-1: 5
pool-1-thread-1: 6
pool-1-thread-1: 7
*/
newScheduledThreadPool
newScheduledThreadPool创建一个固定长度的线程池,可以延迟或定时地执行任务。
public class PoolTest {
public static void main(String[] args) {
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(2);
scheduledThreadPool.schedule(() -> System.out.println(Thread.currentThread().getName() + "延迟2秒执行了"), 2, TimeUnit.SECONDS);
scheduledThreadPool.scheduleAtFixedRate(() -> System.out.println(Thread.currentThread().getName() + "定时执行了任务"), 3, 3, TimeUnit.SECONDS);
}
}
/*
pool-1-thread-1延迟2秒执行了
pool-1-thread-2定时执行了任务
pool-1-thread-2定时执行了任务
pool-1-thread-2定时执行了任务
pool-1-thread-2定时执行了任务
pool-1-thread-2定时执行了任务
*/
上面四种常见的线程池里面的实现都用到了ThreadPoolExecutor
,下面介绍。
ThreadPoolExecutor
ThreadPoolExecutor的通用构造函数
public ThreadPoolExecutor(
int corePoolSize, // 线程池基本大小
int maximumPoolSize, // 线程池最大大小
long keepAliveTime, // 线程保持存活时间
TimeUnit unit, // 存活时间的单位
BlockingQueue<Runnable> workQueue, // 任务队列
ThreadFactory threadFactory, // 线程工程
RejectedExecutionHandler handler // 饱和策略
) {//...}
BlockingQueue
有四种常见的队列
ArrayBlockingQueue
:基于数组的有界阻塞队列。队列按FIFO原则对元素进行排序。 这是一个典型的“有界缓存区”,固定大小的数组在其中保持生产者插入的元素和使用者提取的元素。一旦创建了这样的缓存区,就不能再增加其容量。试图向已满队列中放入元素会导致操作受阻塞;试图从空队列中提取元素将导致类似阻塞。LinkedBlockingQueue
:基于链表的无界阻塞队列。与ArrayBlockingQueue一样采用FIFO原则对元素进行排序。基于链表的队列吞吐量通常要高于基于数组的队列。SynchronousQueue
:同步的阻塞队列。其中每个插入操作必须等待另一个线程的对应移除操作,等待过程一直处于阻塞状态,同理,每一个移除操作必须等到另一个线程的对应插入操作。PriorityBlockingQueue
:基于优先级的无界阻塞队列。优先级队列的元素按照其自然顺序进行排序,或者根据构造队列时提供的 Comparator 进行排序,具体取决于所使用的构造方法。
饱和策略
当有界队列被填满后,饱和策略开始发挥作用,ThreadPoolExecutor的饱和策略可以通过调用setRejectedExecutionHandler来修改。
AbortPolicy
:默认的饱和策略,该策略将抛出未检查的RejectedExecutionException
,调用者可以捕获这个异常。CallerRunsPolicy
:实现了一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退给调用者。DiscardPolicy
:当新提交的任务无法保存到队列中等待时,该策略会抛弃该任务。DiscardOldestPolicy
:该策略会抛弃下一个将被执行的任务,然后尝试提交新的任务。(如果工作队列是一个优先队列,那么该策略将导致抛弃优先级最高的任务,所以不能把该策略和优先队列放在一起使用)
线程池的处理流程
简单的例子
public class PoolTest {
public static void main(String[] args) {
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("pool-%d-thread-%d").build();
ExecutorService pool = new ThreadPoolExecutor(
4,
60,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(1024),
namedThreadFactory,
new ThreadPoolExecutor.AbortPolicy()
);
for (int i = 0; i < 5; i++) {
final int index = i;
pool.execute(() -> System.out.println(Thread.currentThread().getName() + " " + index));
}
pool.shutdown();
}
}
/*
pool-1-thread-1 0
pool-1-thread-1 4
pool-1-thread-2 1
pool-1-thread-3 2
pool-1-thread-4 3
*/
参考:Java并发实战,http://ifeve.com/java-threadpool/