Java线程<第五篇>:线程池
utils包提供开了 ExecutorService 线程池的实现,主要目的是为了重复利用线程,提高系统效率。
Thread是一个重量级的资源,创建、启动以及销毁都是比较耗费系统资源的,因此使用线程池来管理线程是一个非常重要的编程习惯。
1、Thread
new Thread(new Runnable() {
@Override
public void run() {
}
}).start();
直接使用 Thread 的弊端如下:
- 每次new Thread新建对象性能差。
- 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
- 缺乏更多功能,如定时执行、定期执行、线程中断。
2、线程池(ExecutorService、ThreadPool)
(1)newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int index = i;
try {
Thread.sleep(index * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(index);
}
});
}
cachedThreadPool.shutdown();
线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。
(2)newFixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
ExecutorService cachedThreadPool = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
final int index = i;
try {
Thread.sleep(index * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(index);
}
});
}
cachedThreadPool.shutdown();
这里支持的最大线程数是5, 也可以根据系统而定,获取系统可被利用的进程数
Runtime.getRuntime().availableProcessors()
(3)newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。
定义线程池,最大线程数是5
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
延迟执行:
scheduledThreadPool.schedule(new Runnable() {
@Override
public void run() {
System.out.println("delay");
}
}, 3, TimeUnit.SECONDS);
延迟1秒,并每隔3秒定期执行
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("delay 1 seconds, and excute every 3 seconds");
}
}, 1, 3, TimeUnit.SECONDS);
关于延迟执行和周期性执行我们还会想到Timer
Timer timer = new Timer();
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
}
};
timer.schedule(timerTask, 1000, 3000);
(4)newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int index = i;
singleThreadExecutor.execute(new Runnable() {
@Override
public void run() {
try {
System.out.println(index);
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
Java提供的四种线程池的优点
- 重用存在的线程,减少对象创建、消亡的开销,性能佳。
- 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
- 提供定时执行、定期执行、单线程、并发数控制等功能。
(5)自定义线程池
如果我们不想使用以上4种线程池,可以自定义一个线程池:
/**
* 线程池
*/
public class DefaultPoolExecutor {
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
@Override
public Thread newThread(Runnable runnable) {
// 线程
return new Thread(runnable, "ThreadName #" + mCount.getAndIncrement());
}
};
// 可用处理器的Java虚拟机的数量
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
// 最大线程数(最佳线程数 = CPU_COUNT + 1)
private static final int MAX_CORE_POOL_SIZE = CPU_COUNT + 1;
//空闲时间达到 30s 时,回收空闲线程(每隔30s回收一次)
private static final long THREAD_TIMEOUT = 30L;
/**
* 新建一个线程池
* 每个线程都会消耗大概1M的内存,使用线程池管理和复用线程
*
* @param corePoolSize 线程池大小
* @return
*/
public static ThreadPoolExecutor newDefaultPoolExecutor(int corePoolSize) {
if (corePoolSize == 0) {
return null;
}
corePoolSize = Math.min(corePoolSize, MAX_CORE_POOL_SIZE);
int maximumPoolSize = corePoolSize;
// corePoolSize: 当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程
// 当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行
// 当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务
// 当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理
// 当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程
// 当设置allowCoreThreadTimeOut(true)时,线程池中线程空闲时间达到keepAliveTime也将关闭
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, THREAD_TIMEOUT,
TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(64), sThreadFactory);
threadPoolExecutor.allowCoreThreadTimeOut(true);
return threadPoolExecutor;
}
}
扩充:
除了execute可以执行线程池中的线程之外,submit也是可以的
图片.png
submit的效果和execute是一样的,只是execute没有返回值,而submit有返回值。
(1)Future<?> submit(Runnable task)
Future future1 = singleThreadExecutor.submit(new Runnable() {
@Override
public void run() {
}
});
(2)<T> Future<T> submit(Runnable task, T result)
Future<String> future = singleThreadExecutor.submit(new Runnable() {
@Override
public void run() {
}
}, "A");
(3)<T> Future<T> submit(Callable<T> task);
Callable callable = new Callable<String>() {
@Override
public String call() throws Exception {
return "A";
}
};
Future<String> future = singleThreadExecutor.submit(callable);
如果Future中有值的话可以通过以下代码获取
try {
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
Future的其余操作如图
图片.png
[本章完…]
© 著作权归作者所有,转载或内容合作请联系作者
没有回复内容