使用线程池的注意事项
- 合理设置各类参数,应根据实际业务场景来设置合理的工作线程数
- 线程资源必须通过线程池提供,不允许在应用中自行显式创建线程
- 创建线程或线程池时请指定有意义的线程名称,方便出错时回溯
线程池不允许直接使用Executors,而应该通过ThreadPoolExecutor创建,这样的处理方式能更加明确线程池的运行规则,规避资源耗尽的风险。
这些方法最终都调用了ThreadPoolExecutor和ScheduledThreadPoolExecutor的构造函数,而ScheduledThreadPoolExecutor继承自ThreadPoolExecutor。
Java默认提供的线程池
Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池。
new Thread的弊端:
- 每次new Thread新建对象,性能差
- 线程缺乏统一管理,可能无限制的新建线程,相互竞争,有可能占用过多系统资源导致死机或OOM
- 缺少更多功能,如更多执行、定期执行、线程中断
而使用线程的好处:
- 重用存在的线程,减少对象创建、消亡的开销,性能佳
- 可有效控制最大并发线程数,提高系统资源利用率,同时可以避免过多资源竞争,避免阻塞
- 提供定时执行、定期执行、单线程、并发数控制等功能
我们只需要将待执行的方法放入 run 方法中,将 Runnable 接口的实现类交给线程池的
execute 方法,作为他的一个参数,比如:
Executor e = Executors.newSingleThreadExecutor(); e.execute(new Runnable(){ //匿名内部类 public void run(){ //需要执行的任务 } });
线程池原理
线程池的使用
向线程池提交任务
可以使用两个方法向线程池提交任务
execute()
用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功.通过以下代码可知execute()方法输入的任务是一个Runnable类的实例.
threadsPool.execute(new Runnable() { @Override public void run() { // TODO Auto-generated method stub } });
从运行结果可以看出,单线程池中的线程是顺序执行的。固定线程池(参数为2)中,永远最多只有两个线程并发执行。缓存线程池中,所有线程都并发执行。
第二个例子,测试单线程调度线程池和固定调度线程池。
public class ScheduledThreadPoolExam { public static void main(String[] args) { //first test for singleThreadScheduledPool ScheduledExecutorService scheduledPool = Executors.newSingleThreadScheduledExecutor(); //second test for scheduledThreadPool // ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(2); for (int i = 0; i < 5; i++) { scheduledPool.schedule(new TaskInScheduledPool(i), 0, TimeUnit.SECONDS); } scheduledPool.shutdown(); } } class TaskInScheduledPool implements Runnable { private final int id; TaskInScheduledPool(int id) { this.id = id; } @Override public void run() { try { for (int i = 0; i < 5; i++) { System.out.println("TaskInScheduledPool-["+id+"] is running phase-"+i); TimeUnit.SECONDS.sleep(1); } System.out.println("TaskInScheduledPool-["+id+"] is over"); } catch (InterruptedException e) { e.printStackTrace(); } } }
从运行结果可以看出,单线程调度线程池和单线程池类似,而固定调度线程池和固定线程池类似。
总结:
- 如果没有特殊要求,使用缓存线程池总是合适的;
- 如果只能运行一个线程,就使用单线程池。
- 如果要运行调度任务,则按需使用调度线程池或单线程调度线程池
- 如果有其他特殊要求,则可以直接使用ThreadPoolExecutor类的构造函数来创建线程池,并自己给定那五个参数。
submit()
用于提交需要返回值的任务。线程池会返回一个future类型对象,通过此对象可以判断任务是否执行成功。
并可通过get()获取返回值,get()会阻塞当前线程直到任务完成。而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候可能任务没有执行完。
Future<Object> future = executor.submit(harReturnValuetask); try { Object s = future.get(); } catch (InterruptedException e) { // 处理中断异常 } catch (ExecutionException e) { // 处理无法执行任务异常 } finally { // 关闭线程池 executor.shutdown(); }