1 线程基础
线程是并发的基础单元,这里从线程开始说明。实现多线程有以下两种方式:
class TestThreadStateA extends Thread {
@Override
public void run() {
}
new Thread(()->{
System.out.println("sub localVariable is "+localVariable.get());
}).start();
1.1 线程属性
线程的属性包括线程的编号(ID),线程的名称(Name),线程的类别(Daemon)和优先级(Priority)。
属性 | 属性类型及用途 | 只读属性 | 重要注意事项 |
---|---|---|---|
编号( lD ) | 类型:long。用于标识不同的线程。不同的线程拥有不同的编号 | 是 | 某个编号的线程运行结束后,该编号可能被后续创建的线程使用。不同线程拥有的编号虽然不同,但是这种编号的唯一性只在Java 虚拟机的一次运行有效 。也就是说重启一个 Java 虚拟机(如重启 Web 服务器)后,某些线程的编号可能与上次 Java 虚拟机运行的某个线程的编号一样 , 因此该属性的值不适合用作某种唯一标识,特别是作为数据库中的唯一标识(如主键) |
名称(Name) | 类型:String。面向人(而非机器)的一个属性,用于区分不同的线程。默认值与线程的编号有关。默认值的格式为:“Thread-线程编号”,如“Thread-0” | 否 | Java 并不禁止我们将不同的线程的名称属性设置为相同的值。尽管如此,设置线程的名称属性有助于代码调试和问题定位 |
线程类别(Daemon) | 类型:boolean。值为true表示相应的线程为守护线程,否则表示相应的线程为用户线程。该属性的默认值与相应线程的父线程的该属性的值相同。 | 否 | 该属性必须在相应线程启动之前设置,即对setDaemon方法的调用必须在对start方法的调用之前,否则 setDaemon 方法会抛出IllegalThreadStateException异常。负责一些关键任务处理的线程不适宜设置为守护线程 |
优先级(Priority) | 类型:int。该属性本质上是给线程调度器的提示,用于表示应用程序希望哪个线程能够优先得以运行。Java 定义了 l~ 10 的 10个优先级 。默认值一般为 5(表示普通优先级)。对于具体的一个线程而言,其优先级的默认值与其父线程(创建该线程的线程)的优先级值相等 | 否 | 一般使用默认优先级即可。不恰当地设置该属性值可能导致严重的问题(线程饥饿) |
父线程和子线程之间的生命周期也没有必然的联系 。比如父线程运行结束后 ,子线程可以继续运行,子线程运行结束也不妨碍其父线程继续运行 。
1.2 线程状态
Java线程的状态可以使用监控工具查看,也可以通过 Thread.getState()调用来获取 。Thread.getState()的返回值类型 Thread.State 是一个枚举类型( Enum ) 。
Thread.State所定义的线程状态包括以下几种 :
状态 | 说明 |
---|---|
新建状态(New) | 新创建了一个线程对象,但还没有调用start()方法,就进入了新建状态。例如,Thread thread = new Thread()。 |
就绪状态(Runnable) | Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。 |
阻塞状态(Blocked) | 表示线程阻塞于锁。线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。同时放弃CPU使用权,暂时停止运行。 |
等待(WAITING) | 进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。同时放弃CPU使用权,暂时停止运行。例如:通过调用线程的wait()方法,让线程等待某工作的完成。 |
超时等待(TIMED_WAITING) | 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。该状态不同于WAITING,它可以在指定的时间后自行返回。 |
终止(TERMINATED) | 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。 |
下面这张图很好的将各个状态串接起来:
状态转化的特殊情况:(这里要结合wait说明)
从
Object.wait()
状态刚被唤醒时,通常不能立刻抢到monitor锁,那就会从Waiting先进入Blocked状态,抢到锁后再转换到Runnable状态如果发生异常,可以直接跳到Terminated状态,不必在遵循路径,比如可以从Waiting直接到Terminated
1.2.1 初始状态
实现Runnable接口和继承Thread可以得到一个线程类,new一个实例出来,线程就进入了初始状态。
1.2.2 就绪状态
就绪状态只是说你资格运行,调度程序没有挑选到你,你就永远是就绪状态。
调用线程的start()方法,此线程进入就绪状态。
当前线程sleep()方法结束,其他线程join()结束,等待用户输入完毕,某个线程拿到对象锁,这些线程也将进入就绪状态。
当前线程时间片用完了,调用当前线程的yield()方法,当前线程进入就绪状态。
锁池里的线程拿到对象锁后,进入就绪状态。
1.2.3 运行中状态
线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。
1.2.4 阻塞状态
阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态。
1.2.5 等待
处于这种状态的线程不会被分配CPU执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态。
1.2.6 超时等待
处于这种状态的线程不会被分配CPU执行时间,不过无须无限期等待被其他线程显示地唤醒,在达到一定时间后它们会自动唤醒。
一般习惯而言,把Blocked(1.2.4),Waiting(1.2.5),Timed_waiting(1.2.6)都称为阻塞状态。不仅仅是Blocked。
1.2.7 终止状态
当线程的run()方法完成时,或者主线程的main()方法完成时,我们就认为它终止了。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦终止了,就不能复生。
在一个终止的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。
1.2.8 方法调用与线程状态
每个锁对象都有两个队列,一个是同步队列(即就绪队列),一个是等待队列(即阻塞队列)。就绪队列存储了已就绪(将要竞争锁)的线程,阻塞队列存储了被阻塞的线程。当一个阻塞线程被唤醒后,才会进入就绪队列,进而等待CPU的调度;反之,当一个线程被wait后,就会进入阻塞队列,等待被唤醒。
1.2.8.1 等待队列
调用obj的wait(), notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj) 代码段内。
1.2.8.2 同步队列
当前线程想调用对象A的同步方法时,发现对象A的锁被别的线程占有,此时当前线程进入同步队列。简言之,同步队列里面放的都是想争夺对象锁的线程。
当一个线程1被另外一个线程2唤醒时,1线程进入同步队列,去争夺对象锁。
同步队列是在同步的环境下才有的概念,一个对象对应一个同步队列。、
线程等待时间到了或被notify/notifyAll唤醒后,会进入同步队列竞争锁,如果获得锁,进入RUNNABLE状态,否则进入BLOCKED状态等待获取锁。
代码示例:
/**
* 线程状态测试
*/
public class ThreadStateTest {
public static void main(String[] args) throws InterruptedException {
// testState1();
testState2();
}
/**
* 测试从 NEW-》RUNNABLE-》TIMED_WAITING-》RUNNABLE-》TERMINATED
*/
private static void testState1() throws InterruptedException {
Thread thread1 = new TestThreadState();
thread1.start();
Thread.sleep(1000);
// 该状态不同于WAITING,它可以在指定的时间后自行返回。
System.out.println("线程超时等待(TIMED_WAITING)状态:" + thread1.getState().name());
Thread.sleep(10000);
// 表示该线程已经执行完毕
System.out.println("线程终止(TERMINATED)状态:" + thread1.getState().name());
}
/**
* 测试从 NEW-》RUNNABLE-》WAITING-》RUNNABLE-》TERMINATED
*/
private static void testState2() throws InterruptedException {
Thread thread1 = new TestThreadStateA();
thread1.start();
Thread.sleep(1000);
// 该状态不同于WAITING,它可以在指定的时间后自行返回。
System.out.println("线程等待(WAITING)状态:" + thread1.ge