线程状态
Java线程在运行的生命周期中可能处于以下6种不同的状态:
状态名称 | 说明 |
---|---|
NEW | 初始状态,线程被构建,但是还没调用start() 方法 |
RUNNABLE | 运行状态,Java线程将操作系统中的就绪和运行两种状态笼统地称作“运行中” |
BLOCKED | 阻塞状态,表示线程阻塞于锁 |
WAITING | 无限期等待状态,进入该状态表示当前线程需要等待其它线程做出一些特定动作(通知或中断) |
TIME_WAITING | 限期等待状态,可以在指定的时间自行返回 |
TERMINATED | 终止状态,表示当前线程已经执行完毕 |
Java将操作系统中的运行和就绪两个状态合并称为运行状态。阻塞状态是线程阻塞在进入synchronized
关键字修饰的方法或代码块(获取锁)时的状态,但是阻塞在Lock
接口的线程状态是等待状态。
Daemon线程
用户线程是我们平常创建的普通线程,而守护线程则是用来服务于用户线程的一种支持型线程。当一个Java虚拟机中只存在守护线程的时候,Java虚拟机将会退出。可以在启动线程之前通过调用Thread.setDaemon(true)
将该线程设置为守护线程。
守护线程可用于实时监控和管理系统中的可回收资源。例如,Java垃圾回收线程就是一个典型的守护线程,当我们的程序中不再有任何运行中的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是Java虚拟机上仅剩的线程时,Java虚拟机会自动退出。
使用线程
有三种使用线程的方法:
- 实现
Runnable
接口; - 实现
Callable
接口; - 继承
Thread
类。
实现Runnable
和Callable
接口的类只能当做一个可以在线程中运行的任务,不是真正意义上的线程,因此最后还需要通过Thread
来调用。可以说任务是通过线程驱动从而执行的。
实现Runnable接口
需要实现run()
方法。通过Thread
调用start()
方法来启动线程。
1 | public class MyRunnable implements Runnable { |
实现Callable接口
与Runnable
相比,Callable
可以有返回值,返回值通过FutureTask
进行封装。
1 | public class MyCallable implements Callable<Integer> { |
继承Thread类
同样也是需要实现run()
方法,因为Thread
类也实现了Runable
接口。
当调用start()
方法启动一个线程时,虚拟机会将该线程放入就绪队列中等待被调度,当一个线程被调度时会执行该线程的run()
方法。
1 | public class MyThread extends Thread { |
对比
实现接口会更好一些,因为:
- Java不支持多重继承,因此继承了
Thread
类就无法继承其它类,但是可以实现多个接口。 - 类可能只要求可执行就行,继承整个
Thread
类开销过大。
等待/通知机制
等待/通知机制是指一个线程A调用了对象O的wait()
方法进入等待状态,而另一个线程B调用了对象O的notify()
或者notifyAll()
方法,线程A收到通知后从对象O的wait()
方法返回,进而执行后续操作。以下为等待/通知的经典范式:
1 | //等待方 |
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 | public class AwaitSignalExample { |
ThreadLocal
ThreadLocal
即线程变量,是一个以ThreadLocal
对象为键、任意对象为值的存储结构。这个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal
对象查询到绑定在这个线程上的一个值。可以通过set(T)
方法来设置一个值,在当前线程下再通过get()
方法获取到原先设置的值。
参考资料
- 《Java并发编程的艺术》
- CS-Notes