jdk中的简单并发,需要掌握
前言
开心一刻
小时候有一次爸爸带我去偷村头别人家的梨子,我上树摘,爸爸在下面放风,正摘着主人来了,爸爸指着我破口大骂:臭小子,赶紧给我滚下来,敢偷吃别人家梨子,看我不打死你。主人家赶紧说:没事没事,小孩子淘气嘛,多摘点回家吃。我……这坑儿子的爹...
纯正的海豹突击队
路漫漫其修远兮,吾将上下而求索!
github:
码云(gitee):
runnable
如果是简单的实现一个线程,我们会通过实现runnable接口或继承thread类来完成。jdk1.0中就已经存在runnable和thread,thread实现了runnable接口。runnable使用方式一般如下
public class runnabletest { public static void main(string[] args) { // java 8之前: new thread(new runnable() { @override public void run() { system.out.println("before java8, 我是子线程1"); } }).start(); //java 8方式: new thread( () -> { system.out.println("in java8"); system.out.println("我是子线程2"); } ).start(); } }
一般我们的线程不是以匿名内部类的方式存在的,而是以如下方式存在
public class runnabletest { public static void main(string[] args) throws interruptedexception { runnable myrunnable = new myrunnable(); new thread(myrunnable).start(); thread.sleep(1000); system.out.println("我是主线程"); } } class myrunnable implements runnable { @override public void run() { system.out.println("我是子线程1"); } }
当然线程的实现方式还有thread类,thread实现了runnable接口,本质还是一样;无论是runnable,还是thread,实现的线程有一个很明显的缺点,就是没有返回值,执行完任务之后无法获取执行结果。
callable
callable接口是jdk1.5中引入的,和runnable类似,都是用来实现多线程,不同的是,callable能返回结果和抛出checked exception。源代码如下
@functionalinterface public interface callable<v> { /** * computes a result, or throws an exception if unable to do so. * * @return computed result * @throws exception if unable to compute a result */ v call() throws exception; }
可以看到,callable是一个泛型接口,call()函数返回的类型就是传递进来的泛型类型,也是返回的结果类型。那么怎么使用callable呢?一般情况下是配合executorservice来使用的,而executorservice的创建又是用executors来完成的。
线程池
executors
也是jdk1.5新增内容,是创建executorservice、scheduledexecutorservice、threadfactory和callable的工厂,并提供了一些有效的工具方法。有很多创建executorservice的方法
主要分为6类方法,每一类都两两重载,一个有threadfactory threadfactory参数,一个没有threadfactory threadfactory参数,也就是我们可以自定义threadfactory来定制thread;若没有threadfactory参数,则使用默认的defaultthreadfactory来构建thread。6类方法如下
newcachedthreadpool(...)
创建一个可缓存的线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程;返回类型是:threadpoolexecutor。
newfixedthreadpool(...)
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待;返回类型是:threadpoolexecutor。
newscheduledthreadpool(...)
创建一个定长线程池,支持定时及周期性任务执行;返回类型是:scheduledthreadpoolexecutor。多数情况下可用来替代timer类。
newsinglethreadexecutor(...)
创建一个单线程化的线程池,只有唯一的一个工作线程来执行任务,保证所有任务按照指定顺序执行;返回类型是:threadpoolexecutor的代理,我们可以认为就是threadpoolexecutor。
newsinglethreadscheduledexcutor(...)
创建一个单线程化的线程池,与newsinglethreadexecutor类似,但支持定时及周期性任务执行;返回类型是:scheduledthreadpoolexecutor。
newworkstealingpool(...)
创建持有足够线程的线程池来支持给定的并行级别,并通过使用多个队列,减少竞争;它需要穿一个并行级别的参数,如果不传,则被设定为默认的cpu数量。jdk1.8中新增,返回类型是:forkjoinpool。forkjoinfool通常配合forkjointask的子类recursiveaction或recursivetask使用。
常用的主要是以下3类:newcachedthreadpool,newfixedthreadpool,newscheduledthreadpool。至于newworkstealingpool,我还没用过,不太好评论。
executorservice
executorservice是一个interface,继承了executor,是java中对线程池定义的一个接口,类图如下:
executorservice接口中常用方法如下
void execute(runnable command); // 从executor继承而来,用来执行runnale,没有返回值 <t> future<t> submit(callable<t> task); // 执行callable类型的task,并返回future <t> future<t> submit(runnable task, t result); // 这种方式很少使用 future<?> submit(runnable task); // 执行runnable类型的task,并返回future
当然还有invokeall、invokeany,感兴趣的可以去看下。关于future,下面会讲到。
当我们使用完成executorservice之后应该关闭它,否则它里面的线程会一直处于运行状态,导致应用无法停止。关闭executorservice的方式有两种,其一是executorservice.shutdown()方法,在调用shutdown()方法之后,executorservice不会立即关闭,但是它不再接收新的任务,直到当前所有线程执行完成才会关闭,所有在shutdown()执行之前提交的任务都会被执行;其二是调用executorservice.shutdownnow()方法,它将跳过所有正在执行的任务和被提交还没有执行的任务,但是它并不对正在执行的任务做任何保证,有可能它们都会停止,也有可能执行完成。一般推荐的关闭方式是executorservice.shutdown()。
future
对具体的runnable或者callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。相关类图如下
public interface future<v> { /** * 取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false * @param mayinterruptifrunning 是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。 * 如果任务已经完成,则无论mayinterruptifrunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false。 * 如果任务正在执行,若mayinterruptifrunning设置为true,则返回true,若mayinterruptifrunning设置为false,则返回false。 * 如果任务还没有执行,则无论mayinterruptifrunning为true还是false,肯定返回true。 * @return */ boolean cancel(boolean mayinterruptifrunning); /** * 任务是否被取消成功,如果在任务正常完成前被取消成功,则返回true。 * @return */ boolean iscancelled(); /** * 任务是否已经完成,若完成则返回true。 * @return */ boolean isdone(); /** * 获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回。 * @return * @throws interruptedexception * @throws executionexception */ v get() throws interruptedexception, executionexception; /** * 获取执行结果,如果在指定时间内没获取到结果,就直接返回null * @param timeout * @param unit * @return * @throws interruptedexception * @throws executionexception * @throws timeoutexception */ v get(long timeout, timeunit unit) throws interruptedexception, executionexception, timeoutexception; }
从如上代码可以看出future提供了三种功能:
1、判断任务是否完成;2、中断任务;3、获取任务执行结果。
线程池使用示例
runnable使用示例
示例一,定时周期的执行某个任务
import java.util.concurrent.executors; import java.util.concurrent.scheduledexecutorservice; import java.util.concurrent.timeunit; public class runnabletest { public static void main(string[] args) { scheduledexecutorservice executorservice = executors.newscheduledthreadpool(5); myrunnable myrunnable = new myrunnable(); // 应用启动3秒开始执行myrunnable,之后每隔5秒执行一次; scheduleatfixedrate是有返回值的,配合runnable的话,我们不关注返回值 executorservice.scheduleatfixedrate(myrunnable, 3, 5, timeunit.seconds); system.out.println("我是主线程..."); } } class myrunnable implements runnable { @override public void run() { try { thread.sleep(1000); } catch (interruptedexception e) { e.printstacktrace(); } system.out.println("我是子线程1"); } }
示例二,单线程化的线程池执行某个任务,并显示的关闭线程池
public class runnabletest { public static void main(string[] args) throws interruptedexception, executionexception, timeoutexception { executorservice executorservice = executors.newsinglethreadexecutor(); myrunnable myrunnable = new myrunnable(); // 为了可取消性而使用future但又不提供可用的结果,则可以声明 future<?> 形式类型、并返回null作为底层任务的结果 future<?> submit = executorservice.submit(myrunnable); // 如果不shutdown,那它里面的线程会一直处于运行状态,应用不会停止 executorservice.shutdown(); // 输出任务执行结果,由于runnable没有返回值,所以get的是null system.out.println(submit.get(4, timeunit.seconds)); system.out.println("我是主线程..."); } } class myrunnable implements runnable { @override public void run() { try { thread.sleep(1000); } catch (interruptedexception e) { e.printstacktrace(); } system.out.println("我是子线程1"); } }
callable使用示例
示例一,callable + future获取结果;采用缓存线程池执行任务
import java.util.concurrent.*; import java.util.concurrent.future; public class callabletest { public static void main(string[] args) throws interruptedexception, executionexception { executorservice executorservice = executors.newcachedthreadpool(); task task = new task(); future<string> result = executorservice.submit(task); executorservice.shutdown(); thread.sleep(1000); system.out.println("我是主线程, 执行另外的业务..."); system.out.println("task执行结果:" + result.get()); system.out.println("任务全部执行完毕..."); } } class task implements callable<string> { @override public string call() throws exception { system.out.println("子线程, 业务处理中..."); thread.sleep(2000); return "业务执行成功"; } }
示例二,callable + futuretask获取结果;采用定长线程池执行定时任务
import java.util.concurrent.*; public class callabletest { public static void main(string[] args) throws interruptedexception, executionexception, timeoutexception { executorservice executorservice = executors.newcachedthreadpool(); task task = new task(); futuretask<string> futuretask = new futuretask<>(task); executorservice.submit(futuretask); executorservice.shutdown(); thread.sleep(1000); system.out.println("我是主线程, 执行另外的业务..."); system.out.println("task执行结果:" + futuretask.get(5, timeunit.seconds)); system.out.println("任务全部执行完毕..."); } } class task implements callable<string> { @override public string call() throws exception { system.out.println("子线程, 业务处理中..."); thread.sleep(2000); return "业务执行成功"; } }
shiro中session验证定时任务
中讲到了session验证定时任务,我们abstractvalidatingsessionmanager中createsession方法开始
可以看到,调用executors.newsinglethreadscheduledexcutor(threadfactory threadfactory)方法创建了一个支持定时及周期性执行的单线程化线程池,支持定时及周期性地执行task,并且线程池中只有一个线程。executorservicesessionvalidationscheduler本身就是一个runnable,那么会定时、周期性的执行其run()。说的简单点就是:应用启动60分钟后,单线程化的线程池中的单个线程开始执行executorservicesessionvalidationscheduler的run()方法,之后每隔60分钟执行一次,60分钟是默认设置;executorservicesessionvalidationscheduler的run()中,会调用sessionmanager的validatesessions()方法完成session的验证。
总结
1、无需返回结果,简单的线程实现可以用runnable(或thread);需要返回结果的、稍复杂的线程实现可以用callable;如果线程操作频繁、需要连接池管理的可以考虑用executorservice来实现线程池;更复杂的任务调度,则可以用三方工具,比如:quartz,更多三方调度工具可查阅,具体选择哪个,需要结合我们的具体业务来考虑,没有绝对的选择谁而不选择谁,就看谁更契合;
2、一般情况下,callable(或runnale)、executors、executorservice、future会配合来使用,很多时候我们不需要返回值,则可以不关注future;推荐使用线程池的方式,有与数据库连接池类似的优点;
3、很多三方的框架、工具都沿用了jdk的线程池实现,而没有引用第三方调度工具,例如shiro中,session的验证定时任务就是沿用的jdk中的executors.newsinglethreadscheduledexcutor(threadfactory threadfactory)来创建的线程池;
4、jdk中的线程还有很多内容,本文只是涉及到了冰山一角,更深入的学习有待大家自行去进行。