java多线程的运行状态
本文转载了部分 CSDN博主「Hatoandaburedo」的原创文章,也欢迎访问我的个人博客
首先一图概之:
- 当一个线程执行了start方法后,不代表这个线程就会立即被执行,只代表这个线程处于可运行的状态,最终由OS的线程调度来决定哪个可运行状态下的线程被执行。
- 一个线程一次被选中执行是有时间限制的,这个时间段叫做CPU的时间片,当时间片用完但线程还没有结束时,这个线程又会变为可运行状态,等待OS的再次调度;在运行的线程里执行Thread.yeild()方法同样可以使当前线程变为可运行状态。
- 在一个运行中的线程等待用户输入、调用Thread.sleep()、调用了其他线程的join()方法,则当前线程变为阻塞状态。
- 阻塞状态的线程用户输入完毕、sleep时间到、join的线程结束,则当前线程由阻塞状态变为可运行状态。
- 运行中的线程调用wait方法,此线程进入等待队列。
- 运行中的线程遇到synchronized同时没有拿到对象的锁标记、等待队列的线程wait时间到、等待队列的线程被notify方法唤醒、有其他线程调用notifyAll方法,则线程变成锁池状态。
- 锁池状态的线程获得对象锁标记,则线程变成可运行状态。
运行中的线程run方法执行完毕或main线程结束,则线程运行结束。
一、新建状态:
新建状态就是我们通过new关键字实例化出一个线程类的对象时的状态。
`public class IsAThread extends Thread{
@Override
public void run() {
System.out.println("这是一个线程类");
}
}
public static void main(String[] args) {
// 线程进入新建状态
IsAThread isAThread = new IsAThread();
}
`此时,我们就说 isAThread 这个线程对象进入了新建状态
2. 可运行状态:
当我们调用了新建状态下的线程对象的 start() 方法来启动这个线程,并且线程对象已经准备好了除CPU时间片段之外的所有资源后,该线程对象会被放入“可运行线程池”中等待CPU分配时间片段给自身。在自身获得CPU的时间片段之后便会执行自身 run() 方法中定义的逻辑,示例中的线程对象的 run() 方法是打印了 “这是一个线程类” 这么一句话到控制台
public static void main(String[] args) {
// 线程进入新建状态
IsAThread isAThread = new IsAThread();
// 线程进入可运行状态
isAThread.start();
}
- 运行状态:
运行状态的线程在分配到CPU的时间片段之后,便会真正开始执行线程对象 run() 方法中定义的逻辑代码了,示例中的线程对象的 run() 方法是打印了 “这是一个线程类” 这么一句话到控制台。
1)但是生产环境中的线程对象的 run() 方法一般不会这么简单,可能业务代码逻辑复杂,造成CPU的时间片段所规定的时长已经用完之后,业务代码还没执行完;
2)或者是当前线程主动调用了Thread.yield()方法来让出自身的CPU时间片段。
此时,运行状态会转回可运行状态,等待下一次分配到CPU时间片段之1`后继续执行未完成的操作
- 阻塞状态:
阻塞状态指的是运行状态中的线程因为某种原因主动放弃了自己的CPU时间片段来让给其他线程使用,可能的阻塞类型及原因有:
线程被调用了 Object.wait() 方法后会立刻释放掉自身获取到的锁并进入“等待池”进行等待,等待池中的线程被其他线程调用了 Object.notify() 或 Object.notifyAll() 方法后会被唤醒从而从“等待池”进入到“等锁池”,“等锁池”中的线程在重新获取到锁之后会转为可运行状态。
值得注意的是:wait()和notify()/notifyAll()只能用在被synchronized包含的代码块中,而说明中的Object.wait和Object.notify的这个Object实际上是指作为synchronized锁的对象。
例如:
我们创建两个线程类,StringBufferThread和StringBufferThread2,这两个类唯一的不同就是run()方法的实现。
StringBufferThread:用到了 java.util.concurrent.CountDownLatch;(倒计时器)其对象方法.wait();可一直为阻塞状态,直到计数为0
import java.util.concurrent.CountDownLatch;
class StringBufferThread implements Runnable {
StringBuffer sb;
CountDownLatch countDownLatch;
StringBufferThread(StringBuffer sb, CountDownLatch countDownLatch) {
this.sb = sb;
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
// StringBufferThread这个类作为锁
synchronized (StringBufferThread.class) {
sb.append("This is StringBufferThread1\n");
countDownLatch.countDown();
}
}
}
class StringBufferThread2 implements Runnable{
StringBuffer sb;
CountDownLatch countDownLatch;
StringBufferThread2(StringBuffer sb, CountDownLatch countDownLatch) {
this.sb = sb;
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
// StringBufferThread这个类作为锁
synchronized (StringBufferThread.class) {
sb.append("This is StringBufferThread2\n");
countDownLatch.countDown();
}
}
}
public class Demo {
public static void main(String[] args) throws InterruptedException {
StringBuffer tipStr = new StringBuffer();
// 使用CountDownLatch保证子线程全部执行完成后主线程才打印结果
CountDownLatch countDownLatch = new CountDownLatch(2);
StringBufferThread stringBufferThread = new StringBufferThread(tipStr, countDownLatch);
StringBufferThread2 stringBufferThread2 = new StringBufferThread2(tipStr, countDownLatch);
Thread thread1 = new Thread(stringBufferThread);
Thread thread2 = new Thread(stringBufferThread2);
thread1.start();
/*
为了保证先让thread1执行,我们让thread1执行后主线程睡眠5秒钟再执行thread2,
如果不进行睡眠的话我们无法控制CPU分配时间片段,有可能直接就先分配给thread2线程了,
这样就会造成thread2先于thread1执行
*/
Thread.sleep(5000);
thread2.start();
// 调用countDownLatch.await()保证子线程全部执行完后主线程才继续执行
countDownLatch.await();
System.out.println(tipStr.toString());
}
}
接下来,修改StringBufferThread类:
import java.util.concurrent.CountDownLatch;
class StringBufferThread implements Runnable {
StringBuffer sb;
CountDownLatch countDownLatch;
StringBufferThread(StringBuffer sb, CountDownLatch countDownLatch) {
this.sb = sb;
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
// StringBufferThread这个类作为锁
synchronized (StringBufferThread.class) {
try {
/*
在将字符串追加到StringBuffer前,调用锁对象StringBufferThread这个类的wait(),
来使本子线程进行休眠
*/
StringBufferThread.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
sb.append("This is StringBufferThread1\n");
countDownLatch.countDown();
}
}
}
class StringBufferThread2 implements Runnable{
StringBuffer sb;
CountDownLatch countDownLatch;
StringBufferThread2(StringBuffer sb, CountDownLatch countDownLatch) {
this.sb = sb;
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
// StringBufferThread这个类作为锁
synchronized (StringBufferThread.class) {
sb.append("This is StringBufferThread2\n");
/*
在将字符串追加到StringBuffer后,调用锁对象StringBufferThread这个类的notify(),
来唤醒本这个锁对象的wait()方法等待的子线程,本例中就是main方法中的stringBufferThread这个子线程
*/
StringBufferThread.class.notify();
countDownLatch.countDown();
}
}
}
也就是在字符串”This is StringBufferThread2\n”追加到StringBuffer之后调用了 notify() 方法来唤醒被 StringBufferThread.class 这个锁等待的线程,本例中就是main方法中的stringBufferThread这个子线程,本唤醒的子线程会进入等锁池,等待重新争取到锁之后,会继续执行代码。
main方法不变,我们来看看执行结果:
与我们预想的一样,因为thread1在追加字符串到StringBuffer对象之前调用了锁对象的wait(),就立即释放掉了自身获取到的锁并进入等待池中了,这时thread2获取了锁,将字符串”This is StringBufferThread2\n”首先追加到了StringBuffer对象的开头,然后调用锁对象的notify()方法唤醒了thread1,被唤醒的thread1重新获取锁之后,才将自身的字符串”This is StringBufferThread1\n”追加到了StringBuffer对象的末尾。
4.2 同步阻塞:
线程执行到了被 synchronized 关键字保护的同步代码时,如果此时锁已经被其他线程取走,则该线程会进入到“等锁池”,直到持有锁的那个线程释放掉锁并且自身获取到锁之后,自身会转为可运行状态。
4.3 其他阻塞:
1)线程中执行了 Thread.sleep(xx) 方法进行休眠会进入阻塞状态,直到Thread.sleep(xx)方法休眠的时间超过参数设定的时间而超时后线程会转为可运行状态。Thread.sleep(xx)方法的使用在本文很多例子都体现了,就不演示了。
2)线程ThreadA中调用了ThreadB.join()方法来等待ThreadB线程执行完毕,从而ThreadA进入阻塞状态,直到ThreadB线程执行完毕后ThreadA会转为可运行状态。