一、线程池
1、为何创建线程池?
答:创建、启动、销毁单个线程都是非常消耗时间的,使用线程池进行管理和复用线程,提高程序效率。
Java线程池好处:
1、降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
2、提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
3、提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性。
4、使用线程池实现统一分配、调优、监控,但要合理利用。
Java使用线程核心走的是ThreadPoolExecutor。(构造函数)
二、线程池使用方式
使用Executor封装好的四中线程池类型:
1、newCachodThreadPool:创建缓存线程池,如果线程池长度超过处理需要的长度,可灵活回收空闲线程,若无可回收,则新建线程,线程池的规模不存在任何限制。
特点:基本线程数为0,最大线程数为Integer.MAX_VALUE.存活时间60s,采用异步队列SynchronousQueue来避免任务排队。
2、newFixedThreadPool:创建定长的线程池。每当提交一个任务时就创建一个线程,直到达到线程池的最大数量,可控制线程最大并发数,超出的线程会在队列中等待。
特点:线程数量固定,线程处于空闲状态时,它们并不会被回收,除非线程池被关闭。当所有线程都处于活动状态时,新任务都会处于等待状态,直到有线程空闲出来。基本线程数等于最大线程数,没有超时机制,使用无界的队列保存等待执行的任务。
3、newScheduledThreadPool:创建定长的线程池,而且以延迟或定时的方式来执行任务,类似于Timer。
4、newSingleThreadExecutor 创建单线程化的线程池,它只会用唯一的工作线程来执行任务,如果这个线程异常结束,会创建另一个线程来替代。它能确保依照任务在对列中的顺序来串行执行(例如FIFO,LIFO,优先级)。
特点:基本线程数和最大线程数都为1,无存活时间,采用无界的.
三、线程池代码示例
package org.sw.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
*
*
* @Description: TODO
* @author liangsw
* @date 2019-07-23
* @version 1.0v
*/
public class ThreadPoolDemo {
public static void main(String[] args) {
// createNewCachedThreadPool();
// createNewFixedThreadPool();
// createNewScheduledThreadPool();
createNewSingleThreadExecutor();
}
/**
* 1 newCachedThreadPool 创建缓存线程池
*
* @Description: TODO
* @author liangsw
* @date 2019-07-23
*/
public static void createNewCachedThreadPool() {
// 创建缓存线程池,实现重复利用;
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
int temp = i;
newCachedThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println("ThreadName:" + Thread.currentThread().getName() + " i = " + temp);
}
});
}
/* 停止线程池运行*/
newCachedThreadPool.shutdown();
}
/**
* 执行结果:程序是创建10个线程,这里就创建了7个,说明有3个在复用
* ThreadName:pool-1-thread-2 i = 1
* ThreadName:pool-1-thread-3 i = 2
* ThreadName:pool-1-thread-4 i = 3
* ThreadName:pool-1-thread-1 i = 0
* ThreadName:pool-1-thread-6 i = 5
* ThreadName:pool-1-thread-5 i = 4
* ThreadName:pool-1-thread-7 i = 6
* ThreadName:pool-1-thread-5 i = 9
* ThreadName:pool-1-thread-2 i = 7
* ThreadName:pool-1-thread-7 i = 8
*/
/**
* newFixedThreadPool:创建固定长度的线程池
*
* @Description: TODO
* @author liangsw
* @date 2019-07-23
*/
public static void createNewFixedThreadPool() {
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
int temp = i;
newFixedThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println("ThreadName:" + Thread.currentThread().getName() + " i = " + temp);
}
});
}
/* 停止线程池运行*/
newFixedThreadPool.shutdown();
}
/**
* 执行结果:线程池定长为3;
* ThreadName:pool-1-thread-1 i = 0
* ThreadName:pool-1-thread-2 i = 1
* ThreadName:pool-1-thread-1 i = 3
* ThreadName:pool-1-thread-1 i = 5
* ThreadName:pool-1-thread-2 i = 4
* ThreadName:pool-1-thread-2 i = 7
* ThreadName:pool-1-thread-2 i = 8
* ThreadName:pool-1-thread-2 i = 9
* ThreadName:pool-1-thread-1 i = 6
* ThreadName:pool-1-thread-3 i = 2
*
*/
/**
* newScheduledThreadPool:创建可以定时执行的线程池
* @Description: TODO
* @author liangsw
* @date 2019-07-23
*/
public static void createNewScheduledThreadPool() {
/* 参数:核心线程数 */
ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(5);
for (int i = 0; i < 10; i++) {
int temp = i;
/**
* 参数1:线程实例
* 参数2:定时时间长度
* 参数3:定时时间单位
*/
newScheduledThreadPool.schedule(new Runnable() {
@Override
public void run() {
System.out.println("ThreadName:" + Thread.currentThread().getName() + " i = " + temp);
}
}, 3, TimeUnit.SECONDS);
}
/* 停止线程池运行*/
newScheduledThreadPool.shutdown();
}
/**
* 执行效果 :
* ThreadName:pool-1-thread-1 i = 0
* ThreadName:pool-1-thread-3 i = 2
* ThreadName:pool-1-thread-4 i = 4
* ThreadName:pool-1-thread-4 i = 6
* ThreadName:pool-1-thread-4 i = 7
* ThreadName:pool-1-thread-4 i = 8
* ThreadName:pool-1-thread-4 i = 9
* ThreadName:pool-1-thread-1 i = 3
* ThreadName:pool-1-thread-2 i = 1
* ThreadName:pool-1-thread-3 i = 5
*/
/**
* newSingleThreadExecutor:单线程池
*
* @Description: TODO
* @author liangsw
* @date 2019-07-23
*/
public static void createNewSingleThreadExecutor() {
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
int temp = i;
newSingleThreadExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println("ThreadName:" + Thread.currentThread().getName() + " i = " + temp);
}
});
}
/* 停止线程池运行*/
newSingleThreadExecutor.shutdown();
}
/**
* 执行效果:
* ThreadName:pool-1-thread-1 i = 0
* ThreadName:pool-1-thread-1 i = 1
* ThreadName:pool-1-thread-1 i = 2
* ThreadName:pool-1-thread-1 i = 3
* ThreadName:pool-1-thread-1 i = 4
* ThreadName:pool-1-thread-1 i = 5
* ThreadName:pool-1-thread-1 i = 6
* ThreadName:pool-1-thread-1 i = 7
* ThreadName:pool-1-thread-1 i = 8
* ThreadName:pool-1-thread-1 i = 9
*/
}
***********根据实际情况注释shutdown()方法;*************
四、线程池原理
corePoolSize:核心线程池、maximumPoolSize:最大线程数、workQueue:阻塞队列,存储等待执行的任务
三个参数间的关系:
1、当前运行线程数小于corePoolSize时,直接创建新线程处理任务,即使其他线程空闲;
2、当corePoolSize <= 当前线程数 <= maximunPoolSize,只有当workQueue满的时候才创建新的线程处理任务。
3、当corePoolSize = maximunPoolSize时,创建的线程池大小是固定的,如果有新任务提交,当workQueue没满的时候,将请求放入workQueue,等待线程空闲后去取出任务进行处理,如果装满,还有新任务提交,则通过拒绝策略参数来指定策略去处理任务。
4、workQueue是线程池中保存等待执行的任务的阻塞队列,当提交新任务到线程池后,线程池会根据当前线程池中正在运行的线程数量决定该任务的处理方式,总共三种:直接切换,用无界队列,用有界队列。
-- 直接队列:这种方式常用的队列是SynchronousQueue;
-- 无界队列:一般使用基于链表的阻塞队列LinkBlockingQueue。若使用此方式,那么线程池中能够创建的最大线程数就是corePoolSize,而 maximunPoolSize就不会起作用。当线程池中所有的核心线程都是Running状态时,提交新任务就会放入等待队列内。
-- 有界队列:一般使用ArrayBlockingQueue。使用该方式可以将线程池的最大线程数量限制为maximunPoolSize,这样能够降低资源的消耗,但同时此方式使得线程池对线程的调度变得更加困难,因为线程池和队列的容量都是有限的值,所以要想使线程池处理任务的吞吐率达到一个相对合理的范围,又想使线程调度相对简单,并且还要尽可能的降低线程池对资源的消耗,就需要合理的设置这两个数量。
如果要想降低系统资源的消耗(包括CPU的使用率,操作系统资源的消耗,上下文环境切换的开销等),可以设置较大的队列容量和较小的线程池容量,但这样也会降低线程处理任务的吞吐量。
如果提交的任务经常发生堵塞,那么可以考虑通过调用setMaximunPoolSize()方法来重新设定线程池容量。如果队列的容量设置的太小,通常需要将线程池的容量设置大一点,这样CPU的使用率会相对的高一鞋。但如果线程池的容量设置的过大,则在提交的任务数量太多的情况下,并发量会增加,那么线程之间的调度就是一个要考虑的问题,因为这样反而有可能降低处理任务的吞吐量。
拒绝策略:1.(默认策略)直接抛出异常。2.让线程抛出异常。3.抛弃队列中最早的任务。4.抛弃当前任务。