Java 线程编程

编程须知

接口


Runnable

纯粹的运行方法,无返回值的运行方法,无返回值,不能抛异常

Runnable.java
1
2
3
4
@FunctionalInterface
public interface Runnable {
public abstract void run();
}

Callable

可以返回值,可以抛异常

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

Executors类提供工具方法,把Runnable转成Callable,实际上就是通过适配器增加了返回值逻辑
Runnable本身没有返回值,可以通过外部指定一个

Executors.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static Callable<Object> callable(Runnable task) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter<Object>(task, null);
}
public static <T> Callable<T> callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter<T>(task, result);
}

static final class RunnableAdapter<T> implements Callable<T> {
final Runnable task;
final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
task.run();
return result;
}
}

Future

代表了异步计算结果,使用get方法获取结果
提供了一些操作,可以配置最长等待时间,可以取消计算任务

Future.java
1
2
3
4
5
6
7
8
9
10
11
12
public interface Future<V> {
//cancel只表示在语义上视为中止,只进行通知,并非立刻中止运行
//如果true,表示尝试中断通知,false表示不干涉,让任务继续运行
//只要调用过cancel,不管成功失败,都视为任务已完成,isDone总返回true
//返回true表示cancel成功,isCancelled会返回true
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();

V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}

Future作为一种操作扩展,可以分别应用到RunnableCallable

  • RunnableFuture
    同时结合Runnable和Future特性
RunnableFuture.java
1
2
3
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
  • FutureTask
    FutureTask实现了异步结果返回的逻辑,底层是Callable
    可以看到开始执行run方法,内部使用CAS操作将当前线程保存到runner,后续可以用于取消操作
    执行get方法时,没执行完会进行等待
FutureTask.java
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
public class FutureTask<V> implements RunnableFuture<V> {
private Callable<V> callable;
private volatile Thread runner;
private volatile int state;

public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW;
}
//...
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();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
runner = null;
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}

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

FutureTask本身就是具备异步返回的Runnable,最终的载体依然是Thread,使用时用Thread包装,或者提交给线程池

载体


上述各种形式实质上都是Runnable,最终的载体都是线程
Thread本身就是个Runnable,所以也有run方法
Thread还可以作为其他Runnable的载体,构造时传入
Runnable本身没有名字,线程可以绑定名字

Thread.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Thread implements Runnable {
private Runnable target;
//...
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}

//...
@Override
public void run() {
if (target != null) {
target.run();
}
}
}

注意:
虽然Thread有run方法,但是绝不应该直接调用,如果直接调用,那么跟线程没关系,run只是一个普通的方法,正确的方式是通过start,JVM会负责启动线程并调用run方法

可以直接继承并重写Thread的run方法,但是会造成逻辑和线程本身混合,比如线程本身有一些方法,会造成不必要的方法遮蔽
Runnable剥离了线程逻辑,Thread作为载体,同一逻辑可以运行在多个Thread中,适合共享资源实现。在Runnable中如果需要访问Thread,可以使用Thread.currentThread()
另外Runnable作为接口,可以多实现,不占用单继承机会,代价远比继承小,更加灵活

线程组


线程可以加入线程组进行统一管理,构造时进行指定

Thread.java
1
2
3
public Thread(ThreadGroup group, Runnable target, String name, long stackSize) {
init(group, target, name, stackSize);
}

线程组底层就是线程的数组,可以进行统一管理,比如获取活跃线程数量activeCount,进行批量中断interrupt
线程组除了包含线程外,还可以继续划分子线程组,嵌套结构

异常处理


线程中的RuntimeException异常无法被父线程捕获,需要自定义异常处理器,注册给线程

  • 全局设置Thread.setDefaultUncaughtExceptionHandler
  • 单一线程设置thread.setUncaughtExceptionHandler
1
2
3
public interface UncaughtExceptionHandler {
void uncaughtException(Thread t, Throwable e);
}

如果有返回值场景下,可以通过Future类,get方法会报出ExecutionException包装内部异常

优先级


默认级别5,最高10,最低1。不保证有用,有些操作系统会无视级别。

Thread.java
1
2
3
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;

守护线程


JVM中只有守护线程时,才会退出,相当于扩展了生命周期
setDeamon只能在线程启动前设置,否则无效

Thread创建


自己创建出的Thread不是凭空出现的,而是由当前线程创建新线程,存在一个父子关系
如果没有显式指定新配置,新线程会继承父线程相关配置

Thread.java
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
public Thread() {
//默认会生成一个线程名称,线程栈0表示忽略,不对JVM提供建议
init(null, null, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}

this.name = name;
//当前的线程作为父线程
Thread parent = currentThread();
if (g == null) {
//如果不显式指定线程组,那么就和父线程同组
if (g == null) {
g = parent.getThreadGroup();
}
}
//...
g.addUnstarted();

this.group = g;
//守护线程创建的也是守护线程
this.daemon = parent.isDaemon();
//线程优先级相同
this.priority = parent.getPriority();
//类加载器也一样
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext = acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
//如果有可以继承的ThreadLocal,也拿过来
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;

//给新线程分配一个ID
tid = nextThreadID();
}

线程中止


线程类中包含废弃方法
thread.stop()
强制停止执行,抛出Error中的ThreadDeath
粗暴停止不安全,会释放所有锁,如果线程没有正常做完会导致被锁保护的对象处于错误状态,进而暴露给其他等待的线程
如果ThreadDeath被catch,一定要重新抛出,因为这是给JVM的,拦截会导致线程没法真正结束

thread.destroy()
本来预留,但是考虑到安全性,从没实现。。

线程优雅的停止,发出通知让线程感知而非强制中止

  • 利用volatile设置标志位
    无等待的逻辑可以用volatile标志
    while外围检测作为停止,但是如果线程在等待,轮不到外围检测
1
2
3
4
5
6
public static volatile boolean stop = false;
public void run(){
while(!stop) {
//working with waiting
}
}
  • 利用中断
    线程有等待逻辑,可以检测中断来进行响应
1
2
3
4
5
public void run(){
while(!Thread.currentThread.isInterrupted()) {
//working no waiting
}
}