线程状态
一个线程在它的生命周期内有五大状态:
- 线程从运行状态到阻塞状态后,再恢复到运行状态,这个恢复过程不是立即恢复,而是需要进行一系列的操作之后才能进入到就绪状态,然后才能进入运行状态。
- 线程进入死亡状态后不能复活,不能重新开启,如果开启,那么开启之后的是一个新的线程,不是原来的线程。
- new实例化之后,一个线程对象进入新生状态,每个线程都有自己的内存空间(工作空间),这些工作空间直接与主内存进行交互信息,具体是先从主内存中拷贝数据,在自己的内存空间中进行处理。
- 线程对象调用start方法,线程进入就绪状态,进入就绪状态不代表被立即执行,这个线程要等待CPU的调度。此时这个线程具备了运行的条件,但是不一定分配到了计算资源,也就是说还没有开始运行,处于线程的就绪队列中,等待CPU调度。
一个线程进入就绪状态的几种情况:
- 线程对象调用start方法;
- 线程的阻塞事件解除之后,进入就绪状态;
- 调用了yield方法(礼让),让出CPU的调用,线程中断重新进入就绪状态;
- JVM把CPU从本地线程切换到其他线程,导致本地线程进入就绪状态,是JVM决定的。
- 当系统选定了一个等待执行的线程对象(处于就绪状态),线程才进入运行状态。
- 当产生一下几种状态时,会导致线程从运行状态变成阻塞状态。
- 线程对象调用sleep方法,此时是线程仍然占用CPU资源进行等待,进入阻塞状态;
- 线程对象调用wait方法,此时是线程没有占用CPU资源进行等待,进入阻塞状态;
- 线程对象调用join方法(插队),就是加入(插队),需要等待其他线程结束此线程才会运行,这个过程是阻塞状态;
- IO操作(read,write等)会进入阻塞状态。
- 死亡状态。
进入死亡状态的原因有两个:
- run方法代码执行完毕,进入死亡状态;
- 线程被强制终止,stop,destroy方法(不推荐使用,不安全)。让线程结束一般有两种思路,一种是让线程自己执行完毕,另一种是想方设法让这个线程执行完毕,尽量不使用stop方法。
操作线程的方法
线程终止
进入死亡状态(停止)的原因有两个:
- run方法代码执行完毕,进入死亡状态;
- 线程被强制终止,stop,destroy方法(不推荐使用,不安全)。
让线程结束一般有两种思路,一种是让线程自己执行完毕,另一种是想方设法让这个线程执行完毕,尽量不使用stop方法。
一般情况下,线程有执行次数,或者只是方法的调用,执行完毕线程就会停止。
但是有些线程里面是死循环,线程自己不能停止,所以需要其他方法帮助他停止,可以在线程体中加一个布尔类型的控制变量,在线程外部可以根据需要停止线程。
加入布尔变量线程终止的程序
package ThreadStudy02;
import javax.swing.table.TableRowSorter;
/**
* 线程终止的两种情况
* 1.正常终止,循环次数结束,方法执行完毕
* 2.加入结束标识
* 不使用stop,destroy方法
*
* @author 发达的范
* @version 1.0
* @date 2021/05/11 21:56
*/
public class TerminateThread implements Runnable {
//1.加入线程运行标识变量
private boolean flag = true;
private String name;
public TerminateThread(String name) {
this.name = name;
}
public void run() {
int i = 0;
//2.关联标识,true运行,false停止
while (flag) {
System.out.println(name + "--->" + i++);
}
}
//3.对外提供方法改变标识变量
public void change() {
this.flag = false;
}
public static void main(String[] args) {
TerminateThread tt = new TerminateThread("fada");
new Thread(tt).start();
for (int i = 0; i < 7; i++) {
System.out.println("main "+i);
if (i == 5) {
tt.change();
System.out.println(tt.name+" your Thread stop!");
}
}
}
}
运行结果:根据代码来看,运行过程应该是tt线程对象和主线程对象同时运行并输出,但是多次测试都是只有主线程运行输出,tt线程并没有运行,我猜测是主线程的循环次数太少了,但是测试循环十万次的时候,发现果然如此。
2021/05/18运行测试:
线程的sleep方法
- 调用sleep后线程进入的是阻塞状态,sleep的睡觉时间到了之后,线程不是进入运行状态,而是进入就绪状态,等待CPU的调用。
- 每个线程都有一个锁(是一个标志位),调用sleep方法,sleep是不会释放这个锁,而是抱着线程资源,站在马路中间,谁都走不了,CPU并没有释放资源。这个特点是针对wait来说的。
- 在写线程体的时候,run方法是不能抛出(throws)异常的,所以只能使用try…catch…来处理异常。
- sleep一般用于模拟网络延迟,或者用作倒计时等。
使用sleep的程序 :
package ThreadStudy02;
/**
* 模拟sleep的阻塞状态
* 此处模拟了网络延时,放大了问题发生的可能性
*
* @author 发达的范
* @version 1.0
* @date 2021/05/18 19:57
*/
public class BlockedSleep {
public static void main(String[] args) {
Web web = new Web();
new Thread(web, "agent1").start();
new Thread(web, "agent2").start();
new Thread(web, "agent3").start();
}
}
class Web implements Runnable {
private int tickets = 99;
@Override
public void run() {
while (true) {
if (tickets <= 0) {
break;
}
//模拟延时,run方法不能声明异常(throws),只能捕获异常
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " " + tickets--);
}
}
}
运行结果:从运行结果中可以看出,主要有两个问题:一是agent2和agent3都抢到了编号位6的票;二是agent3抢到了编号为-1的票,这两种情况都是不允许存在的。
究其原因,第一种情况我觉得应该是,每个线程在操作数据的时候都是在自己的线程空间中进行数据的操作,但是假设两个线程同时从主内存中取来了相同的数据,同时进行了访问输出,这就会出现同一张票两个人抢到。说明线程之间的数据同步没有做,至于怎么做现在还不太清楚。
但是对于出现其中一个线程抢到了非法的数据这个还不太明白是什么原因。
对于另一个现象,错误的过程可能是agent3从主内存中取到了数据,并没有执行if的条件语句,就输出了。但是具体是什么原因没有执行还不清楚。
下面是使用sleep结合时间类Date进行倒计时的程序:
package ThreadStudy02;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* sleep 结合时间处理类Date模拟倒计时
*
* @author 发达的范
* @version 1.0
* @date 2021/05/18 21:05
*/
public class BlockSleep03 {
public static void main(String[] args) throws InterruptedException {
//获取当前系统的时间加上10秒,作为倒计时的初始时间
//使用Date类创建的是一个时间对象,不是基本数据类型,所以处理的时候要转成基本数据类型
Date endTime = new Date(System.currentTimeMillis() + 1000 * 5);
long end = endTime.getTime();//记录倒计时开始的时间
while (true) {
System.out.println(new SimpleDateFormat("yy:MM:dd:hh:mm:ss").format(endTime));//格式化输出时间
Thread.sleep(1000);//线程阻塞(sleep进入就绪状态)1000ms=1s
endTime = new Date(endTime.getTime() - 1000);
if ((end - 1000 * 5) > endTime.getTime()) {//倒计时结束终止条件
break;
}
}
}
}
运行结果:
线程的yield
与sleep功能相同,都是暂停线程。
礼让yield:
让出CPU的资源,等待重新调用,让线程从运行状态进入就绪状态