线程状态

Java线程在运行的生命周期中可能处于以下6种不同的状态:

状态名称 说明
NEW 初始状态,线程被构建,但是还没调用start()方法
RUNNABLE 运行状态,Java线程将操作系统中的就绪和运行两种状态笼统地称作“运行中”
BLOCKED 阻塞状态,表示线程阻塞于锁
WAITING 无限期等待状态,进入该状态表示当前线程需要等待其它线程做出一些特定动作(通知或中断)
TIME_WAITING 限期等待状态,可以在指定的时间自行返回
TERMINATED 终止状态,表示当前线程已经执行完毕

Java将操作系统中的运行和就绪两个状态合并称为运行状态。阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态,但是阻塞在Lock接口的线程状态是等待状态。

Daemon线程

用户线程是我们平常创建的普通线程,而守护线程则是用来服务于用户线程的一种支持型线程。当一个Java虚拟机中只存在守护线程的时候,Java虚拟机将会退出。可以在启动线程之前通过调用Thread.setDaemon(true)将该线程设置为守护线程。

守护线程可用于实时监控和管理系统中的可回收资源。例如,Java垃圾回收线程就是一个典型的守护线程,当我们的程序中不再有任何运行中的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是Java虚拟机上仅剩的线程时,Java虚拟机会自动退出。

使用线程

有三种使用线程的方法:

  1. 实现Runnable接口;
  2. 实现Callable接口;
  3. 继承Thread类。

实现RunnableCallable接口的类只能当做一个可以在线程中运行的任务,不是真正意义上的线程,因此最后还需要通过Thread来调用。可以说任务是通过线程驱动从而执行的。

实现Runnable接口

需要实现run()方法。通过Thread调用start()方法来启动线程。

1
2
3
4
5
6
7
8
9
10
11
public class MyRunnable implements Runnable {
public void run() {
// ...
}
}

public static void main(String[] args) {
MyRunnable instance = new MyRunnable();
Thread thread = new Thread(instance);
thread.start();
}

实现Callable接口

Runnable相比,Callable可以有返回值,返回值通过FutureTask进行封装。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MyCallable implements Callable<Integer> {
public Integer call() {
return 123;
}
}

public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable mc = new MyCallable();
FutureTask<Integer> ft = new FutureTask<>(mc);
Thread thread = new Thread(ft);
thread.start();
System.out.println(ft.get());
}

继承Thread类

同样也是需要实现run()方法,因为Thread类也实现了Runable接口。

当调用start()方法启动一个线程时,虚拟机会将该线程放入就绪队列中等待被调度,当一个线程被调度时会执行该线程的run()方法。

1
2
3
4
5
6
7
8
9
10
public class MyThread extends Thread {
public void run() {
// ...
}
}

public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
}

对比

实现接口会更好一些,因为:

  • Java不支持多重继承,因此继承了Thread类就无法继承其它类,但是可以实现多个接口。
  • 类可能只要求可执行就行,继承整个Thread类开销过大。

等待/通知机制

等待/通知机制是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。以下为等待/通知的经典范式:

1
2
3
4
5
6
7
8
9
10
11
12
13
//等待方
synchronized(对象){
while(条件不满足){
对象.wait();
}
对应的处理逻辑;
}

//通知方
synchronized(对象){
改变条件
对象.notifyAll();
}

wait()notify()notifyAll()方法只能用在同步方法或者同步控制块中使用,否则会在运行时抛出IllegalMonitorStateException。使用wait()挂起期间,线程会释放锁,这是因为如果没有释放锁,那么其它线程就无法进入对象的同步方法或者同步控制块中,那么就无法执行notify()或者notifyAll()来唤醒挂起的线程,造成死锁。

注意,wait()Object的方法,而sleep()Thread的静态方法;wait()会释放锁,sleep()不会。

线程之间的协作

Thread.join()

在线程中调用另一个线程的join()方法,会将当前线程挂起,而不是忙等待,直到目标线程结束。

await() signal() signalAll()

java.util.concurrent类库中提供了Condition类来实现线程之间的协调,可以在Condition上调用await()方法使线程等待,其它线程调用signal()signalAll()方法唤醒等待的线程。

相比于wait()这种等待方式,await()可以指定等待的条件,因此更加灵活。

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
public class AwaitSignalExample {

private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();

public void before() {
lock.lock();
try {
System.out.println("before");
condition.signalAll();
} finally {
lock.unlock();
}
}

public void after() {
lock.lock();
try {
condition.await();
System.out.println("after");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}

public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
AwaitSignalExample example = new AwaitSignalExample();
executorService.execute(() -> example.after());
executorService.execute(() -> example.before());
}

}

ThreadLocal

ThreadLocal即线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构。这个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值。可以通过set(T)方法来设置一个值,在当前线程下再通过get()方法获取到原先设置的值。

参考资料

  • 《Java并发编程的艺术》
  • CS-Notes