第二章线程状态

Java线程的六大状态

在我们的操作系统中,线程对应五大状态,初始状态、就绪态、运行状态、阻塞状态、终止状态

img

  • NEW(新建状态) 新创建了一个线程对象,但是还没有调用start()方法

  • RUNNABLE(运行态) Java线程中将就绪(READY)和运行中(RUNNING)两种状态并称为运行态,其一个线程被创建了,并且调用了这个线程对象的start()方法,这个线程对象就处于可运行的线程池中,等待被调度选中,获得CPU的使用权,这种状态称为就绪态(万事俱备,只差CPU),当这个线程获得了CPU的使用权,那么就会从就绪态变为运行中

  • 阻塞(BLOCKED)表示这个线程阻塞于锁

  • 等待(WAITING)进入这种状态的线程需要等待其他线程做出一些特定的动作(通知或者中断)

  • 超时等待(TIME_WAITING)该状态不同于WAITINGM,它是指定的时间后自行返回

  • 终止(TERMINATED) 表示该线程已经执行完毕

BLOCKED , WAITING , TIMED_WAITING 都是 Java API 层面对【阻塞状态】的细分

Java API 层面的 RUNNABLE 状态涵盖了 操作系统 层面的【可运行状态】、【运行状态】和【阻塞状态】(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为是可运行)

img

新建状态

public class ThreadDemo3 {
    
    
    public static void main(String[] args) {
    
    
        Thread thread = new Thread(() -> {
    
    
            System.out.println("测试线程的状态");
        }, "t1");
        System.out.println(thread.getState());
    }
}
//NEW
  • 关于NEW,我们通过实现Runnable接口或者继承Thread可以得到一个线程类,new出一个线程对象出来,这个线程就处于NEW状态

运行态

运行态之Ready

  • 就绪(READY)只是说明你有资格运行,只有系统调度你去运行你才是真正的RUNNING,否则你永远是READR,而真正的调用,还是需要操作系统的介入
  • 处于NEW态的线程调用start()就进入就绪状态
  • 当一个线程sleep()方法结束,其他线程join结束,某个线程拿到了对象锁,这些线程也就进入了就绪状态
  • 当前线程时间片用完,或者调用当前运行线程的yield()方法,当前线程进入就绪状态
  • 锁池里的线程拿到对象锁之后,进行就绪状态

运行态之Ruunning

  • 线程的调度程序从可运行池中(RUNNING和READY)挑选一个线程作为运行线程,这是线程变为运行状态的唯一一种方式
public class ThreadDemo3 {
    
    
    static int r = 0;
    public static void main(String[] args) throws InterruptedException {
    
    
        new Thread(()->{
    
    
            System.out.println(Thread.currentThread().getState());
        },"t1").start();
    }
 }
 RUNNABLE

yield进入就绪态

  • 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程
  • 具体的实现依赖于操作系统的任务调度器
public class ThreadDemo3 {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        Thread thread = new Thread(() -> {
    
    
            int count=1;
            while (true){
    
    
                System.out.println(Thread.currentThread().getName()+"第"+ count++ +"运行");
            }
        }, "t1");
        Thread thread1 = new Thread(() -> {
    
    
            int count=1;
            while (true){
    
    
                System.out.println(Thread.currentThread().getName()+"第"+ count++ +"运行");
                // t2线程就会让出CPU,进入就绪态,等待被CPU继续调度
                Thread.yield();
            }
        }, "t2");
        thread.start();
        thread1.start();
    }
}
t2第283349运行
t2第283350运行
t2第283351运行
t2第283352运行
t2第283353运行
t2第283354运行
t2第283355运行
t1第1052474运行
t1第1052475运行
t1第1052476运行
t1第1052477运行
  • 我们发现t1和t2线程都运行了

  • 我们发现t2线程执行yield,让出CPU的使用权,t2线程只是让出CPU,但是下此调度什么线程,还是由我们的操作系统说的算,可能下次调用的还是t2线程(只不过是我们执行完输出这个语句,就把CPU让出去了),也有可能是t1线程,我们无法干涉,只是每次执行到t2线程,都会把CPU让出去,让t1线程执行的次数可能较多一点

Thread 类中的 yield 方法有什么作用?

  • 使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。
  • 当前线程到了就绪状态,那么接下来哪个线程会从就绪状态变成执行状态呢?可能是当前线程,也可能是其他线程,看系统的分配了。

阻塞态

synchronized进入阻塞态

public class ThreadDemo3 {
    
    
    static int r = 0;
    public static void main(String[] args) throws InterruptedException {
    
    
        Object o = new Object();
        Thread t1 = new Thread(() -> {
    
    
            synchronized (o) {
    
    
                System.out.println("t1线程进行加锁操作");
                try {
    
    
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
    
    
            synchronized (o) {
    
    
                System.out.println("t2线程进行加锁操作");
            }
        }, "t2");
        t1.start();
        t2.start();
        TimeUnit.SECONDS.sleep(1);
        System.out.println("t2线程状态为" + t2.getState());
    }
}
t1线程进行加锁操作
t2线程状态为BLOCKED
t2线程进行加锁操作
  • 当我们t2线程想要争取o资源,但是o资源被t1线程占着,所以t2线程进入了阻塞态
  • 关于synchronized和锁的相关知识在后面详解

等待状态

  • 处于这种状态的线程不会被分配CPU执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态。

join()进入等待状态

为什么需要join

public class ThreadDemo3 {
    
    
    static int r = 0;
    public static void main(String[] args) throws InterruptedException {
    
    
        System.out.println(Thread.currentThread().getName()+"开始");
        Thread thread = new Thread(() -> {
    
    
            System.out.println(Thread.currentThread().getName() + "开始");
            try {
    
    
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            r = 10;
            System.out.println(Thread.currentThread().getName() + "结束");
        }, "t1");
        thread.start();
        //thread.join();
        System.out.println("r结果是"+r);
        System.out.println(Thread.currentThread().getName()+"结束");
    }
}
main开始
r结果是0
main结束
t1开始
t1结束
  • 因为主线程和线程 t1 是并行执行的,t1 线程需要 1 秒之后才能算出 r=10
  • 而主线程一开始就要打印 r 的结果,所以只能打印出 r=0,但是我们就是想要t1线程运算后的结果,怎么解决
  • 有时,我们需要等待一个线程完成它的工作后,才能进行自己的下一步工作。例如,张三只有等李四转 账成功,才决定是否存钱,这时我们需要一个方法明确等待线程的结束。

解决方法

  • 用 sleep 行不行?为什么?
    • 我们可以让main线程sleep 2秒,但是在很多情况下,我们不知道对应线程需要多久时间
  • 用 join,加在 t1.start() 之后即可
main开始
t1开始
t1结束
r结果是10
main结束
public class ThreadDemo3 {
    
    
    static int r = 0;
    public static void main(String[] args) throws InterruptedException {
    
    
        Thread thread = new Thread(() -> {
    
    
            try {
    
    
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        });
        Thread thread1 = new Thread(() -> {
    
    
            try {
    
    
                thread.join();
                System.out.println("测试线程状态");
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        });
        thread.start();
        thread1.start();
        TimeUnit.SECONDS.sleep(1);
        System.out.println(thread1.getState());
    }
}
WAITING
测试线程状态
  • join()就是死等,痴汉属性
  • thread1一定要thread执行完,才继续执行,调用thread.join() ,thread进入了等待状态

wait()进入等待状态

public class ThreadDemo3 {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        Object o = new Object();
        Thread thread = new Thread(() -> {
    
    
            synchronized (o) {
    
    
                try {
    
    
                    System.out.println("t1线程进行等待");
                    o.wait();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        }, "t1");
        thread.start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(()->{
    
    
            synchronized (o){
    
    
                System.out.println(thread.getName() + "现在是" + thread.getState());
                o.notify();
            }
        },"t2").start();
    }
}
t1线程进行等待
t1现在是WAITING
  • synchronized是进行加锁操作,也就占用Object这个资源,后面锁进行详解
  • wait进入等待状态 notify进行唤醒
  • 后面在线程等待唤醒体系进行详解

LockSupport.park()进入等待状态

public class ThreadDemo3 {
    
    
    static int r = 0;
    public static void main(String[] args) throws InterruptedException {
    
    
        Thread thread = new Thread(() -> {
    
    
            LockSupport.park();
        }, "t1");
        thread.start();
        TimeUnit.MILLISECONDS.sleep(200);
        System.out.println(thread.getState());
        LockSupport.unpark(thread);
        TimeUnit.MILLISECONDS.sleep(200);
        System.out.println(thread.getState());
    }
}
WAITING
TERMINATED
  • park让线程进入等待状态,unpark(thread)将线程唤醒

  • 后面在线程等待唤醒体系进行详解

  • 当前线程调用 LockSupport.parkNanos(long nanos) 或 LockSupport.parkUntil(long millis) 时,当前线程从 RUNNABLE --> TIMED_WAITING

  • 调用 LockSupport.unpark(目标线程) 或调用了线程 的 interrupt() ,或是等待超时,会让目标线程从TIMED_WAITING–> RUNNABLE

超时等待状态

  • 处于这种状态的线程不会被分配CPU执行时间,不过无须无限期等待被其他线程显示地唤醒,在达到一定时间后它们会自动唤醒。

sleep(long)进入超时等待状态

  • 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
  • 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
    • 关于线程的中断,后面详细解释
  • 睡眠结束后的线程未必会立刻得到执行
  • 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性
public class ThreadDemo3 {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        Thread thread = new Thread(() -> {
    
    
            try {
    
    
                System.out.println("测试线程的状态");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }, "t1");
        System.out.println(thread.getState());
        thread.start();
        System.out.println(thread.getState());
        Thread.sleep(1000);
        System.out.println(thread.getState());
    }
}
NEW
RUNNABLE
测试线程的状态
TIMED_WAITING
  • 当线程调用了sleep时,线程进入了Timed Waiting

限制对 CPU 的使用——sleep 实现
在没有利用 cpu 来计算时,不要让 while(true) 空转浪费 cpu,这时可以使用 yield 或 sleep 来让出 cpu 的使用权给其他程序

while(true) {
    
    
	try {
    
    
		Thread.sleep(50);
	} catch (InterruptedException e) {
    
    
		e.printStackTrace();
	}
}
  • 可以用 wait 或 条件变量达到类似的效果
  • 不同的是,后两种都需要加锁,并且需要相应的唤醒操作,一般适用于要进行同步的场景
  • sleep 适用于无需锁同步的场景

join(long)进入超时等待状态

public class ThreadDemo3 {
    
    
    static int r = 0;
    public static void main(String[] args) throws InterruptedException {
    
    
        Thread thread = new Thread(() -> {
    
    
            try {
    
    
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        });
        Thread thread1 = new Thread(() -> {
    
    
            try {
    
    
                thread.join(1000);
                System.out.println("测试线程状态");
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        });
        thread.start();
        thread1.start();
        TimeUnit.MILLISECONDS.sleep(500);
        System.out.println(thread1.getState());
    }
}
TIMED_WAITING
测试线程状态
  • 打印TIMED_WAITING,说明在thread2为了等待thread1,调用join(long)进入了超时等待状态
  • join(long time) 有理性的等,最多等你多少毫秒,如果执行完就行,执行不完,我也不等你了

为什么 Thread 类的 sleep()和 yield ()方法是静态的

  • Thread 类的 sleep()和 yield()方法将在当前正在执行的线程上运行进行对应操作。所以在其他处于等待状态的线程上调用这些方法是没有意义的。这就是为什么这些方法是静态的。

如果sleep和yield是静态方法,那么不管哪个线程,只要一调用就把自己给sleep、yield了。

如果sleep和yield是实例方法,那就热闹了。一个线程可以获取其他线程对象的引用,然后通过引用调要其他线程的sleep和yield方法,让其他线程让出CPU使用权。试想,如果每个线程都可以通过sleep、yield其他线程的方式来让自己获得CPU使用权,那不是世界大乱了。线程之间可以互相sleep,互相yield。

来啊,互相伤害啊,TMD谁都别想用CPU!

线程的 sleep()方法和 yield()方法有什么区别

  • sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会;
  • 线程执行 sleep()方法后转入TIMED_WAITING(超时等待),而执行 yield()方法后转入就绪READY状态
  • sleep()方法声明抛出 InterruptedException,而 yield()方法没有声明任何异常;
  • sleep()方法比 yield()方法(跟操作系统 CPU 调度相关)具有更好的可移植性,通常不建议使用yield()方法来控制并发线程的执行

wait(long)进入超时等待状态

public class ThreadDemo3 {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        Object o = new Object();
        Thread thread = new Thread(() -> {
    
    
            synchronized (o) {
    
    
                try {
    
    
                    System.out.println("t1线程进行等待");
                    o.wait(1000);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        }, "t1");
        thread.start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(()->{
    
    
            synchronized (o){
    
    
                System.out.println(thread.getName() + "现在是" + thread.getState());
                o.notify();
            }
        },"t2").start();
    }
}
t1线程进行等待
t1现在是TERMINATED
  • 等待一段时间,若再该时间内线程被唤醒,则继续执行,若超过了相应时间还没有其他线程唤醒此线程,此线程不再等待,恢复执行

终止态

public class ThreadDemo3 {
    
    
    static int r = 0;
    public static void main(String[] args) throws InterruptedException {
    
    
        Thread t1 = new Thread(() -> {
    
    
            System.out.println("t1线程运行完毕");
        }, "t1");
        t1.start();
        TimeUnit.MILLISECONDS.sleep(100);
        System.out.println("t1线程的状态为"+t1.getState());
    }
}
t1线程运行完毕
t1线程的状态为TERMINATED

线程优先级

  • 线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它
  • 如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用
  • 优先级越高的线程是越有可能被CPU优先执行,我们JAVA只是建议优先级高的线程被执行,到底执行不执行,由操作系统说的算
public class ThreadDemo4 {
    
    
    public static void main(String[] args) {
    
    
        Thread thread = new Thread(() -> {
    
    
            System.out.println("测试线程优先级");
        }, "t1");
        //获取线程优先级
        System.out.println(thread.getPriority());
        //设置线程优先级
        thread.setPriority(Thread.MAX_PRIORITY);
        System.out.println(thread.getPriority());
    }
}
5
10

猜你喜欢

转载自blog.csdn.net/qq_50985215/article/details/131510486
今日推荐