Java 线程创建的几种方式

「这是我参与2022首次更文挑战的第2天,活动详情查看:2022首次更文挑战」。

我们在工作中经常会用到多线程开发,今天我们一起来梳理一下线程创建的几种方式:

  1. 继承 Thread 线程类
  2. 实现 Runnable 接口
  3. 创建 Callable 接口的实现类
  4. 使用拉姆达表达式创建(1.8)
  5. ForkJoinPool 线程池
  6. 继承 RecursiveAction, 无返回值
  7. 继承 RecursiveTask, 带返回值的
  8. 使用线程池 ThreadPoolExecutor
  9. 使用并发包 Executors 工具类创建

继承 Thread 类

继承线程类,然后重写 run 方法,调用线程 start 方法后, 等待 JVM 为当前线程分配到 CPU 时间片会调用 run 方法执行。

class Thread1 extends Thread {
    @Override  
    public void run() {
        //dosomething()
        System.out.println("Thread1");
    }
}
复制代码

实现 Runnable 接口

实现 Runnable 接口,重写 run 方法,执行线程需要丢入 Runnable 接口实现类,调用 Thread 对象的 start 方法执行。

// 定义线程
class Runner1 implements Runnable {
    @Override  
    public void run() {
        //dosomething()
        System.out.println("Runner1");
    }
}

// 线程执行
new Thread thread = new Thread(new Runner1());
thrad.start();
复制代码

创建 Callable 接口的实现类

Callable: 返回结果并且可能抛出异常的任务。 优点:

  • 可以获得任务执行返回值;
  • 通过与 Future 的结合,可以实现利用 Future 来跟踪异步计算的结果。
class Callable1 implements Callable {  
  @Override
  public String call() throws Exception {
    return "1";
  }
}

FutureTask stringFutureTask = new FutureTask<>(new Callable1());
stringFutureTask.run();
复制代码

使用流创建

其实本质是通过 Lambda(拉姆达)表示创建线程,也就是得到一个 Runnable 接口的实例。

Runnable runnable = () -> {
	System.out.println("createThreadForStream");
};
new Thread(runnable).start();
复制代码

ForkJoinPool 线程池

ForkJoinPool:Java提供了ForkJoinPool来支持将一个任务拆分成多个“小任务”并行计算,再把多个“小任务”的结果合成总的计算结果。ForkJoinPool是ExecutorService的实现类,因此是一种特殊的线程池。ForkJoinPool提供了如下两个常用的构造器。

public ForkJoinPool(int parallelism):创建一个包含parallelism个并行线程的ForkJoinPool。 public ForkJoinPool():以 Runtime.getRuntime().availableProcessors()的返回值作为 parallelism 来创建 ForkJoinPool。 ​

创建ForkJoinPool实例后,可以调用 ForkJoinPool 的 submit(ForkJoinTask task) 或者invoke(ForkJoinTask task) 来执行指定任务。其中 ForkJoinTask 代表一个可以并行、合并的任务。ForkJoinTask 是一个抽象类,它有两个抽象子类:RecursiveAction 和 RecursiveTask。

  • RecursiveTask:代表有返回值的任务;
  • RecursiveAction:代表没有返回值的任务;

我们就可以通过 RecursiveTask、 RecursiveAction 实例来创建线程。

new ForkJoinPool().submit(new Thread1());
复制代码

继承 RecursiveAction 无返回值

继承 RecursiveAction 我们可以创建不带返回值的线程对象。

 class PrintTask extends RecursiveAction {  
   @Override
    protected void compute() {
        System.out.println("RecursiveAction 子类");
    }
}

new ForkJoinPool().submit(new PrintTask());
复制代码

继承 RecursiveTask 获取返回值

继承 RecursiveTask我们可以创建带返回值的线程。

class CalcuteTask extends RecursiveTask {
  @Override
  protected Integer compute() {
    return 10;
  }
  
}

Executors.newSingleThreadExecutor().submit(new CalcuteTask());
ForkJoinTask submit = new ForkJoinPool().submit(new CalcuteTask());
Integer res = submit.get();
复制代码

使用线程池

ThreadPoolExecutor是我们创建线程池的核心工具类,我们创建线程池后。execute或者 submit方法提交线程执行任务,进入线程池中。等待线程池为我们分配资源执行。并且 ThreadPoolExecutor 提供了非常丰富的线程池配置参数。

通过 ThreadPoolExecutor 创建线程池,也是 Alibaba java 编程规范中倡导的变成方式。

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 2, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));
threadPoolExecutor.execute(new Thread1());
复制代码

使用并发包 Executors

Executors其实就是对 ThreadPoolExecutor的封装,是一个典型的工厂方法模式。提供了非常多的创建线程池的方法。比如:

  • public static ExecutorService newFiexedThreadPool(int Threads) 创建固定数目线程的线程池。
  • public static ExecutorService newCachedThreadPool():创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果没有可用的线程,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
  • public static ExecutorService newSingleThreadExecutor():创建一个单线程的Executor。
  • public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。
Executors.newSingleThreadExecutor().execute(new Thread1());
Executors.newFixedThreadPool(8).execute(new Thread1());
Executors.newWorkStealingPool(8).execute(new Thread1());
Executors.newCachedThreadPool().execute(new Thread1());
Executors.newSingleThreadScheduledExecutor().schedule(new Thread1(), 2, TimeUnit.SECONDS);
Executors.newScheduledThreadPool(8).schedule(new Thread1(), 2, TimeUnit.SECONDS);
复制代码

总结

Java 创建线程任务过后,JVM 底层会创建一个 JavaThread 和一个 OSThread, 将 Java 线程对象和操作系统内核态的进程绑定。

猜你喜欢

转载自juejin.im/post/7054914879030820872