本篇来看下java线程池相关技术的实现和使用方式。
0x01 线程的实现
一开始我们想要实现多线程最通常的做法是:
new Thread(new Runnable() { public void run() { System.out.println("raw thread"); }}).start();
这种方式,这种实现方式也没有什么不好,只是如果线程一多的话不好对所有的线程进行统一管理。然后java有了线程池技术,我们可以通过线程池技术来替换实现上面的方式。
0x02 线程池
ExecutorService executorPool = Executors.newCachedThreadPool();Futurefuture = executorPool.submit(new Callable () { public String call() throws Exception { return "future finish"; }});try { System.out.println(future.get());} catch (Exception e) { e.printStackTrace();}
Executors有如下几种方式创建线程:
newCachedThreadPool
newFixedThreadPool
newScheduledThreadPool
上面三种方式最终都是调用ThreadPoolExecutor的构造函数进行线程池的创建,只是传入的参数不一样,而实现不同的线程池对象。
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueueworkQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
corePoolSize:创建线程池时创建多少个线程。
maximumPoolSize:这个线程池中对多能有多少个线程。
keepAliveTime:当线程数量超过corePoolSize时,多余的空闲线程最大的存活时间。也就是说多余的线程在keepAliveTime时间还是没有处理任何的任务将会被终止。
unit:时间单位
workQueue:这个线程池中线程处理任务的的任务队列。
threadFactory:创建新线程的线程工厂。
handler:当线程数量达到maximumPoolSize,对新加入的任务的处理策略。一般很少使用这个参数基本都采用默认的handler。
上面的例子中我们向线程池中提交了一个Callable,并接受一个返回值Future。Callable可能会是一个非常耗时的操作但是使用方有不想阻塞等待其返回再继续执行,这时Callable执行完后会将结果放到Future中,使用方可以在需要的时候去判断是否Callable已经执行完成,如果完成就可以通过Future拿到其返回值。
0x03 ScheduledExecutorService
任务定时调度线程的使用:
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); scheduledExecutorService.scheduleAtFixedRate(new Runnable() { public void run() { System.out.println("schedule task with fixed rate:" + System.currentTimeMillis()); } }, 2000, 1000, TimeUnit.MILLISECONDS); scheduledExecutorService.scheduleWithFixedDelay(new Runnable() { public void run() { System.out.println("schedule task with fixed delay:" + System.currentTimeMillis()); int count = Integer.MAX_VALUE; while (count-- > 0){ } System.out.println("time:" + System.currentTimeMillis()); } }, 2000, 1000, TimeUnit.MILLISECONDS);
ScheduledExecutorService有两种定时调度的方式:
scheduleAtFixedRate:以固定速率进行调度,意思是任何两个被调度的任务之间的时间间隔是固定的。第二个任务的调度时间(开始执行时间)= 第一个任务的调度时间 + 间隔时间
scheduleWithFixedDelay:第二个任务的调度时间 = 第一个任务的调度时间 + 第一个任务的执行时间 + 间隔时间
0x04 Timer定时任务
上面介绍了ScheduledExecutorService来做定时任务,在编程的过程中还可以使用Timer来做定时任务,代码如下:
timer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { try { doSomething(); } catch (Exception e) { System.out.println("timer excute exception", e); } } }, 1000 * 3, 1000);
其第一参数是一个TimerTask,第二第三个参数scheduledExecutorService.scheduleAtFixedRate
这个调用的第二三个参数一致。
0x05 参考
我觉得最好的参考还是阅读相关源码去理解Executor的使用方式,这样自己才能理解的比较深入同时做到活学活用。
线程池的的核心实现ThreadPoolExecutor,想了解更多还是自己去look look源码吧。