> 文档中心 > 第⼆章 Java多线程入门类和接口

第⼆章 Java多线程入门类和接口


2.1 Thread类和Runnable接口

2.1.1 继承Tread类

首先是继承Tread类:

/*** @author :ls* @date :Created in 2022/4/18 15:10* @description:*/public class T1 {    public static class MyThread extends Thread{ @Override public void run() {     System.out.println("MyThread!"); }    }    public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.start();    }}

注意要调用 start() 方法后,该线程再算启动。

2.1.2 实现Runnable接口

Runnable接口源码

@FunctionalInterfacepublic interface Runnable {    /**     * When an object implementing interface Runnable is used     * to create a thread, starting the thread causes the object's     * run method to be called in that separately executing     * thread.     * 

* The general contract of the method run is that it may * take any action whatsoever. * * @see java.lang.Thread#run() */ public abstract void run();}

我们可以使用java8函数式编程来简化一下代码:

/*** @author :ls* @date :Created in 2022/4/18 15:10* @description:*/public class T1 {    public static class MyThread implements Runnable { @Override public void run() {     System.out.println("MyThread"); }    }    public static void main(String[] args) { new Thread(new MyThread()).start(); // Java 8 函数式编程,可以省略MyThread类 new Thread(() -> {     System.out.println("Java 8 匿名内部类"); }).start();    }}

2.1.3 Thread类构造方法

Thread类是一个 Runnable 接口的实现类
查看Thread类的构造方法,发现其实是简单调用一个私有的init 方法来实现初始化的。

//Thread类源码//片段1  init方法private void init(ThreadGroup g, Runnable target, String name,    long stackSize, AccessControlContext acc,    boolean inheritThreadLocals)//片段二  构造函数public Thread(Runnable target) {    init(null, target, "Thread-" + nextThreadNum(), 0);}//片段三  在init方法中初始化的 AccessControlContextthis.inheritedAccessControlContext = acc != null ? acc : AccessController.getContext();//片段四  两个支持ThreadLocal的私有属性ThreadLocal.ThreadLocalMap threadLocals = null;ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

解释一下 这些个参数:

  • g : 线程组,指定这个线程是在哪个线程组下。
  • target : 指定要执行的任务。
  • name : 线程的名字,多个线程的名字是可以重复的。如不指定名字,见片段二。
  • acc : 见片段三,用于初始化私有变量inheritedAccessControlContext。
  • inheritThreadLocals : 可继承的 ThreadLocal ,见片段4,Thread类里面有量的私有属性来支持ThreadLocal。

一般来说我们用的最多的是:

Thread(Runnable target) Thread(Runnable target, String name)

2.1.3 Thread类的几个常用方法

  • currentThread() : 静态方法,返回对当前正在执行的线程对象的引用。
  • start() : 开始执行线程的方法,java虚拟机会调用线程内部的run()方法。
  • yield() : 放弃当前线程占用的cup资源。这里需要注意的是,就算当前线程调用了yield()方法,程序在调度的时候,也还有可能继续运行这个线程。
  • sleep() : 静态方法,使用当前线程睡眠一段时间。
  • join() : 使用当前线程等待另一个线程执行完毕之后在继续执行,内部调用是Object类的wait()方法实现的。

2.1.4 Thread类与Runnable接口的比较

  • 由于java“单继承,多实现”的特性,Runnable接口使用起来比Thread更灵活。
  • Runnable接口出现更符合面向对象,将线程单独进行对象的封装。
  • Runnable接口出现,降低了线程对象和线程任务的耦合性。
  • 如果使用线程时不需要使用Thread类的诸多方法,显然使用Runnable接口更为轻量。

2.2 Callable、Future与FutureTask

2.2.1 Callbale接口

Callable与Runnable类似,同样是只有一个抽象方法的函数式接口。不同的是,callable提供的方法是有返回值的,而且支持泛型。

@FunctionalInterface public interface Callable {     V call() throws Exception; }

callable一般是配合线程池工具 ExecutorService 来使用的。通过 submit 方法来让一个 Callable接口执行。它会返回一个 Future ,通过 Future 的 get 方法可以得到返回的结果。

/*** @author :ls* @date :Created in 2022/4/18 22:46* @description:*/public class T2 implements Callable<Integer>{ @Override public Integer call() throws Exception {     // 模拟计算需要⼀秒     Thread.sleep(1000);     return 2; } public static void main(String args[]) throws ExecutionException, InterruptedException {     // 使⽤     ExecutorService executor = Executors.newCachedThreadPool();     T2 task = new T2();     Future<Integer> result = executor.submit(task);     // 注意调⽤get⽅法会阻塞当前线程,直到得到结果。     // 所以实际编码中建议使⽤可以设置超时时间的重载get⽅法。     System.out.println(result.get()); }}

输出结果:

2

2.2.2 Future 接口

public abstract interface Future<V> {    public abstract boolean cancel(boolean mayInterruptIfRunning);    public abstract boolean isCancelled();    public abstract boolean isDone();    public abstract V get() throws InterruptedException, ExecutionException;    public abstract V get(long timeout, TimeUnit unit)  throws InterruptedException, ExecutionException, TimeoutException;}

cancel 方法是试图取消一个线程的执行。 注意这里是试图取消并不一定成功。因为任务可能已完成、或者一些其他因素不能取消,存在取消失败可能。
返回值 表示“是否取消成功”
参数 mayInterruptIfRunning 表示是否采用中断的方式取消线程。

因此为了让任务有能够取消的功能,就使用callable来代替runnable。如果为了可取消性⽽使⽤ Future 但⼜不提供可⽤的结果,则可以声明 Future 形式类型、并返回 null 作为底层任务的结果。

2.2.3 FutureTask类

这个接口有一个实现类叫 FutureTask 。 FutureTask 是 实现的 RunnableFuture 接口的,而 RunnableFuture 接口同时继承了 Runnable 接口 和 Future 接口:

public interface RunnableFuture<V> extends Runnable, Future<V> {    /**     * Sets this Future to the result of its computation     * unless it has been cancelled.     */    void run();}

Future 只是一个接口,而它里面的 cancel , get , isDone 等方法要自己实现 起来都是非常复杂的。所以JDK提供了一个 FutureTask 类来供我们使用。

/*** @author :ls* @date :Created in 2022/4/18 23:15* @description:*/public class T3 implements Callable<Integer> {    @Override    public Integer call() throws Exception { // 模拟计算需要⼀秒 Thread.sleep(1000); return 2;    }    public static void main(String args[]) throws ExecutionException, InterruptedException { // 使⽤ ExecutorService executor = Executors.newCachedThreadPool(); FutureTask<Integer> futureTask = new FutureTask<>(new T3()); Future<?> submit = executor.submit(futureTask); System.out.println(futureTask.get());    }}

使用上与第一个Demo有一点小的区别。首先,调用 submit 方法是没有返回值的。 这里实际上是调用的 submit(Runnable task) 方法,而上面的Demo,调用的 是 submit(Callable task) 方法。

然后,这里是使用 FutureTask 直接取 get 取值,而上面的Demo是通过 submit 方 法返回的 Future 去取值。

在很多高并发的环境下,有可能Callable和FutureTask会创建多次。FutureTask能 够在高并发环境下确保任务只执行一次。

2.2.3 FutureTask的几个状态

/**   * state可能的状态转变路径如下:   * NEW -> COMPLETING -> NORMAL   * NEW -> COMPLETING -> EXCEPTIONAL   * NEW -> CANCELLED   * NEW -> INTERRUPTING -> INTERRUPTED   */private volatile int state;   //任务的运⾏状态private static final int NEW   = 0;    //初始状态为NEWprivate static final int COMPLETING   = 1;    //完成状态/结束private static final int NORMAL= 2;    //正常的private static final int EXCEPTIONAL  = 3;    //异常的private static final int CANCELLED    = 4;    //已取消private static final int INTERRUPTING = 5;    //打断private static final int INTERRUPTED  = 6;    //已中断