1. 写在前面
线程也有类似“生命周期”的东西,只不过,一般我们叫线程的状态。
2. 线程的状态
线程总共有5个状态,分别是:
- 创建/新生状态
- 就绪状态
- 运行状态
- 阻塞状态
- 死亡状态
首先,在线程创建后就会是创建/新生状态状态了,也就是调用了这个方法
Thread t = new Thread():
然后,调用start方法,就会进入就绪状态,就绪状态的线程可以随时获得时间片,被CPU调度执行。值得注意的是,处于就绪状态的线程,并不一定会被立刻调度执行
然后,线程抢到了时间片,CPU就会调度此线程,此线程处于运行状态
然后,当调用sleep、wait、join等方法,或者等待用户输入时,线程会进入阻塞状态,不会继续执行后面的代码。当阻塞事件解除之后,则会重新进入就绪状态,等待CPU调度执行。记得哦,是就绪状态,而非运行状态
最后,线程代码执行完毕,就会进入死亡状态。线程一旦进入死亡状态,就不可以被再次启动
更直观一点,可以看下这幅图
这是狂神说课程中的一幅图,我觉得比较直观,这里借用一下~
3. 线程常见的一些方法
一些静态方法,比如
sleep: 线程休眠,进入阻塞状态,不会释放掉锁yield: 暂停当前正在执行的线程,使其重新转为就绪状态,与其他线程一起竞争时间片
还有一些成员方法
join: 调用此方法的线程“插队”进来,等插队的线程执行完后,被插队的线程才能继续运行。在这期间,被插队的线程处于阻塞状态wait: 线程进入阻塞状态,会释放锁setPriority: 设置线程优先级,一般来说优先级高的线程会被先调度执行getState: 获得线程当前的状态,JDK将其分成了6种,分别是NEW(创建)、RUNNABLE(就绪以及运行)、BOLCKED(阻塞,一般和synchronized有关)、WAINTING(阻塞,一般和wait、join方法有关)、TIMED_WAINTING(阻塞,一般和sleep方法有关)、TERMINATED(死亡)setDaemon: 设置线程是否为守护线程。虚拟机必须确保用户线程执行完毕,且不用等待守护线程执行完毕。守护线程有垃圾回收线程、后台记录操作日志线程等
3.1 sleep 来放大问题
sleep可以用来倒计时,也可以用来线程休眠,但还有一个非常重要的功能,就是放大问题的发生性。比如这样一段代码
public class ThreadDemo {
public static void main(String[] args) {
Runnable threadSon = new ThreadSon();
Thread t1 = new Thread(threadSon, "你");
Thread t2 = new Thread(threadSon, "我");
Thread t3 = new Thread(threadSon, "他");
t1.start();
t2.start();
t3.start();
}
}
class ThreadSon implements Runnable {
int ticketNum = 5; // 票的总数
@Override
public void run() {
while (ticketNum > 0) {
System.out.println(Thread.currentThread().getName() + " 买到了第" + ticketNum-- + "张票");
}
}
}
三个线程,共同来买票,我们看下输出的结果
ok,貌似是没有问题,但,假如我们在run方法的while循环里,加上sleep的话,如下
class ThreadSon implements Runnable {
int ticketNum = 5; // 票的总数
@Override
public void run() {
while (ticketNum > 0) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 买到了第" + ticketNum-- + "张票");
}
}
}
会输出什么结果呢?
咦,这就出问题了,怎么会买到第0张票呢?其实这是线程不安全的一个典型例子。但如果没有使用sleep方法来放大问题的话,这个问题可能会比较难发现。当然,这个问题如何解决,可以看后面关于线程安全的文章,不是本篇文章的重点。
3.2 线程礼让 yield
可以看一段例子
public class ThreadDemo {
public static void main(String[] args) {
Runnable threadSon = new ThreadSon();
Thread t1 = new Thread(threadSon, "1");
Thread t2 = new Thread(threadSon, "2");
t1.start();
t2.start();
}
}
class ThreadSon implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 开始执行");
Thread.yield();
System.out.println(Thread.currentThread().getName() + " 结束执行");
}
}
执行结果为:
多运行几次,还会出现线程1执行完之后,线程2才会执行的情况,所以线程“礼让”不一定成功,只是释放了CPU资源,至于具体调用谁,还是得看CPU调度
3.3 线程“插队” join
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
Runnable threadSon = new ThreadSon();
Thread t1 = new Thread(threadSon, "1");
t1.start();
for (int i = 0; i < 10; i++) {
System.out.println("我是main线程,i为 " + i);
if (i == 3) {
t1.join();
}
}
}
}
class ThreadSon implements Runnable {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("我是ThreadSon线程, i 为" + i);
}
}
}
输出结果
发现当调用ThreadSon线程的join方法之后,ThreadSon线程就插入进来了,其实是抢到了时间片,然后一直执行,一直到ThreadSon运行完,主线程才继续运行,在此期间,主线程处于阻塞状态。
4. 总结一下
本篇文章主要讲了以下内容
- 线程的五种状态
- 线程的几个常用方法介绍
- 线程的几个重点方法介绍
over~