Java笔记31
线程状态
五大状态
- 一个线程的生命周期里共有五大状态:
- 1. 新建状态(New):线程对象被创建后,就进入了新建状态。例如,
Thread thread = new Thread()
。 - 2. 就绪状态(Runnable):也被称为“可执行状态”。线程对象被创建后,当调用了该对象的
start()
方法启动该线程,则该线程立即进入就绪状态。例如,thread.start()
。注意:若某个线程处于就绪状态,并不意味着该线程将立刻被执行,只是意味着该线程随时可能被CPU调度执行。 - 3. 运行状态(Running):当CPU选定了一个处于就绪状态的线程,并进行执行,这时线程就进入了运行状态。注意:线程只能从就绪状态进入到运行状态,不能直接从阻塞状态进入到运行状态。
- 4. 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,代码不再继续往下执行,而是在等待。直到阻塞解除之后,线程重新进入就绪状态,然后再等待CPU调度执行,从就绪状态进入运行状态。阻塞的情况分三种:
- (1) 等待阻塞:通过调用线程的
wait()
方法,让线程等待某工作完成,即该线程进入等待阻塞状态。 - (2) 同步阻塞:线程在获取
synchronized
同步锁失败时(因为锁被其它线程所占用),该线程会进入同步阻塞状态。 - (3) 其他阻塞:通过调用线程的
sleep()
或join()
方法或者发出了I/O请求,可以使线程进入阻塞状态。当sleep()
状态超时、或者join()
等待线程终止或超时、或者I/O处理完毕时,线程将重新转入就绪状态。
- (1) 等待阻塞:通过调用线程的
- 5. 死亡状态(Dead):线程执行结束或者因异常中断退出了
run()
方法,该线程结束生命周期进入到死亡状态。注意:一旦线程进入了死亡状态,就不能再调用start()
,也就不能再次启动了。
- 1. 新建状态(New):线程对象被创建后,就进入了新建状态。例如,
线程方法
方法 | 说明 |
---|---|
setPriority(int newPriority) | 更改线程的优先级 |
static void sleep(long millis) | 在指定的毫秒数内让当前正在执行的线程休眠 |
void join() | 等待该线程终止 |
static void yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
void interrupt() | 中断线程,别用这个方式 |
boolean isAlive() | 测试线程是否处于活动状态 |
停止线程
- 不推荐使用 JDK 提供的
stop()
、destroy()
方法【已废弃】。 - 推荐线程自己停止下来。
- 建议使用一个标志位进行终止变量:
boolean flag=true
,当flag=false
,则终止线程运行。
使用标志位终止线程
package com.clown.state;
//测试停止线程
//1. 建议线程正常停止 ---> 利用次数,不建议死循环
//2. 建议使用标志位 ---> 设置一个标志位
//3. 不要使用 stop()或者 destroy()等过时或者 JDK不建议使用的方法
public class TestStop implements Runnable {
//1. 设置一个标志位
private boolean flag = true;
@Override
public void run() {
int i = 0;
//2. 线程体使用该标志位
while (flag) {
System.out.println("run...Thread" + i++);
}
}
//3. 设置一个公开的方法停止线程,转换标志位
public void stopThread() { //自己写一个 stopThread()方法
this.flag = false;
}
public static void main(String[] args) {
TestStop testStop = new TestStop();
new Thread(testStop).start(); //启动线程
for (int j = 0; j < 1000; j++) {
System.out.println("main" + j);
if (j == 900) { //当 j == 900 时,停止线程
//4. 调用 stopThread()方法转换标志位,让线程停止
testStop.stopThread();
System.out.println("线程该停止了");
}
}
}
}
- 运行结果:
线程休眠 - sleep()
sleep(时间)
指定当前线程阻塞的毫秒数;sleep
存在异常:InterruptedException
;sleep
时间达到后线程进入就绪状态;sleep
可以模拟网络延时,倒计时等。- 每一个对象都有一个锁,
sleep
不会释放锁;
模拟延时
- 我们使用之前学习初识并发问题时使用过的一个案例:多个人买限量的火车票:
- 如果我们在此案例中不使用模拟延时,则代码如下:
package com.clown.state;
//不使用模拟网络延时
public class TestSleep implements Runnable {
//票数
private int ticketNums = 10;
@Override
public void run() {
while (true) {
if (ticketNums<=0) {
break;
}
//currentThread(); 返回对当前正在执行的线程对象的引用
System.out.println(Thread.currentThread().getName() + " --> 拿到了第" + ticketNums-- + "张票");
}
}
public static void main(String[] args) {
//创建线程对象
TestSleep ticket = new TestSleep();
//将线程对象丢入 Runnable接口的实现类(Thread)中,再调用 start()方法来开启线程
new Thread(ticket, "小明").start();
new Thread(ticket, "老师").start();
new Thread(ticket, "黄牛党").start();
}
}
- 运行结果:
- 由于多线程程序中线程的执行顺序是由CPU调度的,每次运行程序得到的结果都可能不一样,所以上面的运行结果只是诸多可能的结果之一。
- 但就单从上面的运行结果来看,结果中的数据并没有发生紊乱,我们并不能通过此次的运行结果发现我们的程序存在并发问题。
- 下面,我们给上面的程序加上模拟网络延时:
package com.clown.state;
//使用模拟网络延时:放大问题的发生性
public class TestSleep implements Runnable {
//票数
private int ticketNums = 10;
@Override
public void run() {
while (true) {
if (ticketNums<=0) {
break;
}
//模拟延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
//currentThread(); 返回对当前正在执行的线程对象的引用
System.out.println(Thread.currentThread().getName() + " --> 拿到了第" + ticketNums-- + "张票");
}
}
public static void main(String[] args) {
//创建线程对象
TestSleep ticket = new TestSleep();
//将线程对象丢入 Runnable接口的实现类(Thread)中,再调用 start()方法来开启线程
new Thread(ticket, "小明").start();
new Thread(ticket, "老师").start();
new Thread(ticket, "黄牛党").start();
}
}
- 运行结果:
- 可以发现,当我们使用模拟网络延时后,很明显就能发现我们程序的运行结果出现了异常,这体现了使用模拟网络延时的作用:放大问题的发生性
模拟倒计时
- 我们使用
sleep()
方法写一个程序来模拟数字10到1的倒计时:
package com.clown.state;
//模拟倒计时
public class TestSleep2 {
public static void main(String[] args) {
try {
tenDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//模拟倒计时 10 --> 1
public static void tenDown() throws InterruptedException {
int num = 10;
while (true) {
Thread.sleep(1000);
System.out.println(num--);
if (num <= 0) {
break;
}
}
}
}
- 运行结果:
打印当前系统时间
- 我们使用
sleep()
方法写一个程序来实现每隔一秒就打印一次当前的系统时间:
package com.clown.state;
import java.text.SimpleDateFormat;
import java.util.Date;
//打印当前系统时间
public class TestSleep3 {
public static void main(String[] args) {
Date startTime = new Date(System.currentTimeMillis()); //获取系统当前时间
while (true) {
try {
Thread.sleep(1000); //休眠 1秒
System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime)); //格式化 startTime,以给定样式的字符串形式打印当前的系统时间
startTime = new Date(System.currentTimeMillis()); //更新当前时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
- 运行结果:
线程礼让 - yield()
-
yield()
方法,礼让线程,让当前正在执行的线程暂停,但不阻塞。 -
将线程从运行状态转为就绪状态。
-
礼让只是让CPU重新调度,重新调度后的线程执行顺序仍是由CPU决定的,所以礼让并不能保证一定成功!
-
下面我们写一个线程类
MyYield
继承Runnable
接口,并启动a
、b
两个线程: -
- 不使用
yield()
方法礼让线程:
- 不使用
package com.clown.state;
//测试礼让线程
//礼让不一定成功,看 CPU心情
public class TestYield {
public static void main(String[] args) {
MyYield myYield = new MyYield();
new Thread(myYield, "a").start();
new Thread(myYield, "b").start();
}
}
class MyYield implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程开始执行");
System.out.println(Thread.currentThread().getName() + "线程停止执行");
}
}
- 运行结果:
-
- 使用
yield()
方法礼让线程:
- 使用
package com.clown.state;
//测试礼让线程
//礼让不一定成功,看 CPU心情
public class TestYield {
public static void main(String[] args) {
MyYield myYield = new MyYield();
new Thread(myYield, "a").start();
new Thread(myYield, "b").start();
}
}
class MyYield implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程开始执行");
Thread.yield(); //礼让
System.out.println(Thread.currentThread().getName() + "线程停止执行");
}
}
- 礼让成功时的运行结果:
- 注意:礼让也有可能不成功,下面是礼让不成功时的运行结果:
合并线程 - join()
join()
合并线程( 将几个并行线程的线程合并为一个单线程执行 ),待此线程执行完成后,再执行其他线程,其他线程阻塞。- 可以想象成插队。
- 下面我们来随便写一个线程
t1
( 这里我写的是让线程打印字符串:“线程vip来了0” ~ “线程vip来了19” ),接着,我们再在main
方法中写一个主线程( 这里我写的是让主线程打印字符串:“main0” ~ “main99” ); - 然后我们设置当主线程运行到准备打印字符串:“main10” 时,使用
join()
方法让线程t1
插队。当线程t1
执行完毕之后,再继续执行主线程。具体代码如下:
package com.clown.state;
//测试 join()方法
//想象为插队
public class TestJoin implements Runnable {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("线程vip来了" + i);
}
}
public static void main(String[] args) throws InterruptedException {
//启动线程
TestJoin testJoin = new TestJoin();
Thread t1 = new Thread(testJoin);
t1.start();
//主线程
for (int i = 0; i < 100; i++) {
if (i == 10) {
t1.join(); //插队,主线程阻塞
}
System.out.println("main" + i);
}
}
}
- 运行结果:
线程状态观测 - getState()
- 查看JDK帮助文档 -
State
类(java.lang.Thread.State
): - 线程状态。线程可以处于以下状态之一:
NEW
:尚未启动的线程处于此状态。RUNNABLE
:在Java虚拟机中执行的线程处于此状态。BLOCKED
:被阻塞等待监视器锁定的线程处于此状态。WAITING
:正在等待另一个线程执行特定动作的线程处于此状态。TIMED_WAITING
:正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。TERMINATED
:已退出的线程处于此状态。
- 一个线程可以在给定时间点处于一个状态。 这些状态是不反映任何操作系统线程状态的虚拟机状态。
- 下面我们写一个程序来演示线程状态的观测:
package com.clown.state;
//观察测试线程的状态
public class TestState {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> { //lambda表达式
//每间隔 1秒就打印一次 "///",共打印三次
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(1000); //模拟延时 1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("///");
}
});
//观察状态
//getState(); 返回此线程的状态
Thread.State state = thread.getState(); //获取状态
System.out.println(state); //输出状态 //NEW
//观察启动后
thread.start(); //启动线程
state = thread.getState(); //获取状态
System.out.println(state); //输出状态 //RUNNABLE
while (state != Thread.State.TERMINATED) { //只要线程不终止,就一直输出状态
Thread.sleep(200); //间隔 0.2秒,避免卡死
state = thread.getState(); //更新线程状态
System.out.println(state); //输出状态
}
}
}
- 运行结果: