线程创建及启动
1. Runnable 接口与线程类 Thread
1.1 定义概述
Runnable
是一个函数式接口,其源码如下:
package java.lang;
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
Thread
类实现了 Runnable
接口,并可接收一个 Runnable
对象作为构造方法入参,Thread
和 Runnable
有千丝万缕的关系。
1.2 Thread 类详解
Java 使用Thread
类代表线程,所有的线程对象都必须是 Thread
类或其子类的实例。
Thread
类实现了Runnable
接口,并使用其run
方法作为线程要执行的核心逻辑,位于java.lang
包。Thread
类封装了线程对象需要的属性和方法。
Thread 类的方法 | 作用 |
---|---|
Thread() | 无参构造方法构建一个线程 |
Thread(Runnable target) | 构造一个新线程,调用指定目标的run() 方法 |
Thread(...) | 还有很多构造方法,看 JDK 源码吧...... |
void start() | 使线程处于就绪状态,线程就绪后才能调用run() 方法。start 方法本身会立即返回,JVM 会让线程择机并发运行。 |
void run() | 线程执行的核心内容。默认实现为:调用Thread 内部绑定的Runnable 的run 方法;如果没有内部没有Runnable ,什么也不做。 |
String getName() | 返回调用该方法的线程名字 |
synchronized void setName(String name) | 为线程设置名字。默认情况下,主线程的名字为 main ,用户启动的多个线程的名字依次为 Thread-0, Thread-1, ... 等。 |
Thread.State getState() | 得到这个线程的状态 |
void join() | 等待终止指定的线程 |
void join(long millis) | 等待指定的线程终止或者等待经过指定的毫秒数 |
void stop() | 已弃用 |
void suspend() | 已弃用 |
void resume() | 已弃用 |
static native void sleep(long millis) | 休眠指定的毫秒数 |
static native Thread currentThread() | 返回当前正在执行的线程对象 |
static void yield() | 使当前正在执行的线程向另一个线程交出运行权 |
根据构造方法 Thread(Runnable target)
可以发现,Thread
实现 Runnable
接口的同时,还接收另一个 Runnable
类型作为 target
参数。实际上,Thread
对象自己的 run
方法的方法体,去调用了 target
的 run
方法,即:
/**
* If this thread was constructed using a separate
* <code>Runnable</code> run object, then that
* <code>Runnable</code> object's <code>run</code> method is called;
* otherwise, this method does nothing and returns.
* <p>
* Subclasses of <code>Thread</code> should override this method.
*
* @see #start()
* @see #stop()
* @see #Thread(ThreadGroup, Runnable, String)
*/
@Override
public void run() {
if (target != null) {
target.run();
}
}
另外,对于一个 Java 程序,主线程的线程执行体不是由 run()
方法确定的,而是由 main()
方法确定的,main()
方法的方法体代表主线程的线程执行体。
由此可见,目前为止,你有两种手段来创建 Java 线程:
- 继承
Thread
类,重写run
方法:此时说白了是调用无参构造方法Thread()
来创建线程; - 传入一个
Runnable
对象:此时说白了是调用有参构造方法Thread(Runnable target)
来创建线程。
1.3 创建线程之继承 Thread 类
// 通过创建Thead类来创建线程类
class FirstThread extends Thread {
private int i;
public void run() {
for (; i < 100; ++i) {
// 当线程类继承Thread类时,直接使用this即可获取当前线程
// Thread对象的getName()返回当前线程的名字
System.out.println(getName() + " " + i);
}
}
public static void main(String[] args) {
for (int i = 0; i < 100; ++i) {
// 调用Thread的currentThread()方法获取当前线程
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == 20) {
new FirstThread().start(); // 创建并启动第一个线程
new FirstThread().start(); // 创建并启动第二个线程
}
}
}
}
注:使用继承
Thread
类的方法来创建线程类时,多个线程之间无法共享线程类的实例变量。
1.4 创建线程之传入 Runnable 对象
class SecondThread implements Runnable {
private int i;
// run()方法同样是线程执行体
public void run() {
for (; i < 100; ++i) {
// 当线程类实现Runnable接口时
// 获得当前线程只能使用Thread.currentThread()方法
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
public static void main(String[] args) {
for (int i = 0; i < 100; ++i) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == 20) {
SecondThread st = new SecondThread();
// 通过new Thread(target, name)方法创建新线程
new Thread(st, "新线程1").start();
new Thread(st, "新线程2").start();
}
}
}
}
只要在使用
new Thread(Runnable target)
创建线程时传入同一个Runnable
对象,则创建出来的多个线程就共享了同一个target
,因而共享了同一片内存资源。
2. Callable 与 Future 接口
2.1 Callable 接口
package java.util.concurrent;
/**
* A task that returns a result and may throw an exception.
* Implementors define a single method with no arguments called
* {@code call}.
*
* <p>The {@code Callable} interface is similar to {@link
* java.lang.Runnable}, in that both are designed for classes whose
* instances are potentially executed by another thread. A
* {@code Runnable}, however, does not return a result and cannot
* throw a checked exception.
*
* <p>The {@link Executors} class contains utility methods to
* convert from other common forms to {@code Callable} classes.
*
* @see Executor
* @since 1.5
* @author Doug Lea
* @param <V> the result type of method {@code call}
*/
@FunctionalInterface
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
Java 的 Callable
接口可以看作是 Runnable
接口的增强版(它们的设计用途类似),其提供了一个 call
方法可以作为线程执行体,但 call
方法比 run
方法功能更强大:
call
方法可以有返回值。call
方法可以声明抛出异常。
这么一说,你可能不自觉地想使用一个 Callable
对象作为 Thread
对象的 target
,然而,Callable
接口是 Java5 新增的接口,它不是 Runnable
接口的子接口,所以 Callable
对象不能直接作为 Thread
的 target
。
2.2 Future 接口
package java.util.concurrent;
public interface Future<V> {
// 尝试取消这个任务的运行。如果任务已经开始,并且mayInterrupt参数值为true,它就会被中断。
boolean cancel(boolean mayInterruptIfRunning);
// 如果任务在完成前被取消,则返回true
boolean isCancelled();
// 如果任务结束,无论是正常完成、中途取消还是发生异常,都返回true
boolean isDone();
// 获取结果,这个方法会阻塞,直到结果可用
V get() throws InterruptedException, ExecutionException;
// 获取结果,这个方法会阻塞,直到结果可用或者超过了指定的时间
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}
Java 使用 Future
表示一场异步计算的结果,并提供对应的方法来检查计算是否完成、等待计算完成、取回计算结果等。
A
Future
represents the result of an asynchronous computation. Methods are provided to check if the computation is complete, to wait for its completion, and to retrieve the result of the computation.
Future
功能强大,关于其中每个方法的具体用法,强烈建议看 JDK 源码上的注释。
Java 使用 Future
接口来代表 Callable
接口里 call
方法的返回值,并为 Future
接口提供了一个 FutureTask
实现类,该实现类同时实现了 Future
接口和 Runnable
接口,故而可以作为 Thread
类的 target
。
2.3 RunnableFuture 接口
Java 提供了一个 RunnableFuture
接口同时继承 Runnable
与 Future
接口,方便使用。从设计定位上来说,RunnableFuture
表示一个可以 Runnable
的 Future
。
A
Future
that isRunnable
. Successful execution of the run method causes completion of theFuture
and allows access to its results.
package java.util.concurrent;
/**
* A {@link Future} that is {@link Runnable}. Successful execution of
* the {@code run} method causes completion of the {@code Future}
* and allows access to its results.
* @see FutureTask
* @see Executor
* @since 1.6
* @author Doug Lea
* @param <V> The result type returned by this Future's {@code get} method
*/
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
对于 RunnableFuture
接口,JDK 提供了 FutureTask
实现类。FutureTask
可以接收一个 Callable
入参作为构造方法,这也使得 Callable
接口和 Future
接口产生了联系。
A
FutureTask
can be used to wrap aCallable
orRunnable
object. BecauseFutureTask
implementsRunnable
, aFutureTask
can be submitted to anExecutor
for execution.
/**
* A cancellable asynchronous computation. This class provides a base
* implementation of {@link Future}, with methods to start and cancel
* a computation, query to see if the computation is complete, and
* retrieve the result of the computation. The result can only be
* retrieved when the computation has completed; the {@code get}
* methods will block if the computation has not yet completed. Once
* the computation has completed, the computation cannot be restarted
* or cancelled (unless the computation is invoked using
* {@link #runAndReset}).
*
* <p>A {@code FutureTask} can be used to wrap a {@link Callable} or
* {@link Runnable} object. Because {@code FutureTask} implements
* {@code Runnable}, a {@code FutureTask} can be submitted to an
* {@link Executor} for execution.
*
* <p>In addition to serving as a standalone class, this class provides
* {@code protected} functionality that may be useful when creating
* customized task classes.
*
* @since 1.5
* @author Doug Lea
* @param <V> The result type returned by this FutureTask's {@code get} methods
*/
public class FutureTask<V> implements RunnableFuture<V> {
...
/** The underlying callable; nulled out after running */
private Callable<V> callable;
/** The result to return or exception to throw from get() */
private Object outcome; // non-volatile, protected by state reads/writes
/** The thread running the callable; CASed during run() */
private volatile Thread runner;
/** Treiber stack of waiting threads */
private volatile WaitNode waiters;
/**
* Creates a {@code FutureTask} that will, upon running, execute the
* given {@code Callable}.
*
* @param callable the callable task
* @throws NullPointerException if the callable is null
*/
public FutureTask(Callable<V> callable) {
...
}
...
}
2.4 使用 Callable 和 Future 创建线程
这种手段本质上是使用了
FutureTask
类作为了黏合剂。
步骤:
- 实现
Callable
接口并创建一个实例,其中的call
方法将作为线程执行体; - 使用
FutureTask
类来包装Callable
对象; - 将
FutureTask
对象作为Thread
的target
创建并启动新线程; - 调用
FutureTask
对象的get()
方法来获得子线程执行结束后的返回值。
class ThirdThread {
public static void main(String[] args) {
ThirdThread rt = new ThirdThread();
// Lambda表达式创建了Callable对象:先创建Callable<Integer>对象,再用FutureTask来包装Callable对象
FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>) () -> {
int i = 0;
for (; i < 100; ++i) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
// call()方法可以有返回值
return i;
});
for (int i = 0; i < 100; ++i) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == 20) {
// 实质还是以Callable对象来创建并启动线程的
new Thread(task, "有返回值的线程").start();
}
}
try {
// 获取线程返回值
System.out.println("子线程的返回值:" + task.get());
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
3. 创建线程的三种方式对比
采用继承 Thread
类的方式创建多线程的优缺点:
- 优势:编写简单,如果需要访问当前线程,无须使用
Thread.currentThread()
方法,直接使用this
即可获得当前线程; - 劣势:因为线程类已经继承了
Thread
类,所以不能再继承其他父类。
采用实现 Runnable, Callable
接口的方式创建多线程的优缺点:
- 线程类只是实现了
Runnable
接口或Callable
接口,还可以继承其他类; - 多个线程共享同一个
target
对象,非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想; - 劣势:编程稍稍复杂,若需要访问当前线程,必须使用
Thread.currentThread()
方法。
总结:一般推荐采用实现 Runnable
接口、Callable
接口的方式来创建多线程。