为什么使用线程池
线程池作用就是通过限制系统中执行线程的数量从而达到优化资源分配的目的。
控制执行线程的数量
假如现在有一个工作台,上面只能有5个人作业.
使用锁
Java引入了
Semaphore
的一个类,称为信号量
.可以指定开放多少把锁
事例代码
public class WorkPlace {
private static Semaphore semaphore = new Semaphore(5);
public static void main(String[] args) throws Exception {
/**
* 模拟10个工人去申请使用工作台作业
*/
for (int i = 0; i < 10; i++) {
new Thread() {
public void run() {
work();
};
}.start();
}
}
/*
* 工作台的作业情景,每次只能有5个工人在上面作业
*/
public static void work() {
try {
semaphore.acquire(); //要工作了,获取一把锁
System.out.println(Thread.currentThread().getName() + "正在工作!");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "工作完毕!");
semaphore.release(); //工作完毕,释放这把锁
}
}
这样就保证了每次最多有5个工人可以在上面作业
使用线程池实现
Java线程池类型
newSingleThreadExecutor
线程池中只有一个线程执行,相当于单线程.但是如果线程挂了之后(异常结束等),会重新启动一个线程替原来的线程执行下去
newFixedThreadPool
线程池的大小是固定的.每次提交一个任务就会创建一个线程,直到线程达到线程池的最大限制,里面有一个
任务队列
去维护未执行的任务,当有空闲的线程
的时候就会到任务队列里面去取.
newCachedThreadPool
线程池中的线程是可缓存的.如果线程池的大小超过所需执行任务的大小,则系统会回收
空闲线程
(60秒不执行任务).当任务数增加时,线程池又可以自动添加新线程来处理任务.并且线程池不做大小的限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
- newScheduledThreadPool
线程池不限大小,并且支持任务
定时
与周期性循环
事例代码
public class WorkPlace {
private static Executor executor = Executors.newFixedThreadPool(5);
// private static Executor executor = Executors.newScheduledThreadPool(5);//也可以
public static void main(String[] args) throws Exception {
/**
* 模拟100个工人去申请使用工作台作业
*/
for (int i = 0; i < 100; i++) {
executor.execute(new Runnable() {
@Override
public void run() {
work();
}
});
}
}
/*
* 工作台的作业情景,每次只能有5个工人在上面作业
*/
public static void work() {
try {
System.out.println(Thread.currentThread().getName() + "正在工作!");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "工作完毕!");
}
}
简单剖析Java的线程池类型
通过对方法的追踪,我们可以看到上面的四种线程池都是来自同一个东西
ThreadPoolExecutor
那我们能不能尝试使用ThreadPoolExecutor
这个类来实现线程池呢?下面继续...
ThreadPoolExecutor构造器参数
我们通过查看
ThreadPoolExecutor
类,可以看到有几个主要的参数.
-
corePoolSize
//核心线程数量 -
maximumPoolSize
//最大线程数量 -
keepAliveTime
//空闲线程存活时间 -
unit
//keepAliveTime的单位(时分秒等) -
workQueue
//任务队列,用来存放将要执行的任务(Runnable类型) -
threadFactory
//产生线程的方式 -
handler
//当线程池不能接受任务时抛出的异常由该对象处理
错综复杂的corePoolSize maximumPoolSize workQueue关系
- 当提交任务时当前启动的线程总数小于
corePoolSize
的时候,每次提交一个任务,都会新建一个线程.- 当提交任务时当前启动的线程总数大于等于
corePoolSize
的时候,就会把任务扔到workQueue
里面去,然后新建
或者复用
线程,并且保证启动的线程总数小于等于maximumPoolSize
.- 每当一个任务结束之后,线程池里面的线程就会从
workQueue
中取.
maximumPoolSize = 核心线程数量 + 非核心线程数量
也容易看出,提交的任务总数应该小于maximumPoolSize + workQueue的大小
例如:maximumPoolSize = 10,workQueue的大小 = 20
当提交31个任务的时候,所有线程10个全开.然后还有21个任务需要进入任务队列里面等待被取走执行,但是任务队列的大小只有20,装不下21.这时候就会抛出RejectedExecutionHandler
的异常
事例代码
public class WorkPlace {
private static AtomicInteger count = new AtomicInteger(0);
private static LinkedBlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(5);
private static Executor executor = new ThreadPoolExecutor(5, 5, 1, TimeUnit.SECONDS,
workQueue, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("线程" + count.getAndIncrement());
return thread;
}
});
public static void main(String[] args) throws Exception {
/**
* 模拟10个工人去申请使用工作台作业
*/
for (int i = 0; i < 10; i++) {
executor.execute(new Runnable() {
@Override
public void run() {
work();
}
});
}
}
/*
* 工作台的作业情景,每次只能有5个工人在上面作业
*/
public static void work() {
try {
System.out.println(Thread.currentThread().getName() + "正在工作!");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "工作完毕!");
}
}
同样,上面的代码也能够实现前面的效果
当我们把10个工人修改成11个工人的时候,程序就抛出RejectedExecutionHandler
异常了
AtomicInteger
是int
的另一个包装类,不过是线程安全的.因为我们不清楚同时会有多少个对象去调用ThreadFactory
的newThread
方法,所以为了保证编号的正确性,所以使用AtomicInteger
代替int