第⼆章 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(); }}
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; //已中断