Java 多线程编程 (壹)

core-java
标签: #<Tag:0x00007f1d26e08b30>

#1

这篇文章主要介绍如何新建和管理线程类. 没有线程同步相关的内容.

如何编写线程类?

有三种方法可以实现一个线程类

通过继承 Thread 类

  • 不推荐这种这种方法, 因为 java 只能继承一个 class, 如果继承了 Thread 这样的纯功能性类, 业务逻辑的实现可能需要做一些妥协.
private static class Thread1 extends Thread {
    @Override
    public void run() {
        System.out.println("Thread 1 is running.");
    }
}
new Thread1().start();

通过实现 Runnable 接口

  • 实现了 Runnab 接口的类并不能将直接运行, 它的实例并不是一个线程对象
  • 需要把它的实例传入一个线程对象来执行他的 run 方法
private static class Thread2 implements Runnable {
    @Override
    public void run() {
        System.out.println("Thread 2 is running.");
    }
}
new Thread(new Thread2()).start();

因为 Runnable 是@FunctionalInterface的, 所以可以通过 Lambda 表达式简化

new Thread(() -> {
    System.out.println("Thread 3 is running.");
}).start();

通过实现Callable接口,

  • Callable可以实现带有返回值的线程, 也就是说新建的线程执行完成后, 会返回一个指定类型的返回值给当前线程. 是一种简单的线程间通信的实现.
  • 以下这种实现方式略有些复杂, 需要一个 Callable 实例, 一个FutureTask实例和一个 Thread 实例.
  • 线程执行完的返回值可以通过FutureTask的 get 方法得到.
  • 调用 get 方法时会阻塞当前线程. 只有等 Callable 线程执行完并返回值后, 当先线程才会从 blocked 状态变为 runable
  • 即使使用Callable和FutureTask运行一个线程, 如果不调用 get方法, 就不会阻塞当前线程.
private static class Thread3 implements Callable<String> {
    @Override
    public String call() throws Exception {
        String str = "Callable Thread 3 is running.";
        return str;
    }
}
FutureTask<String> future = new FutureTask<String>(new Thread3());
new Thread(future).start();
System.out.println(future.get());

Callable是@FunctionalInterface 的, 所以FutureTask可以简写成这样

FutureTask<String> future =
        new FutureTask(() ->"Callable Thread 3 is running.");

以上三种方式的共同点是都需要一个 Thread 对象来启动线程(stratr 方法). 当线需要用到的线程数量非常多的时, 就需要用到线程管理相关的类.

首先介绍 ThreadGroup

  • 每个线程都属于一个线程组.
  • Java入口方法 main 方法的默认线程组的名字是 main
  • 线程组中不能启动线程, 因为只有启动的线程才能添加到线程组中
  • dead 状态的线程没有线程组
  • 线程组可以是嵌套关系, 也就是一个线程组可以包含多个线程和线程组

ThreadGroup 常用方法

ThreadGroup(String name) 

构造方法, 需要传入名字

ThreadGroup(ThreadGroup parent,String name)

构造方法, 如果想把新建的 group 作为另一个 group 的子 group, 就可以用这个方法

int activeCount()

返回group 中线程的数量

int activeGroupCount()

返回 group 中 子group的数量

String getName()

返回 group 的名字

ThreadGroup getParent()

返回父 group, 如果没有则返回 null

void interrupt()

中断 group 中的所有线程

Executor框架

ThreadGroup 功能非常有限, 我个人意见是这个类的实际用处不大. 在Java 5之后引入了Executor框架. Executor框架中包含了大量的于线程管理, 性能优化的类.

这个教程主要是介绍 Java 多线程的基本概念和使用, 所有这里就不介绍ThreadPoolExecutor了, 虽然它是Executor框架中非常重要的类, 但很多基本的使用并不需要用到.

Executor 框架的基本用法

  1. 先得到一个ExecutorService实例.
  2. 然后把线程对象传入 execute 或 submit 方法中, 线程就开始执行了.
  3. 最后 调用shutdown 或 shutdownNow 方法.

如何得到ExecutorService实例

通过ThreadPoolExecutor

ExecutorService是一个接口, 继承了Executor. ThreadPoolExecutor是他的一个实现类. 所以可以这样写

ExecutorService executorService =
  new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,  
  new LinkedBlockingQueue<Runnable>());

更简单的方法, 也是更常用的方法是使用Executors类.

Executors是一个工具类, 它有一些工厂方法来创建不同类型的线程池. 以下是Executors常用的一些方法.

public static Callable<Object> callable(Runnable task) 
  • 把一个Runnable转为Callable.
public static ExecutorService newSingleThreadExecutor() 
  • 返回一个ExecutorService对象
  • 这个对象只有一个线程可用来执行任务
  • 线程多于一个时, 线程将按先后顺序执行
public static ExecutorService newCachedThreadPool()
  • 返回一个ExecutorService对象
  • 这个对象有一个线程池, 线程池的大小会根据需要调整
  • 线程执行完任务后返回线程池, 供执行下一次任务使用
public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory)
  • 返回一个ExecutorService对象
  • 这个对象带有一个大小为poolSize的线程池
  • 线程数大于poolSize时, 线程会被放在一个queue里顺序执行
public static ScheduledExecutorService newSingleThreadScheduledExecutor()
  • 返回一个ScheduledExecutorService对象 (ScheduledExecutorService 是个接口, 继承了ExecutorService)
  • 这个对象的线程池大小为1.
  • 若线程多于一个时, 线程将按先后顺序执行
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)
  • 返回一个ScheduledExecutorService对象
  • 这个对象的线程池大小为poolSize
  • 当线程数大于poolSize,线程会在一个queue里等待执行

所以, 这样也可以得到一个 ExecutorService.

ExecutorService executor = Executors.newFixedThreadPool(10);

ExecutorService方法execute 和 submit 的区别

void execute(Runnable command);
  • execute() 方法接受一个 Runnable
  • 它没有返回值
Future<?> submit (Runnable task);
<T> Future<T> submit (Callable<T> task);
<T> Future<T> submit (Runnable task, T result);
  • submit() 方法既可以接受 Runnable 也支持 Callable

  • 返回 Future 对象

  • execute() 是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。

  • submit() 是在ExecutorService中声明的方法, 在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写, 这个方法也是用来向线程池提交任务的, 它能够返回任务执行的结果

  • submit() 实际上还是调用的execute(), 只不过它利用了Future来获取线程执行结果.

  • 在 java8中submit方法也支持Runnable.

shutdown 和 shutdownNow的作用和区别

  • 我们可以通过调用线程池的shutdown或shutdownNow方法来关闭线程池
  • shutdown的原理是只是将线程池的状态设置成SHUTDOWN状态, 然后中断所有没有正在执行的线程
  • shutdownNow的原理是遍历线程池中的线程, 逐个调用线程的interrupt方法来中断线程. 所以无法响应中断的任务可能永远无法终止
  • shutdownNow会首先将线程池的状态设置成STOP, 然后尝试停止所有的正在执行或暂停的线程.
  • 只要调用了这两个关闭方法的其中一个, ExecutorService的isShutdown方法就会返回true.
  • 当所有的任务都已关闭后, 才表示线程池关闭成功, 这时调用isTerminaed方法会返回true.
  • 通常调用shutdown来关闭线程池, 如果任务不一定要执行完, 则可以调用shutdownNow.

一个使用 Executor 的简单例子

        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 20; i++){
            executorService.execute(() -> {//Runnable
                try {
                    int sec = new Random().nextInt(10);
                    TimeUnit.SECONDS.sleep(sec);
                    System.out.println(Thread.currentThread().getName() + " is running... sec: " + sec);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });

            executorService.submit(()->{ // Callable
                return null;
            });
            executorService.submit(()->{ // Runnable
                System.out.println("submit Runnable");
            });
        }
        executorService.shutdown();  

参考

A Guide to the Java ExecutorService | Baeldung

Java多线程中FutureTask详解与正式环境问题定位 - CSDN博客

Threadgroup in java - javatpoint

java创建线程的三种方式及其对比 - CSDN博客

Java 101: Understanding Java threads, Part 4: Thread groups, volatility, and thread-local variables | JavaWorld

Java多线程之ExecutorService - Batys - 博客园

Java 8 Concurrency Tutorial: Threads and Executors - Benjamin Winterberg