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

Posted youzhibing

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了jdk中的简单并发,需要掌握相关的知识,希望对你有一定的参考价值。

前言

  开心一刻

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

技术分享图片

纯正的海豹突击队

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

  github:https://github.com/youzhibing

  码云(gitee):https://gitee.com/youzhibing

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();

    }
}
View Code

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

技术分享图片
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的方法

技术分享图片

    主要分为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;
}
View Code

    从如上代码可以看出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");
    }
}
View Code

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

技术分享图片
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获取结果;采用缓存线程池执行任务

技术分享图片
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获取结果;采用定长线程池执行定时任务

技术分享图片
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验证定时任务

  shiro源码篇 - 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,更多三方调度工具可查阅spring-boot-2.0.3之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

以上是关于jdk中的简单并发,需要掌握的主要内容,如果未能解决你的问题,请参考以下文章

什么是ForkJoin?看这一篇就能掌握!

Java并发(10)- 简单聊聊JDK中的七大阻塞队列

48个值得掌握的JavaScript代码片段(上)

并发编程Future模式及JDK中的实现

golang代码片段(摘抄)

什么是ForkJoin?看这一篇就能掌握!