1 Thread类和Runnable接口

上一章我们了解了操作系统中多线程的基本概念。那么在Java中,我们是如何使用多线程的呢?

首先,我们需要有一个“线程”类。JDK提供了Thread类和Runnable接口来让我们实现自己的“线程”类。

  • 继承Thread类,并重写run方法;
  • 实现Runnable接口的run方法;

1.1 继承Thread类

先学会怎么用,再学原理。首先我们来看看怎么用ThreadRunnable来写一个Java多线程程序。

首先是继承Thread类:

public class Demo {
public static class MyThread extends Thread {
@Override
public void run() {
System.out.println("MyThread");
}
} public static void main(String[] args) {
Thread myThread = new MyThread();
myThread.start();
}
}

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

我们在程序里面调用了start()方法后,虚拟机会先为我们创建一个线程,然后等到这个线程第一次得到时间片时再调用run()方法。

注意不可多次调用start()方法。在第一次调用start()方法后,再次调用start()方法会抛出IllegalThreadStateException异常。

1.2 实现Runnable接口

接着我们来看一下Runnable接口(JDK 1.8 +):

@FunctionalInterface
public interface Runnable {
public abstract void run();
}

可以看到Runnable是一个函数式接口,这意味着我们可以使用Java 8的函数式编程来简化代码。

示例代码:

public class Demo {
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();
}
}

1.3 Thread类构造方法

Thread类是一个Runnable接口的实现类,我们来看看Thread类的源码。

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

// Thread类源码 

// 片段1 - init方法
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) // 片段2 - 构造函数调用init方法
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方法的这些参数:

  • g:线程组,指定这个线程是在哪个线程组下;

  • target:指定要执行的任务;

  • name:线程的名字,多个线程的名字是可以重复的。如果不指定名字,见片段2;

  • acc:见片段3,用于初始化私有变量inheritedAccessControlContext

    这个变量有点神奇。它是一个私有变量,但是在Thread类里只有init方法对它进行初始化,在exit方法把它设为null。其它没有任何地方使用它。一般我们是不会使用它的,那什么时候会使用到这个变量呢?可以参考这个stackoverflow的问题:Restrict permissions to threads which execute third party software

  • inheritThreadLocals:可继承的ThreadLocal,见片段4,Thread类里面有两个私有属性来支持ThreadLocal,我们会在后面的章节介绍ThreadLocal的概念。

实际情况下,我们大多是直接调用下面两个构造方法:

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

1.4 Thread类的几个常用方法

这里介绍一下Thread类的几个常用的方法:

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

1.5 Thread类与Runnable接口的比较:

实现一个自定义的线程类,可以有继承Thread类或者实现Runnable接口这两种方式,它们之间有什么优劣呢?

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

所以,我们通常优先使用“实现Runnable接口”这种方式来自定义线程类。

2.2 Callable、Future与FutureTask

通常来说,我们使用RunnableThread来创建一个新的线程。但是它们有一个弊端,就是run方法是没有返回值的。而有时候我们希望开启一个线程去执行一个任务,并且这个任务执行完成后有一个返回值。

JDK提供了Callable接口与Future接口为我们解决这个问题,这也是所谓的“异步”模型。

2.2.1 Callable接口

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

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

那一般是怎么使用Callable的呢?Callable一般是配合线程池工具ExecutorService来使用的。我们会在后续章节解释线程池的使用。这里只介绍ExecutorService可以使用submit方法来让一个Callable接口执行。它会返回一个Future,我们后续的程序可以通过这个Futureget方法得到结果。

这里可以看一个简单的使用demo:

// 自定义Callable
class Task implements Callable<Integer>{
@Override
public Integer call() throws Exception {
// 模拟计算需要一秒
Thread.sleep(1000);
return 2;
}
public static void main(String args[]) throws Exception {
// 使用
ExecutorService executor = Executors.newCachedThreadPool();
Task task = new Task();
Future<Integer> result = executor.submit(task);
// 注意调用get方法会阻塞当前线程,直到得到结果。
// 所以实际编码中建议使用可以设置超时时间的重载get方法。
System.out.println(result.get());
}
}

输出结果:

2

2.2 Future接口

Future接口只有几个比较简单的方法:

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

cancel方法是试图取消一个线程的执行。

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

所以有时候,为了让任务有能够取消的功能,就使用Callable来代替Runnable。如果为了可取消性而使用 Future但又不提供可用的结果,则可以声明 Future<?>形式类型、并返回 null作为底层任务的结果。

2.3 FutureTask类

上面介绍了Future接口。这个接口有一个实现类叫FutureTaskFutureTask是实现的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();
}

FutureTask类有什么用?为什么要有一个FutureTask类?前面说到了Future只是一个接口,而它里面的cancelgetisDone等方法要自己实现起来都是非常复杂的。所以JDK提供了一个FutureTask类来供我们使用。

示例代码:

// 自定义Callable,与上面一样
class Task implements Callable<Integer>{
@Override
public Integer call() throws Exception {
// 模拟计算需要一秒
Thread.sleep(1000);
return 2;
}
public static void main(String args[]) throws Exception {
// 使用
ExecutorService executor = Executors.newCachedThreadPool();
FutureTask<Integer> futureTask = new FutureTask<>(new Task());
executor.submit(futureTask);
System.out.println(futureTask.get());
}
}

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

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

在很多高并发的环境下,有可能Callable和FutureTask会创建多次。FutureTask能够在高并发环境下确保任务只执行一次。这块有兴趣的同学可以参看FutureTask源码。

2.4 FutureTask的几个状态

/**
*
* state可能的状态转变路径如下:
* NEW -> COMPLETING -> NORMAL
* NEW -> COMPLETING -> EXCEPTIONAL
* NEW -> CANCELLED
* NEW -> INTERRUPTING -> INTERRUPTED
*/
private volatile int state;
private static final int NEW = 0;
private 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;

state表示任务的运行状态,初始状态为NEW。运行状态只会在set、setException、cancel方法中终止。COMPLETING、INTERRUPTING是任务完成后的瞬时状态。

以上就是Java多线程几个基本的类和接口的介绍。可以打开JDK看看源码,体会这几个类的设计思路和用途吧!

最新文章

  1. iTextSharp简单生成pdf和操作pdf添加水印
  2. CentOS 配置solr中文分词器
  3. jquery中prop()方法和attr()方法的区别
  4. 在 PHP 中结合 Ajax 技术进行图片上传
  5. JS多种方法实现随机颜色;
  6. easyloader [easyui_1.4.2] 分析源码,妙手偶得之
  7. shell脚本中常见的一些特殊符号和作用详解
  8. Cocos2d-x 截图功能
  9. 远离压力,提高效率——Getting things done读书笔记
  10. Java 工厂模式(一)— 简单工厂模式
  11. ES6学习笔记三(proxy和reflect)
  12. Java 基础知识总结 3
  13. Java基础87 MySQL数据约束
  14. Future接口和FutureTask类【FutureTask实现了Runnable和Future接口】
  15. jQuery获得元素位置offset()和position()的区别
  16. HDU 4639 hehe 杭电2013多校联赛第四场1008题
  17. Vue学习笔记之Vue的模板字符串
  18. Python 中for...esle和while...else语法
  19. kotlin 类 接口 抽象类 子类类型有限的class 。sealed class
  20. eclipse生成export生成jar详解

热门文章

  1. python3使用requests模块发https请求,提示caused by ssl error, can&#39;t connect to https url because the ssl module is not available
  2. Java开发环境配置(win7-64bit)
  3. Loadrunner性能测试简记
  4. JZOJ 4279. 【NOIP2015模拟10.29B组】树上路径
  5. 自己从零写操作系统GrapeOS——1.GrapeOS介绍
  6. 跳板攻击之:reGeorg 代理转发
  7. 基于C++的OpenGL 12 之多光源
  8. I2C接口(续一)
  9. PHP面向对象(二)
  10. 对象可能是类数组对象 不具备数组的原型内的方法 所以可以用call或者apply把this指向改成数组或对象原型