Java多线程基础类和接口
我们了解了多线程的基本概念,那么在 Java 中,是如何表示线程和使用多线程的呢?
Java 中用 Thread 类来表示线程,提供 Runnable 接口来实现任务。
- extends Thread,重写 run 方法
- implements Runnable ,实现 run 方法
多线程的基本使用
Thread 和 Runnable
创建和启动
- 创建 Thread 实例
我们首先要创建一个 Thread 实例,可以继承 Thread,或者传递一个 Runnable.
继承的方式
public class MyThread extends Thread{
@Override
public void run() {
System.out.println("I'm new Thread");
}
}
传递 Runnable 的方式, 可以使用 lambda 语法
new Thread(()->{
System.out.println("I'm new Thread");
})
- 调用 **
start
**方法
调用start()
方法后,线程才会启动。虚拟机会创建一个线程来运行我们实现的run
方法。
启动
new MyThread().start();
或者
new Thread(()->{
System.out.println("I'm new Thread");
}).start();
注意不可多次调用 start 方法。多次调用会抛出 IllegalThreadStateException。
Thread 类构建方法
Thread 实现了 Runnable 接口。
来看 Thread 类的构造方法,内部是调用 init 来初始化。
//片段1 init 方法
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals)
//片段2 构造函数
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
// 片段3 - 使用在init方法里初始化AccessControlContext类型的私有属性
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
// 片段4 - 两个对用于支持ThreadLocal的私有属性
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
init
方法在片段一,见名知意,补充一下:
- name:线程名,可重复,不指定名称,见片段2
- acc: 初始化私有变量,见片段3
inheritedAccessControlContext 可以用来限制线程的执行权限。一般我们是不会使用它的,那什么时候会使用到这个变量呢?可以参考这个stackoverflow的问题:Restrict permissions to threads which execute third party software;
- inheritThreadLocals:可继承的 ThreadLocal。见片段4.
实际情况下,我们大多直接调用下面两个构造方法
Thread(Runnable target)
Thread(Runnable target, String name)
Thread常用方法
静态方法
- currentThread():返回当前执行线程对象的引用
- yield(): 当前线程愿意放弃对当前处理器的占用。只是意愿,调用该方法后,有可能会继续运行当前线程。
- sleep(): 当前线程睡眠一段时间
实例方法
- join(): 当前线程等待 实例线程 die 后再执行
例如
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("Thread Hello");
});
thread.start();
thread.join();
System.out.println("Main Hello");
}
主线程会等 thread 线程执行完,再输出 "Main Hello"
Callable、Future、FutureTask
通常,我们使用 Runnable 和 Thread 来创建新的线程。但是有一个问题,就是 run 方法没有返回值。
JDK 提供了 Callable
和 Future
接口来解决这个问题,也就是所谓的异步模型。
Callable 接口
Callable 与 Runnable 类似,都是只有一个抽象方法的 lambda 接口。不同的是 Callable 接口有返回值,而且支持泛型。
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
Callable 的使用
Callable 一般配合线程池工具 ExecutorService 使用。
ExecutorService 提交一个 Callable ,返回一个 Future,结果可以通过 Future 的 get 方法得到。
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<String> callable = () -> {
return "hello,callable";
};
ExecutorService executorService = Executors.newCachedThreadPool();
Future<String> future = executorService.submit(callable);
String s = future.get();
System.out.println(s);
}
输出:
hello,callable
Future 接口
Future 表示了异步计算的结果。
public interface Future<V> {
boolean cancel(boolean paramBoolean);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long paramLong, TimeUnit paramTimeUnit)
throws InterruptedException, ExecutionException, TimeoutException;
}
常用方法有
- get 获取异步结果
- cancel 尝试取消线程执行,boolean参数表示是否采用中断方式取消线程。
所以,为了让任务有取消的功能,可以用 Callable 来代替 Runnable。
FutureTask 类
JDK为 Future 接口提供了实现,这个实现类叫做 FutureTask。
它实现了 Runnable 和 Future 接口。
创建 FutureTask 的接口。
public FutureTask(Callable<V> callable)
基本用法如下:
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> futureTask = new FutureTask(() -> {
return "hello,world";
});
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.submit(futureTask);
String s = futureTask.get();
System.out.println(s);
}
为什么又FutureTask呢?因为 Future 只是一个接口,里面的 cancel 、 get 、 isDone 自己实现都非常复杂。所以JDK 提供了一个 FutureTask 供我们使用。
submit 提交的调用的是 submit(Runnable task)
方法,是没有返回值的。结果要 通过 FutureTask
实例的 get
方法来获取。
提交Callable
是调用 submit(Callable<T> task)
,返回一个 Future
,通过返回的Futrue
来取值。