线程创建的4种方式

       Java多线程实现方式主要有四种:继承Thread类、实现Runnable接口、实现Callable接口通过FutureTask包装器来创建Thread线程、使用ExecutorService Callable Future实现有返回结果的多线程。其中前两种方式线程执行完后都没有返回值,后两种是带返回值的。

继承Thread

       Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过Thread类的start()实例方法,start()方法是一个native方法,run()方法作为线程执行体被执行。开发步骤如下:

  • 创建一个类继承Thread类并重写run()方法
  • 实例化该类new MyThread()
  • 调用线程的start()方法启动线程
  • run()作为线程执行体被执行
1
2
3
4
5
6
7
8
9
10
public class MyThread extends Thread {
  public void run() {
   System.out.println("MyThread run() start...");
  }
}
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
myThread1.start();
myThread2.start();

实现Runnable接口

       Java中是单继承,所以上种方式有很大的局限性。实现Runnable接口。启动MyThread,需要首先实例化一个Thread,并传入自己的MyThread实例,开发步骤如下:

  • 创建一个类实现Runnable接口并重写run()方法
  • 实例化该类new MyThread(),或者new Thread(new MyThread())
  • 调用线程的start()方法启动线程
  • run()作为线程执行体被执行
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class MyThread extends OtherClass implements Runnable {
      public void run() {
       System.out.println("MyThread.run()");
      }
    }
    //伪代码
    MyThread myThread = new MyThread();
    Thread thread = new Thread(myThread);
    thread.start();

本质上,当传入一个Runnable target参数给Thread后,Thread的run()方法就会调用target.run(),以JDK1.8为例:

1
2
3
4
5
6
7
private Runnable target;
@Override
public void run() {
if (target != null) {
target.run();
}
}

实现Callable接口

这种方式创建线程,可以进行异步计算,涉及到 future设计模式。

创建线程

Callable接口(也只有一个方法)定义如下:

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

通过Callable接口创建线程,开发步骤如下:

  • 创建Callable接口的实现类,重写call()方法作为线程执行体,该方法有返回值
  • 创建Callable接口类的实例,使用FutureTask类来包装该实例
  • 将FutureTask对象作为Thread类的target创建启动线程
  • 使用FutureTask的get()方法获取子线程call()方法的返回值。主线程将在get()方法处阻塞等待子线程执行完成
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public class MyThread implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
    int sum = 0;
    for (int i = 0;i<=100;i++) {
    sum += i;
    }
    return sum;
    }
    public static void main(String[] args) {
    MyThread myThread = new MyThread();
    FutureTask<Integer> futureTask = new FutureTask<>(myThread);
    new Thread(futureTask).start();
    try {
    Integer result = futureTask.get();
    System.out.println(result);
    } catch (InterruptedException e) {
    e.printStackTrace();
    } catch (ExecutionException e) {
    e.printStackTrace();
    }
    }
    }

Future模式

       Future接口是Java线程Future模式的实现(一种异步计算模式),可以来进行异步计算,并支持返回计算结果,在调用get()获取到计算结果前可以阻塞调用者线程。
Future模式可以这样来描述:
       我有一个任务,提交给了Future,Future替我完成这个任务。期间我自己可以去做任何想做的事情。一段时间之后,我就便可以从Future那儿取出结果。就相当于下了一张订货单,一段时间后可以拿着提订单来提货,这期间可以干别的任何事情。其中Future接口就是订货单,真正处理订单的是Executor类,它根据Future接口的要求来生产产品。

设计原理

       FutureTask是JDK针对与future模式的一种实现,它除了支持future特有的特点,还支持task的一些操作,比如取消,打断。一个FutureTask就是一个任务的计算单元,是调度的最小单位,它的调度借助于JDK的Executor任务调度模型。需要开发人员创建好FutureTask对象后,并送入到Executor去等待调度。
执行过程:

  1. 创建一个futureTask对象task,提交task到调度器executor等待调度
  2. 等待调度……
  3. 如果此时currentThread调取执行结果task.get(),会有几种情况:

    1
    2
    3
    4
    5
    if task 还没有被executor调度或正在执行中
    阻塞当前线程,并加入到一个阻塞链表中waitNode
    else if task被其它Thread取消,并取消成功 或task处于打断状态
    throw exception
    else if task执行完毕,返回执行结果,或执行存在异常,返回异常信息
  4. 如果此时有另外一个线程调用task.get() ,执行过程同上.

注意:executor在执行FutureTask前,会先判断是否被取消,如果取消就不在执行,但执行后就不可以在取消了.

FutureTask

在 futureTask定义task的状态有:

1
2
3
4
5
6
7
8
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; //invoke task 出现异常
private static final int CANCELLED = 4; //cancel task
private static final int INTERRUPTING = 5; // interrupt task
private static final int INTERRUPTED = 6;

创建一个FutureTask(创建futureTask只需要需要一个callable对象或runnable对象的参数,并在创建时设置状态为NEW):

1
2
3
4
5
6
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}

调用get()方法获取执行结果方法—awaitDone():

1
2
3
4
5
6
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}

executor 调度是执行的方法-run(),本质上调用了call()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public void run() {
if (state != NEW ||!UNSAFE.compareAndSwapObject(this, runnerOffset,null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();//调用了call()
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
....
}
}

demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class UseFuture implements Callable<String>{
private String para;
public UseFuture(String para){
this.para = para;
}
/**
* 这里是真实的业务逻辑,其执行可能很慢
*/
@Override
public String call() throws Exception {
//模拟执行耗时
Thread.sleep(5000);
String result = this.para + "处理完成";
return result;
}
//主控制函数
public static void main(String[] args) throws Exception {
String queryStr = "query";
//构造FutureTask,并且传入需要真正进行业务逻辑处理的类,该类一定是实现了Callable接口的类
FutureTask<String> future = new FutureTask<String>(new UseFuture(queryStr));
FutureTask<String> future2 = new FutureTask<String>(new UseFuture(queryStr));
//创建一个固定线程的线程池且线程数为1,
ExecutorService executor = Executors.newFixedThreadPool(2);
//这里提交任务future,则开启线程执行RealData的call()方法执行
//submit和execute的区别: 第一点是submit可以传入实现Callable接口的实例对象, 第二点是submit方法有返回值
Future f1 = executor.submit(future); //单独启动一个线程去执行的
Future f2 = executor.submit(future2);
System.out.println("请求完毕");
try {
//这里可以做额外的数据操作,也就是主程序执行其他业务逻辑
System.out.println("处理实际的业务逻辑...");
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
//调用获取数据方法,如果call()方法没有执行完成,则依然会进行等待
System.out.println("数据:" + future.get());
System.out.println("数据:" + future2.get());
executor.shutdown();
}
}

执行结果:

1
2
3
4
5
请求完毕
处理实际的业务逻辑...
// 这里阻塞着,等待异步计算完成
数据:query处理完成
数据:query处理完成

线程池ExecutorService

       ExecutorService、Callable、Future三个接口实际上都是属于Executor框架。返回结果的线程是在JDK1.5中引入的新特征,可返回值的任务必须实现Callable接口。类似的,无返回值的任务必须实现Runnable接口。执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了。再结合线程池接口ExecutorService就可以实现传说中有返回结果的多线程了。具体demo请参考上述的 Future的demo。
       注意:get方法是阻塞的,即:线程无返回结果,get方法会一直等待。

文章目录
  1. 1. 继承Thread
  2. 2. 实现Runnable接口
  3. 3. 实现Callable接口
    1. 3.1. 创建线程
    2. 3.2. Future模式
      1. 3.2.1. 设计原理
      2. 3.2.2. FutureTask
      3. 3.2.3. demo
  4. 4. 线程池ExecutorService
|