多线程并发基础(3)线程的状态及常用方法

243 阅读4分钟

1. 写在前面

线程也有类似“生命周期”的东西,只不过,一般我们叫线程的状态。

2. 线程的状态

线程总共有5个状态,分别是:

  1. 创建/新生状态
  2. 就绪状态
  3. 运行状态
  4. 阻塞状态
  5. 死亡状态

首先,在线程创建后就会是创建/新生状态状态了,也就是调用了这个方法 Thread t = new Thread():

然后,调用start方法,就会进入就绪状态,就绪状态的线程可以随时获得时间片,被CPU调度执行。值得注意的是,处于就绪状态的线程,并不一定会被立刻调度执行

然后,线程抢到了时间片,CPU就会调度此线程,此线程处于运行状态

然后,当调用sleep、wait、join等方法,或者等待用户输入时,线程会进入阻塞状态,不会继续执行后面的代码。当阻塞事件解除之后,则会重新进入就绪状态,等待CPU调度执行。记得哦,是就绪状态,而非运行状态

最后,线程代码执行完毕,就会进入死亡状态。线程一旦进入死亡状态,就不可以被再次启动

更直观一点,可以看下这幅图

image.png 这是狂神说课程中的一幅图,我觉得比较直观,这里借用一下~

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-- + "张票");
        }
    }
}

三个线程,共同来买票,我们看下输出的结果

image.png

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-- + "张票");
        }
    }
}

会输出什么结果呢?

image.png

咦,这就出问题了,怎么会买到第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() + " 结束执行");
    }
}

执行结果为:

image.png

多运行几次,还会出现线程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);
        }
    }
}

输出结果

image.png

发现当调用ThreadSon线程的join方法之后,ThreadSon线程就插入进来了,其实是抢到了时间片,然后一直执行,一直到ThreadSon运行完,主线程才继续运行,在此期间,主线程处于阻塞状态。

4. 总结一下

本篇文章主要讲了以下内容

  1. 线程的五种状态
  2. 线程的几个常用方法介绍
  3. 线程的几个重点方法介绍

over~