Java并发编程之创建线程

122 阅读4分钟

前言

Java中创建线程的方式主要有以下三种:

  • 继承Thread类
  • 实现Runnable接口
  • 实现Callable接口

继承Thread类

通过继承Thread类并重写run方法来创建线程,调用start方法来开启线程

// 自定义线程类继承Thread
public class MyThread extends Thread {
    // 重写run方法
    @Override
    public void run() {
        System.out.println("自定义线程执行...");
    }
    public static void main(String[] args) {
        new MyThread().start();
    }
}

image.png

基本步骤为:

  • 自定义线程类继承Thread
  • 重写run方法
  • 创建线程类并调用start方法

注意

  • 虽然我们重写了run方法,并且线程最终也执行了run方法,但是不能直接调用run方法来开启线程,直接调用只会当做普通方法来执行,并不会创建线程。只有调用start方法,执行其中的start0本地方法,才会创建线程。
  • start方法只能调用一次,重复调用会抛出java.lang.IllegalThreadStateException异常,这个跟线程状态有关,后面文章会详细介绍。

实现Runnable接口

首先查看一下Thread类和Runnable接口源码

// 构造方法
public Thread(Runnable target) {                                        // 1
    init(null, target, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize) {
    init(g, target, name, stackSize, null, true);
}
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
    // ...省略其他代码
    this.target = target;                                              // 2
    setPriority(priority);
    // ...省略其他代码
}
public void run() {
    if (target != null) {
        target.run();                                                 // 3
    }
}

public interface Runnable {
    public abstract void run();                                       // 4
}
  1. Thread的构造方法可以接收一个Runnable类型参数
  2. 这个Runnable类型参数赋值给了target属性
  3. Thread类的run方法执行时会先判断target是否为null,不为null的话就执行targetrun方法
  4. Runnable接口只有一个run方法

所以我们就可以通过实现Runnable接口并实现run方法的方式来创建线程,代码如下:

// 1.实现Runnable接口
public class MyRunnable implements Runnable {
    // 2.实现run方法
    @Override
    public void run() {
        System.out.println("通过实现Runnable接口创建线程...");
    }
    public static void main(String[] args) {
        // 3.创建Runnable对象
        Runnable runnable = new MyRunnable();
        // 4.创建Thread对象并传入Runnable对象参数
        Thread thread = new Thread(runnable);
        // 5.调用start方法
        thread.start();
    }
}

通过Java8lambda可以进行简化

public static void main(String[] args) {
        new Thread(() -> {
            System.out.println("lambda简化创建线程...");
        }).start();
    }

实现Callable接口

主要步骤:

  1. 自定义类实现Callable接口
  2. 创建FutureTask对象并传入Callable类型参数
  3. 创建Thread对象并传入FutureTask对象
  4. 调用Thread类的start方法开启线程
  5. 调用FutureTask类的get方法获取返回值

代码如下:

public class MyCallable implements Callable {                       // 1
    public static void main(String[] args) {
        MyCallable myCallable = new MyCallable();                   // 2
        FutureTask futureTask = new FutureTask(myCallable);         // 2
        Thread thread = new Thread(futureTask);                     // 3
        thread.start();                                             // 4
        Object result = null;
        try {
            result = futureTask.get();                              // 5
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(result);
    }
    @Override
    public Object call() throws Exception {
        System.out.println("实现Callable创建线程...");
        return "success";
    }
}

下面通过查看FutureTaskCallable源码查看其执行原理(这里只考虑程序正常执行的情况)

public class FutureTask<V> implements RunnableFuture<V> {}         // 0
public interface RunnableFuture<V> extends Runnable, Future<V> {}  // 0

public FutureTask(Callable<V> callable) {             // 1
    if (callable == null)
        throw new NullPointerException();
    this.callable = callable;                         // 1
    this.state = NEW;      
}
public void run() {
    // 省略...
    try {
        Callable<V> c = callable;                     // 2
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
                result = c.call();                    // 2
                ran = true;
            } catch (Throwable ex) {
                result = null;
                ran = false;
                setException(ex);
            }
            if (ran)
                set(result);                         // 3
        }
    } finally {
        // 省略...
    }
}
protected void set(V v) {
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        outcome = v;                               // 4
        UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
        finishCompletion();
    }
}

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

private V report(int s) throws ExecutionException {
    Object x = outcome;
    if (s == NORMAL)
        return (V)x;                             // 6
    if (s >= CANCELLED)
        throw new CancellationException();
    throw new ExecutionException((Throwable)x);
}
public interface Callable<V> {
    V call() throws Exception;                    // 7
}
  1. 通过FutureTask类的继承关系可以看出其间接继承自Runnable接口,因此可以按照Runnable的方式创建线程;
  2. FutureTask构造方法可以接收Callable参数,并赋值给callable属性;
  3. run方法中把callable赋值给局部变量c,并调用ccall方法,并且得到一个返回值result
  4. 调用set方法处理result
  5. 最终result保存到属性outcome属性中;
  6. get方法返回的是report方法的结果;
  7. report方法最终返回的是属性outcome的值;
  8. Callable接口只有一个call方法,并且可以有返回值。

Runnable和Callable的区别

通过两个接口的源码可以看出有以下3种区别:

  1. Callable接口的方法名是call()Runnable接口中方法名是run();
  2. Callable接口中call()方法有返回值,Runnable接口中run()方法没有返回值;
  3. Callable接口中call()方法会抛出异常,源码中是这样写的Throws: Exception - if unable to compute a result,也就是说call方法如果无法计算出一个结果就会抛出异常,Runnable接口中run()方法不会抛出异常;