本篇讲解线程的基本概念,包括线程是什么、线程的API及使用规范。
线程的状态
JDK中线程有五种状态:初始状态NEW、可运行状态RUNNABLE、运行状态RUNNING、阻塞状态BLOCKED及终止状态TERMINATED。这五种状态的关系如下图:
初始状态: 线程被创建,线程对象生成,即进入了初始状态NEW。这时只是生成了一个Java对象,操作系统还没有感知。
可运行状态: 线程启动后,操作系统已经感知,进入操作系统的线程调度框架,可以被分配CPU;
运行状态:当有CPU空闲时,操作系统线程调度系统分配CPU,线程进入运行状态,开始执行run代码;
阻塞状态:线程在执行过程调用一个阻塞API或等待某个事件时,线程的状态会进入阻塞状态,释放CPU;当执行条件得到满足后,如阻塞API返回、等待事件发生,线程重新时入可运行状态,等待操作系统分配CPU;
终止状态:线程结束运行,有三种情况可能初线程结束:run终止正常退出、收到stop/interrupt调用终止、线程出现异常。
线程状态的切换
以上状态是从操作系统的角度看的实际运行状态,Java语言层面的实现与此略有差异,Java语言中线程一共有六种状态,分别是:
初始化状态 NEW;
可运行/运行状态 RUNNABLE;
阻塞状态 BLOCKED;
无限时等待状态 WAITING;
有限时等待状态 TIMED_WAITING;
终止状态 TERMINATED;
Java把OS的可运行与运行状态合一,因为这个语言层面上控制不了;把阻塞状态拆分成三种状态:BLOCKED、WAITING与TIMED_WAITING,对应不同阻塞场景。合并后的场景如下:
RUNNABLE与三种阻塞状态的转换
RUNNABLE到BLOCK状态
只有一种情况会触发这种转换,就是线程等待synchronized时。syncchronized修改的方法、代码同一时刻只允许一个线程执行,当有线程占用时,其他线程只能等待。当等待线程获取到synchronized隐式锁时,会切回到RUNNABLE状态。
需要注意的一个问题是,调用阻塞API时,如IO,Java线程对象的状态不会是BLOCKED。虽然实际上OS会将此线程休眠,等待再次调度。但Java语言层面上感知不到,也不用感知。
2. RUNNABLE到WAITING状态
有三种情况会让线程从RUNNABLE切换到WAITING状态:
线程获取synchronized后,调用了无参数的Object.wait()方法;这种情况会在“等待-通知机制”一节中详细说明;
线程执行时调用了其他线程的thread.join()方法,等待其他线程结束,再重新变为RUNABLE状态;
调用lockSupport.park()方法,这是底层方法,我们不会用到,不介绍;
3. RUNNABLE到TIMED_WAITING状态
到TIMED_WAITING与WAITING类似,只是多了一个time参数。具体来说有四种情况:
有超时参数的sleep,Thread.sleep(long);
有超时参数的wait,Object.wait(long);
有超时参数的join,Thread.join(long);
有超时参数的LockSupport(为了完整正确,我们不会用到的);
基本的线程使用
线程的创建方法:Thread/Runnable
线程的创建有两种基本的方式:Thread对象与Runnable接口。使用Thread创建线程:
// 自定义线程对象
class MyThread extends Thread {
public void run() {
// 线程需要执行的代码
......
}
}
// 创建线程对象
MyThread myThread = new MyThread();使用Runnable创建线程:
// 实现 Runnable 接口
class Runner implements Runnable {
@Override
public void run() {
// 线程需要执行的代码
......
}
}
// 创建线程对象
Thread thread = new Thread(new Runner());线程的启动方式
线程的启动只有一种方式,start方法:
MyThread myThread = new MyThread(); // 从 NEW 状态转换到 RUNNABLE 状态 myThread.start();
线程的让渡方式:sleep/join/yield
让渡线程一般有三种方式:
sleep:这是Thread的静态方法,一定要带超时参数,表示执行sleep的线程进入TIMED_WAITING状态;
yield:也是Thread的静态方法,表示暂时让渡CPU,从OS层面上看,线程从运行状态到可执行状态,重试等时间片;
join:Thread的对象方法,在threadA的run中调用threadB.join,表示threadA等threadB执行结束。
线程的终止方法:stop/interrupt
调用stop方法可以直接终止线程,但此方法已经被标记为@Deprecated,生产环境上也不能使用。因为stop方式过于粗暴。直接清除线程对象,没给线程任何清理现场的机会。如获取了一个锁,还没有来得及释放,就stop了。那这个锁就再也没有机会释放了。这在生产环境上是不可以接受的。
interrupt就温和很多,interrupt方法通知线程需要中止了。线程收到这个通知,可以选择清理现场,结束任务,也可以选择继续执行(但是不建议)。
线程获取interrupt的方式两种,接收InterruptedException异常与主动查看interrupt标志位。一些阻塞操作会抛出InterruptedException,如wait()、join()、sleep()等。同时线程还提供了isInterrupted()方法,当执行线程的interrupted方法后,此方法返回true。
线程的使用规范
尽量不要直接创建线程
在实际的生产环境上,我们强烈建议不要直接使用线程,而要使用线程池的方式。有如下原因:
线程的创建与销毁,有巨大的消耗,线程池可以避免此问题;
线程是一种系统资源,使用线程池可以管控线程资源;
正确地启动线程,不要调用run
不要直接调用run方法,run只是一个方法,不会启动线程;
3. 正确地终止线程,不要调用stop
不要使用stop对线程进行终止,线程的正确终止方法会在后面详细介绍;
4. 不要使用yield方法做线程同步
yield是不可靠的,只是暂时让线程重新等待,下次什么时机执行是不可控制的。
总得来说,我们建议不要直接使用Thread的基础API,Java提供了更多高级的API、机制和工具实现并发的各种场景。线程基础API,是最后的选择。





































苏公安网备 32011402010933号