欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

jdk中的简单并发,需要掌握

程序员文章站 2022-07-26 10:35:59
前言 开心一刻 小时候有一次爸爸带我去偷村头别人家的梨子,我上树摘,爸爸在下面放风,正摘着主人来了,爸爸指着我破口大骂:臭小子,赶紧给我滚下来,敢偷吃别人家梨子,看我不打死你。主人家赶紧说:没事没事,小孩子淘气嘛,多摘点回家吃。我……这坑儿子的爹... 纯正的海豹突击队 路漫漫其修远兮,吾将上下而求 ......

前言

  开心一刻

    小时候有一次爸爸带我去偷村头别人家的梨子,我上树摘,爸爸在下面放风,正摘着主人来了,爸爸指着我破口大骂:臭小子,赶紧给我滚下来,敢偷吃别人家梨子,看我不打死你。主人家赶紧说:没事没事,小孩子淘气嘛,多摘点回家吃。我……这坑儿子的爹...

jdk中的简单并发,需要掌握

纯正的海豹突击队

  路漫漫其修远兮,吾将上下而求索!

  github:

  码云(gitee):

runnable

  如果是简单的实现一个线程,我们会通过实现runnable接口或继承thread类来完成。jdk1.0中就已经存在runnable和thread,thread实现了runnable接口。runnable使用方式一般如下

jdk中的简单并发,需要掌握
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();

    }
}
view code

  一般我们的线程不是以匿名内部类的方式存在的,而是以如下方式存在

jdk中的简单并发,需要掌握
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");
    }
}
view code

  当然线程的实现方式还有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的方法

jdk中的简单并发,需要掌握

    主要分为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中对线程池定义的一个接口,类图如下:

jdk中的简单并发,需要掌握

    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方法获取执行结果,该方法会阻塞直到任务返回结果。相关类图如下

jdk中的简单并发,需要掌握

jdk中的简单并发,需要掌握
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;
}
view code

    从如上代码可以看出future提供了三种功能:

      1、判断任务是否完成;2、中断任务;3、获取任务执行结果。

线程池使用示例

  runnable使用示例

    示例一,定时周期的执行某个任务

jdk中的简单并发,需要掌握
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");
    }
}
view code

    示例二,单线程化的线程池执行某个任务,并显示的关闭线程池

jdk中的简单并发,需要掌握
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");
    }
}
view code

  callable使用示例

    示例一,callable + future获取结果;采用缓存线程池执行任务

jdk中的简单并发,需要掌握
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 "业务执行成功";
    }
}
view code

    示例二,callable + futuretask获取结果;采用定长线程池执行定时任务

jdk中的简单并发,需要掌握
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 "业务执行成功";
    }
}
view code

shiro中session验证定时任务

  中讲到了session验证定时任务,我们abstractvalidatingsessionmanager中createsession方法开始

jdk中的简单并发,需要掌握

  可以看到,调用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中的线程还有很多内容,本文只是涉及到了冰山一角,更深入的学习有待大家自行去进行。

参考

  java 8 教程汇总

  java并发编程:callable、future和futuretask

  深入理解 java 线程池:threadpoolexecutor