操作系统中线程的状态:新建
、就绪
、运行
、阻塞
、结束
。
但是 java.lang.Thead.State 中现线程的状态分为:NEW 新建
、RUNNABLE 可执行
、BLOCKED 阻塞
、WAITING 等待
、TIMED_WAITING 限时等待
、TERMINATED 终止
。
package java.lang.Thead.State;
public enum State {
NEW, // 新建
RUNNABLE, // 可执行:包含操作系统的就绪、运行两种状态
BLOCKED, // 阻塞
WAITING, // 等待
TIMED_WAITING, // 限时等待
TERMINATED; // 终止
}
线程创建成功但是没有调用start()方法启动的Thread线程实例都处于NEW状态。但是并不是Thread线程实例的start()方法一经调用,其状态就从NEW状态到RUNNABLE状态,调用start()方法后并不意味着线程立即获取CPU时间片并且立即执行,中间需要一系列操作系统的内部操作。
NEW状态的Thread实例调用了start()方法后,线程需要获取了CPU时间片,线程的run()方法开始执行,线程的状态将变成RUNNABLE状态。Java把Ready(就绪)和Running(执行)两种状态合并为一种状态:RUNNABLE(可执行)状态(或者可运行状态)。调用了线程的start()实例方法后,线程就处于就绪状态。此线程获取到CPU时间片后,开始执行run()方法中的业务代码,线程处于执行状态。
在操作系统中,处于运行状态的线程在CPU时间片用完之后,又回到就绪状态,等待CPU的下一次调度。操作系统线程在就绪状态和执行状态之间被系统反复地调度,直到线程的代码逻辑执行完成或者异常终止。
就绪状态和运行状态都是操作系统中的线程状态。在Java语言中,将这两种状态合并成同一种状态—>RUNNABLE状态。在Thread.State枚举类中,没有定义线程的就绪状态和运行状态,只是定义了RUNNABLE状态
(1)就绪状态
就绪状态仅仅表示线程具备运行资格,如果没有被操作系统的调度程序挑选中,线程就永远处于就绪状态。
当前线程进入就绪状态的条件大致包括以下几种:
- 调用线程的start()方法,此线程就会进入就绪状态。
- 当前线程的执行时间片用完。
- 线程睡眠(Sleep)操作结束。
- 对其他线程合入(Join)操作结束。
- 等待用户输入结束。
- 线程争抢到对象锁(Object Monitor)。
- 当前线程调用了yield()方法出让CPU执行权限。
(2)执行状态
线程调度程序从就绪状态的线程中选择一个线程,被选中的线程状态将变成执行状态。这也是线程进入执行状态的唯一方式。
处于WAITING(无限期等待)状态的线程不会被分配CPU时间片,需要被其他线程显式地唤醒,才会进入就绪状态。线程调用以下3种方法会让自己进入无限等待状态:
能让线程处于限时等待 (TIMED_WAITING) 状态的操作大致有以下几种:
处于BLOCKED(阻塞)状态的线程并不会占用CPU资源,以下情况会让线程进入阻塞状态:
(1)线程等待获取锁
等待获取一个锁,而该锁被其他线程持有,则该线程进入阻塞状态。当其他线程释放了该锁,并且线程调度器允许该线程持有该锁时,该线程退出阻塞状态。
(2)IO阻塞
线程发起了一个阻塞式IO操作后,如果不具备IO操作的条件,线程就会进入阻塞状态。IO包括磁盘IO、网络IO等。IO阻塞的一个简单例子:线程等待用户输入内容后继续执行。
进入BLOCKED状态、WAITING状态、TIMED_WAITING状态的线程都会让出CPU的使用权;另外,等待或者阻塞状态的线程被唤醒后,进入Ready状态,需要重新获取时间片才能接着运行
处于RUNNABLE状态的线程在run()方法执行完成之后就变成终止状态TERMINATED了。当然,如果在run()方法执行过程中发生了运行时异常而没有被捕获,run()方法将被异常终止,线程也会变成TERMINATED状态。
sleep()
的作用是让目前正在执行的线程休眠,让CPU去执行其他的任务。调用 sleep()
的线程会进入到 限时等待(TIMED_WAITING)
状态。
package java.lang;
public class Thread implements Runnable {
……
// 使目前正在执行的线程休眠millis毫秒
public static native void sleep(long millis) throws InterruptedException;
// 使目前正在执行的线程休眠millis毫秒,nanos纳秒
public static void sleep(long millis, int nanos) throws InterruptedException{……};
}
interrupt() 方法的作用:
如果线程处于阻塞状态(如调用了Object.wait()方法),就会立马退出阻塞,并抛出InterruptedException异常,线程就可以通过捕获InterruptedException来做一定的处理,然后让线程退出。即,如果线程被Object.wait()、Thread.join()和Thread.sleep()三种方法之一阻塞,此时调用该线程的interrupt()方法,该线程将抛出一个InterruptedException中断异常(该线程必须事先预备好处理此异常),从而提早终结被阻塞状态。
如果此线程正处于运行之中,线程就不受任何影响,继续运行,仅仅是线程的中断标记被设置为true。所以,程序可以在适当的位置通过调用isInterrupted()方法来查看自己是否被中断,并执行退出操作。
如果线程的interrupt()方法先被调用,然后线程开始调用阻塞方法进入阻塞状态,InterruptedException异常依旧会抛出。如果线程捕获InterruptedException异常后,继续调用阻塞方法,将不再触发InterruptedException异常。
package java.lang;
public class Thread implements Runnable {
……
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
@HotSpotIntrinsicCandidate
private native boolean isInterrupted(boolean ClearInterrupted);
}
示例:
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
@Slf4j
public class InterruptDemo {
static class InterruptThread extends Thread {
static int threadSeqNumber = 1;
public InterruptThread() {
super("InterruptThread-" + threadSeqNumber);
threadSeqNumber++;
}
@Override
public void run() {
try {
log.info("{}:状态->{} ---> 线程即将进入sleep, 50s", Thread.currentThread().getName(),
Thread.currentThread().getState());
TimeUnit.SECONDS.sleep(50);
log.info("{}:状态->{} ---> 线程获取到CPU时间片,继续执行", Thread.currentThread().getName(),
Thread.currentThread().getState());
} catch (InterruptedException e) {
e.printStackTrace();
log.error("{}:状态->{} ---> 线程被打断", Thread.currentThread().getName(),
Thread.currentThread().getState());
return;
}
}
}
public static void main(String[] args) throws InterruptedException {
InterruptThread thread1 = new InterruptThread();
thread1.start();
InterruptThread thread2 = new InterruptThread();
thread2.start();
InterruptThread thread3 = new InterruptThread();
thread3.start();
// 主线程 sleep 5s
TimeUnit.SECONDS.sleep(5);
// 注意:这个时候 thread1、thread2、thread3 应该都已经 sleep了,即进入限时等待状态(TIMED_WAITING)
log.info("{}:状态->{}", thread1.getName(), thread1.getState());
log.info("{}:状态->{}", thread2.getName(), thread2.getState());
log.info("{}:状态->{}", thread3.getName(), thread3.getState());
// thread2 Interrupt(),对于线程thread2会立即异常并捕获,执行: log.error("{} ---> 线程被打断", Thread.currentThread().getName());
thread2.interrupt();
// thread3 Interrupt(),对于线程thread3会立即异常并捕获,执行: log.error("{} ---> 线程被打断", Thread.currentThread().getName());
thread3.interrupt();
log.info("{} ---> 主线程执行结束", Thread.currentThread().getName());
}
}
join()
方法可以说是线程合并。现在线程A在执行过程中对线程B的执行有依赖,具体的依赖为:线程A需要将线程B的执行流程合并到自己的执行流程中(至少表面如此),这就是线程合并,被动方线程B可以叫作被合并线程。调用join()方法的语句可以理解为合并点,合并的本质是:线程A需要在合并点等待,一直等到线程B执行完成,或者等待超时。
package java.lang;
public class Thread implements Runnable {
……
// 重载版本1:此方法会把当前线程变为TIMED_WAITING,直到被合并线程执行结束
public final void join() throws InterruptedException {
join(0);
}
// 重载版本2:此方法会把当前线程变为TIMED_WAITING,直到被合并线程执行结束,或者等待
// 被合并线程执行millis的时间
public final synchronized void join(long millis) throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
// 重载版本3:此方法会把当前线程变为TIMED_WAITING,直到被合并线程执行结束,或者等待
// 被合并线程执行millis+nanos的时间
public final synchronized void join(long millis, int nanos) throws InterruptedException {
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
millis++;
}
join(millis);
}
}
调用join()方法的要点:
join()方法是实例方法,需要使用被合并线程的句柄(或者指针、变量)去调用,如threadb.join()。执行threadb.join()这行代码的当前线程为合并线程(甲方),进入TIMED_WAITING等待状态,让出CPU。
如果设置了被合并线程的执行时间millis(或者millis+nanos),并不能保证当前线程一定会在millis时间后变为RUNNABLE。
如果主动方合并线程在等待时被中断,就会抛出InterruptedException受检异常。
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
@Slf4j
public class JoinDemo {
static class JoinRunnable extends Thread {
public JoinRunnable(String threadName) {
super(threadName);
}
@Override
public void run() {
log.info("{}: 状态 -> {} ---> 即将进入 sleep ,模仿执行业务逻辑",
Thread.currentThread().getName(), Thread.currentThread().getState());
try {
TimeUnit.SECONDS.sleep(30);
log.info("{}: 状态 -> {} ---> 执行完!",
Thread.currentThread().getName(), Thread.currentThread().getState());
} catch (InterruptedException e) {
log.error("{}: 状态 -> {} ---> 即将进入InterruptedException异常",
Thread.currentThread().getName(), Thread.currentThread().getState());
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread A1 = new JoinRunnable("乙方线程A1");
A1.start();
log.info("{}:状态->{}", A1.getName(), A1.getState());
// 执行 join
A1.join();
// 当B1 在 join 期间,使用插件查main线程的状态
log.info("{}:状态->{}", Thread.currentThread().getName(), Thread.currentThread().getState());
log.info("main 执行又开始执行了");
Thread A2 = new JoinRunnable("乙方线程A2");
A2.start();
A2.join(70 * 1000);
log.info("main 执行又又又又开始执行了");
log.info("maim 终于执行完了");
}
}
线程的 yield()
的作用是让目前正在执行的线程放弃当前的执行,让出CPU的执行权限,使得CPU去执行其他的线程。处于让步状态的JVM层面的线程状态仍然是RUNNABLE状态,但是该线程所对应的操作系统层面的线程从状态上来说会从执行状态变成就绪状态。线程在yield时,线程放弃和重占CPU的时间是不确定的,可能是刚刚放弃CPU,马上又获得CPU执行权限,重新开始执行.
Thread.yeid()方法有以下特点:
(1)yield仅能使一个线程从运行状态转到就绪状态,而不是阻塞状态。
(2)yield不能保证使得当前正在运行的线程迅速转换到就绪状态。
(3)即使完成了迅速切换,系统通过线程调度机制从所有就绪线程中挑选下一个执行线程时,就绪的线程有可能被选中,也有可能不被选中,其调度的过程受到其他因素(如优先级)的影响。
Java的多线程系统建立于Thread类、Thread类的方法,以及Thread类的共伴接口Runnable基础上。Thread类封
装了线程的执行。由于不能直接引用运行着的线程的状态,故需要通过它的代理处理它,于是Thread实例产生了。要想
创建一个新的线程,我们的程序必须扩展(继承)Thread类或实现Runnable接口。
Thread类定义了一些方法来帮助管理线程,具体如下:
方法 | 意义 |
---|---|
run() | 线程的入口点 |
join() | 等待一个线程终止 |
start() | 通过调用运行方法来启动线程 |
sleep() | 在一段时间内挂起线程 |
isAlive() | 判定线程是否仍在运行 |
getName() | 获取线程名称 |
getPriority() | 获取线程优先级 |
主线程:
当Java 程序启动时,一个线程立即运行,该线程通常叫做程序的主线程(main thread)。获取主线程的方式 :
Thread.currentThread() 。currentThread() 是Thread类的公有的静态成员。static Thread currentThread()
主线程的重要性体现在两方面:
创建线程有两种方式:
- 实现Runnable接口。
- 继承Thread类。
class RunnableTest implements Runnable{
@Override
public void run()
{
// 业务实现……
}
}
步骤2:创建一个Runnable实现类对象
Runnable runnableTest = new RunnableTest ();
步骤3:由Runnable创建一个Thread对象
Thread thread = new Thread(runnableTest );
步骤4:启动线程
thread .start();
到这里,一个线程就创建完成了。需要说明的是:线程的执行流程很简单,当执行代码thread .start();时,就会执
行RunnableTest 对象中的void run() 方法。该方法执行完成后,线程就消亡了。
public class NewThreadA implements Runnable{
Thread t;
NewThreadA() {
// 创建新的 Thread 对象
t = new Thread(this, "NewThreadA Thread");
System.out.println("NewThreadA Thread" + t);
// 启动线程
t.start();
}
/**
* 重写run方法,线程业务逻辑都是在run 方法中
*/
@Override
public void run() {
try {
for (int i = 5; i > 0; i--) {
System.out.println("NewThreadA Thread: " + i);
// 线程挂起1秒
Thread.sleep(500);
}
} catch (Exception e) {
System.out.println("NewThreadA Thread interrupted !");
e.printStackTrace();
}
System.out.println("NewThreadA thread exiting!");
}
}
测试代码:
public class ThreadDemoA {
public static void main(String[] args) {
// 声明该新线程对象时,在构造方法中即已经调用启动线程,
// 详情看NewThread代码
new NewThreadA();
try {
for(int i = 5; i > 0 ; i--) {
System.out.println("Main Thread: " + i);
// 休眠1秒
Thread.sleep(1000);
}
} catch (Exception e) {
System.out.println("Main thread interrupted !");
e.printStackTrace();
}
System.out.println("Main thread exiting !");
}
}
public class NewThreadB implements Runnable {
@Override
public void run() {
for (int i = 5; i > 0 ; i--) {
try {
System.out.println(Thread.currentThread().getName() + ": " + i);
Thread.sleep(500);
} catch (Exception e) {
System.out.println(Thread.currentThread().getName() + " interrupted !");
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " exiting!");
}
}
测试代码:
public class ThreadDemoB {
public static void main(String[] args) {
Runnable newThreadB = new NewThreadB();
Thread thread = new Thread(newThreadB, "NewThreadB Thread");
thread.start();
try {
for(int i = 5; i > 0 ; i--) {
System.out.println("Main Thread: " + i);
// 休眠1秒
Thread.sleep(1000);
}
} catch (Exception e) {
System.out.println("Main thread interrupted !");
e.printStackTrace();
}
System.out.println("Main thread exiting !");
}
}
创建线程的另一种方法,是创建一个新类来继承Thread类,然后创建该类的实例。当一个类继承Thread时,它必
须重载run()方法,这个run()方法是新线程的入口。同时,它也必须调用start()方法去启动新线程执行。
public class NewThreadC extends Thread{
NewThreadC (){
// supper("")调用了Thread的构造函数 public Thread(String threadName);
// 给线程设置线程名称
super("child thread C");
System.out.println(Thread.currentThread().getName()+this);
// 启动线程
start();
}
public void run () {
for (int i = 5; i > 0; i--) {
try {
System.out.println(Thread.currentThread().getName() + ": " + i);
Thread.sleep(500);
} catch (Exception e) {
System.out.println(Thread.currentThread().getName() + " interrupted !");
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " exiting!");
}
}
测试代码:
public class ThreadDemoC {
public static void main(String[] args) {
new NewThreadC();
try {
for(int i = 5; i > 0 ; i--) {
System.out.println("Main Thread: " + i);
// 休眠1秒
Thread.sleep(1000);
}
} catch (Exception e) {
System.out.println("Main thread interrupted !");
e.printStackTrace();
}
System.out.println("Main thread exiting !");
}
}
public class NewThreadD extends Thread{
public void run () {
// 设置线程名称
this.setName("child thread D");
for (int i = 5; i > 0; i--) {
try {
System.out.println(Thread.currentThread().getName() + ": " + i);
Thread.sleep(500);
} catch (Exception e) {
System.out.println(Thread.currentThread().getName() + " interrupted !");
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " exiting!");
}
}
测试代码:
public class ThreadDemoD {
public static void main(String[] args) {
NewThreadD thread = new NewThreadD();
thread.start();
try {
for(int i = 5; i > 0 ; i--) {
System.out.println("Main Thread: " + i);
// 休眠1秒
Thread.sleep(1000);
}
} catch (Exception e) {
System.out.println("Main thread interrupted !");
e.printStackTrace();
}
System.out.println("Main thread exiting !");
}
}
线程模块,计划用一个系列博文整理总结。上一篇博文: 《线程系列2:Thread 的 join() 方法》