1.线程池是什么
java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池,在开发过程中,合理地使用线程池能够带来很多好处
降低资源消耗,提高响应速度,提高线程的可管理性。
线程池是为突然大量爆发的线程设计的,通过有限的几个固定线程为大量的操作服务,减少创建和销毁线程所需的时间,从而提高效率。
2.线程池分类
ThreadPollExecutor
Executor框架的最顶层实现是ThreadPoolExecutor类,Executors工厂类中提供的newScheduledThreadPool,newFixedThreadPool,newCachedThreadPool方法其实也只是ThreadPoolExecutor的构造函数参数不同而已,通过传入不同的参数,就可以构造出不同应用场景下的线程池。
CorePoolSize:核心池的大小,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目到达corePoolSize后,就会把到达的任务放到缓存队列当中。
maximumPoolSize:线程池最大线程数,它表示在线程池中最多能创建多少个线程。
keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。
unit:参数keepAliveTime的时间单位,有7中取值,在TimeUnit类中有7中静态属性。
java通过Executors提供四中线程池
newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行。
newCachedThreadPool
public class ThreadDemo {
public static void main(String[] args) throws Exception {
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for(int i = 0; i<100; i++) {
int tem = i;
cachedThreadPool.execute(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName() + ":" + tem);
}
});
}
}
}
线程池为无限大,当执行第二个线程任务时第一个线程任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。
newFixedThreadPool
public class ThreadDemo {
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newFixedThreadPool(3);
while(true) {
executorService.execute(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName() + ":执行了。。。。。。");
}
});
}
}
}
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool
public class ThreadDemo {
public static void main(String[] args) throws Exception {
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
while(true) {
scheduledThreadPool.schedule(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName());
}
}, 3,TimeUnit.SECONDS);
}
}
}
创建一个定长线程池,支持定时及周期性任务执行,延迟3秒执行。
newSingleThreadExecutor
public class ThreadDemo {
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newSingleThreadExecutor();
for(int i=0; i<10; i++) {
int tem = i;
executorService.execute(new Runnable() {
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + tem);
}
});
}
}
}
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行。
3.线程池原理
提交一个任务到线程池中,线程池的处理流程如下:
1.判断线程池里的核心线程释放都在执行任务,如果不是则创建一个新的工作线程来执行任务,如果核心线程都在执行任务,则进入下个流程。
2.线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里,如果工作队列满了,则进入下个流程。
3.判断线程池里的线程释放都处于工作状态,如果没有,则创建一个新的工作线程来执行任务,如果已经满了,则交给饱和策略来处理这个任务。
合理配置线程池
cpu密集时,任务可以少配置线程数,大概和机器的cpu核数相当,这样可以使得美国线程都在执行任务
IO密集时,大部分线程都阻塞,故需要多配置线程数,2*cpu核数。
5.手写线程池
public class Demo {
public static void main(String[] args) {
MyThreadPool m = new MyThreadPool(5);
Runnable r = new Runnable() {
public void run() {
System.out.println("线程执行。。。。。");
}
};
for(int i =0 ;i <30; i++) {
m.execute(r);
}
}
}
class MyThreadPool {
//定义一个集合存储线程
private ArrayList<Thread> threads;
//定义一个阻塞式队列
private ArrayBlockingQueue<Runnable> arrayBlockingQueue;
//线程池最大线程数
private int maxThreadCount;
//已经工作的线程数
private int jobThreadCount;
private ReentrantLock lock = new ReentrantLock();
public MyThreadPool(int maxThreadCount) {
threads = new ArrayList<Thread>();
this.maxThreadCount = maxThreadCount;
//队列中的线程数是集合中的线程2倍
arrayBlockingQueue = new ArrayBlockingQueue<>(this.maxThreadCount * 2);
jobThreadCount = 0;
}
public void execute(Runnable runnable) {
try {
lock.lock();
//判断线程池中的线程是否已满
if(maxThreadCount > jobThreadCount) {
Thread t = new Thread(runnable);
t.start();
threads.add(t);
jobThreadCount++;
}else {
//线程池已满放入队列中
if(!arrayBlockingQueue.offer(runnable)) {
System.out.println("队列已满");
}
}
} catch (Exception e) {
}finally {
lock.unlock();
}
}
}
6.java锁的深度化
悲观锁:悲观锁悲观的认为每一次操作都会造成更新丢失问题,在每次查询时加上排他锁。
乐观锁:乐观锁会乐观的认为每次查询都不会造成更新丢失,利用版本字段控制。
重入锁:也叫递归锁,指的是同一线程,外层函数获取锁之后,内层递归函数依然有获取该锁的代码。
读写锁:两个线程同时读一个资源没有任何问题,所有应该允许多个线程能在同时读取共享资源,但是如果有一个线程想去写这些共享资源,就不应该有其他线程对该资源进行读或写。
CAS无锁机制:它包含三个参数CAS(v,e,n) V表示要感谢的变量,E表示预期值,N表示新值。仅当V值等于E值时,才会将V的值设为N,如果V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做,最后,CAS返回当前V的真实值。
自旋锁:自旋锁是采用让当前线程不停地在循环体内执行实现的,当循环的条件被其他线程改变时,才能进入临界区。