目录
Java中描述线程这个对象的类——java.lang.Thread类
2.6 获取当前线程对象的引用——currentThread()
观察 1:关注 NEW 、 RUNNABLE 、 TERMINATED 状态的转换
观察 2:关注 WAITING 、 BLOCKED 、 TIMED_WAITING 状态的转换
进程和线程
程序
程序:一系列有组织的文件,封装操作系统的各种API,实现不同的效果。
进程
进程:程序在系统中的一次执行过程。
进程是现代操作系统中资源分配(CPU,内存等关键系统资源)的最小单位。不同进程之间是相互独立的。
IP地址:唯一在网络上标识一台主机的位置,就相当于我们收发快递的地址。
127.0.0.1——>本机的IP
MySQL服务端启动后,进程端口号就是3306
Http:80端口号
Https:443端口号可以通过IP地址找到主机,那么如何定位主机的某个进程?——端口号
端口号在操作系统上能唯一的定位一个进程,进程的端口号一般不变。
进程的PCB(操作系统描述本次进程的信息结构)PID,每次启动进程都会改变。
线程
线程:就是进程中的子任务。同一个进程的所有线程共享该进程的资源,线程是操作系统任务(系统调度)执行的基本单位。
买CPU都会告诉你几核心几线程
例如:4核8线程
指的是当前电脑的CPU有4个核心(4个独立的芯片),每个核心上能并行处理2个线程
同时能并行执行8个线程。打开浏览器——>打开的是一个进程
线程就是进程中的一个独立任务,在浏览器上可以同时下载视频、同时听音乐,下载任务是一个线程,听音乐就是另一个线程。
进程和线程的区别
1. 进程是os资源分配的基本单位,线程是os系统调度的基本单位。
2. 创建和销毁进程的开销要远比创建和销毁线程大得多(创建和销毁一个进程的时间要比创建和销毁一个线程大的多),线程更加轻量化。
例如:启动浏览器的速度(进程)慢于启动浏览器的标签(线程)的速度。
3. 调度一个线程也远比调度一个进程快的多。4. 进程包含线程,每个进程至少包含一个线程(主线程)。
main方法就是一个主线程,java命令 + 主类名称 就是启动Java进程
5. 进程之间彼此相对独立,不同的进程不会共享内存空间;同一个进程的线程共享内存空间。
JDK提供的线程库实际上就是利用操作系统提供的线程库进行二次封装
Java中描述线程这个对象的类——java.lang.Thread类
线程的核心类,都是通过Thread类来启动一个新的线程。
import java.util.Random; /** * 第一个多线程示例代码 */ public class FirstThreadDemo { private static class MyThread extends Thread { // run方法就是线程的核心工作方法,线程要干的所有事情都在run方法中进行定义 @Override public void run() { Random random = new Random(); while (true) { // 打印当前线程名称 System.out.println(Thread.currentThread().getName()); // 当前线程随机暂停0-9秒 try { Thread.sleep(random.nextInt(10)); } catch (InterruptedException e) { throw new RuntimeException(e); } } } } public static void main(String[] args) { // 创建三个线程对象 MyThread m1 = new MyThread(); MyThread m2 = new MyThread(); MyThread m3 = new MyThread(); //启动三个子线程 m1.start(); m2.start(); m3.start(); Random random = new Random(); while (true) { // 打印当前线程名称 System.out.println(Thread.currentThread().getName()); // 当前线程随机暂停0-9秒 try { Thread.sleep(random.nextInt(10)); } catch (InterruptedException e) { throw new RuntimeException(e); } } } }
1. run方法是线程类的工作任务,由JVM来执行。
run方法决定了线程启动之后的核心执行流程方法,每个线程都是从run方法开始执行代码,当run方法执行完毕,线程就进入销毁状态
2. start方法是Thread类中启动线程的方法
只有当线程对象调用start方法之后才会被系统调度,进入运行状态。线程启动之后会由JVM自动执行每个线程的run方法。
问题:若这四个线程是顺序执行的,主方法会不会打印输出?其他线程的打印呢?
答:①顺序执行时,3个子线程run方法执行之后才执行主线程。
②只有m1子线程在打印
当线程已经启动之后,再次调用该线程的start方法就会抛出此异常,说明该线程已经启动过了。
JDK和JRE相比最大的区别,提供了很多开发程序会用到的辅助工具
javac ——> javac.exe
java ——> java.exe
jconsole命令查看当前运行的JVM内部的线程情况——>jconsole.exe
1. 创建线程的方法
创建线程:Java中创建一个线程一共有四种方式
a. 继承Thread类,覆写run方法(线程的核心工作任务方法)
b. 实现Runnable接口,覆写run方法
c. 覆写Callable接口,覆写call方法
d. 使用线程池创建线程
a、b最终启动线程都是通过Thread类的start方法启动线程
a. 继承Thread类,覆写run方法
Ⅰ. 一个子类继承Thread类
Ⅱ. 覆写run方法
Ⅲ. 产生当前这个子类对象,而后调用start方法启动线程public class Main { public static void main(String[] args) { //创建线程类对象 ThreadMethod mt = new ThreadMethod(); mt.start(); System.out.println("主线程的输出语句"); } }
public class ThreadMethod extends Thread{ @Override public void run() { System.out.println("子线程的输出结果"); } }
以上产生两种不同输出结果
调用start方法启动线程,是由JVM产生操作系统的线程并启动,到底什么时候真正启动,对于我们来说不可见的,也没法控制。
b. 实现Runnable接口,覆写run方法
Ⅰ. 一个子类继承Runnable类
Ⅱ. 覆写run方法
Ⅲ. 产生当前Thread子类对象,而后调用start方法启动线程/** * 这个实现了Runnable接口的子类,并不是直接的线程对象,只是一个线程的核心工作任务。 * 线程的任务和线程实体的关系 */ public class RunnableMethod implements Runnable { @Override public void run() { System.out.println("Runnable方式实现的子线程任务"); } }
public class Main { public static void main(String[] args) throws InterruptedException { // 创建线程的任务对象 RunnableMethod runnableMethod = new RunnableMethod(); // 创建线程对象,将任务对象传入线程对象 Thread thread = new ThreadMethod(runnableMethod); // 启动线程 thread.start(); System.out.println("主线程的输出语句"); } }
这个实现了Runnable接口的子类,并不是直接的线程对象,只是一个线程的核心工作任务。
推荐使用方式b,实现Runnable接口更加灵活,子类还能实现别的接口,继承别的类。
方式a只能继承Thread,单继承局限。
方式a 和方式b 的不同写法
匿名内部类继承了Thread类,然后实现run方法
public class OtherMethod { public static void main(String[] args) { // 匿名内部类继承Thread类 Thread t1 = new Thread() { @Override public void run() { System.out.println("匿名内部类继承Thread类"); System.out.println(Thread.currentThread().getName()); } }; t1.start(); System.out.println("这是主线程" + Thread.currentThread().getName()); } }
匿名内部类实现Runnable接口
public class OtherMethod { public static void main(String[] args) { // Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println("匿名内部类继承Runnable接口"); System.out.println(Thread.currentThread().getName()); } }); thread.start(); System.out.println("这是主线程" + Thread.currentThread().getName()); } }
Lambda表达式实现Runnable接口
public static void main(String[] args) { // Lambda表达式实现Runnable接口 Thread t1 = new Thread(()-> System.out.println("使用Lambda表达式实现Runnable接口")); t1.start(); System.out.println("这是主线程" + Thread.currentThread().getName()); }
c. 实现Callable接口
带返回值的接口,覆写call方法(线程的核心工作任务方法,有返回值)
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; /** * Callable接口的使用 */ public class CallableTest { public static void main(String[] args) throws ExecutionException, InterruptedException { Callable<Integer> callable = new Callable<Integer>() { @Override public Integer call() throws Exception { int sum = 0; for (int i = 0; i <= 1000; i++) { sum += i; } return sum; } }; // 接收call方法的返回值使用FutureTask对象 FutureTask<Integer> futureTask = new FutureTask<>(callable); // Thread类接收Callable接口必须通过FutureTask类 Thread t = new Thread(futureTask); t.start(); // get方法会阻塞当前线程,直到call方法执行完毕,才恢复执行 int result = futureTask.get(); System.out.println("子线程执行结束,result = " + result); } }
import java.util.concurrent.*; public class CallableTest { public static void main(String[] args) throws ExecutionException, InterruptedException { Callable<Integer> callable = new Callable<Integer>() { @Override public Integer call() throws Exception { int sum = 0; for (int i = 0; i <= 1000; i++) { sum += i; } return sum; } }; ExecutorService pool = Executors.newFixedThreadPool(3); // FutureTask就是Future接口的子类,Future接口就是搭配Callable接口使用的,来接收Callable的返回值 Future<Integer> callResult = pool.submit(callable); int result = callResult.get(); System.out.println("子线程执行结束,result = " + result); } }
多线程和顺序执行的执行速度差异
执行10个亿量级数字的连续累加
顺序执行,先+10亿,再+10亿,最终都是在主线程中+20亿。
并发执行,子线程+10亿,主线程+10亿,主线程和了线程并发的在执行+10亿理论来讲,并发执行的速度是顺序执行的一倍,耗时是一半。
/** * 串行和并发的时间对比 */ public class ThreadNB { private static final long COUNT = 10_0000_0000; public static void main(String[] args) throws InterruptedException { serial(); concurrent(); } // 串行进行20亿的累加 private static void serial() { long start = System.nanoTime(); long a = 0; for (long i = 0; i < COUNT; i++) { a++; } // 主线程中也执行 +10亿操作 // b的执行需要等待a走完才能进行 long b = 0; for (long i = 0; i < COUNT; i++) { b++; } long end = System.nanoTime(); double allTime = (end - start) * 1.0 / 1000 / 1000; System.out.println("顺序执行共耗时" + allTime + "ms"); } // 并发执行 +10亿操作 private static void concurrent() throws InterruptedException { long start = System.nanoTime(); // 子线程进行 + 10亿操作 Thread thread = new Thread(() -> { long a = 0; for (long i = 0; i < COUNT; i++) { a++; } }); thread.start(); // 主线程中也执行 +10亿操作 long b = 0; for (long i = 0; i < COUNT; i++) { b++; } // 等待子线程执行结束,主线程和子线程的加法操作都完成 // 等待子线程thread执行结束才能执行下面代码 thread.join(); long end = System.nanoTime(); double allTime = (end - start) * 1.0 / 1000 / 1000; System.out.println("并发执行共耗时" + allTime + "ms"); } }
多线程最大的应用场景就是把一个大任务拆分为多个子任务(交给子线程),多个子线程并发执行,提高系统的处理效率。
例如:12306系统是多线程程序
我们每个人其实都是一个线程,我们多个人可以同时登录系统买票,付款操作是一个非常耗时的操作。
若不是多线程,每个人买票都得排队,从第一个登录系统的人开始买票,依次进行,非常慢,这个程序就没法用。
2. Thread 类及常见方法
2.1 Thread类常见方法
无论是继承Thread类还是实现Runnable接口,最终启动线程调用的都是Thread类的start方法。Thread类就是JVM描述管理线程的类,每个线程都对应唯一的一个Thread对象1. 构造方法
public static void main(String[] args) { // 一般搭配子类使用,需要有一个继承了Thread类的子类 Thread t1 = new Thread(); Thread t2 = new Thread(new Runnable() { @Override public void run() { System.out.println("传入Runnable对象"); } }); Thread t3 = new Thread("3号线程"); Thread t4 = new Thread(new Runnable() { @Override public void run() { System.out.println("666"); } },"4号线程"); }
2.2 Thread 的核心属性
1. ID 是线程的唯一标识,不同线程不会重复。每个线程都有一个独立的id。
2. 名称是各种调试工具用到的。
3. 状态表示线程当前所处的一个情况。
4. 优先级高的线程,理论上来说更容易被调度到。优先级越高的线程越有可能被CPU优先执行,Java程序只是建议优先级高的线程优先执行,到底执行不执行,OS说了算。
5. 关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。
6. 是否存活,即简单的理解为 run 方法是否运行结束了。7. 线程的中断问题,下面我们进一步说明
2.3 线程属性示例
/** * 线程常用属性 */ public class ThreadAttr { public static void main(String[] args) { Thread thread = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("线程存活"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } System.out.println(Thread.currentThread().getName() + "线程即将消逝"); } }, "测试线程"); // -----------------------------------------------------------------以下为主线程 System.out.println(Thread.currentThread().getName() + ":ID" + thread.getId()); System.out.println("状态:" + Thread.currentThread().getState()); System.out.println("优先级:" + Thread.currentThread().getPriority()); System.out.println("是否为后台线程 ?" + Thread.currentThread().isDaemon()); System.out.println("主线程是否存活 ?" + Thread.currentThread().isAlive()); System.out.println("子线程是否存活 ?" + thread.isAlive()); thread.start(); } }
2.4 中断一个线程
中断线程,线程间通信的一种方式
中断一个正在执行的线程(run方法还没有执行结束);普通线程会在run方法执行结束之后自动停止。
中断线程有两种方式
a. 通过共享变量进行中断
设置转账场景
/** * 通过共享变量中断线程 */ public class ThreadInterruptByVar { private static class MyThread implements Runnable { // 多个线程都会用到的变量加上volatile关键字 // 表示当前线程是否需要停止 volatile boolean isQuit = false; @Override public void run() { while (!isQuit) { System.out.println(Thread.currentThread().getName() + "正在转账..."); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } System.out.println(Thread.currentThread().getName() + "转账中断"); } } public static void main(String[] args) throws InterruptedException { // 创建线程的任务对象 MyThread mt = new MyThread(); // 创建线程对象,将任务对象传入线程对象 Thread thread = new Thread(mt, "转账线程"); System.out.println("可以开始转账了"); thread.start(); // 主线程暂停3s,暂停thread Thread.sleep(3000); System.out.println("转账完毕,可以停止"); mt.isQuit = true; } }
注意
Thread类的所有静态方法,在哪个线程中调用的,就生效在哪个线程。
b. 使用Thread.interrupted()静态方法 或 Thread对象的成员方法isInterrupted()
Thread类的内部包含了一个属性,当前线程是否被中断的属性。Thread.interrupted()静态方法
/** * 通过内置的属性中断线程 */ public class ThreadInterruptedByMethod { private static class MyRunnable implements Runnable { @Override public void run() { // 静态方法,判断当前线程是否被中断 while (!Thread.interrupted()) { System.out.println(Thread.currentThread().getName() + "正在转账"); try { Thread.sleep(1000); } catch (InterruptedException e) { // 当线程被中断时,会抛出中断异常 // 抛出中断异常之后,中断状态就会还原! System.err.println("转账失败"); break; } } System.out.println(Thread.currentThread().getName() + "系统故障"); } } public static void main(String[] args) throws InterruptedException { MyRunnable mt = new MyRunnable(); Thread thread = new Thread(mt, "转账线程"); System.out.println("准备转账"); thread.start(); Thread.sleep(5 * 1000); // 中断子线程 // 调用此方法就会将子线程的状态置为中断状态 thread.interrupt(); } }
Thread对象的成员方法isInterrupted()
package thread; /** * 通过内置的属性中断线程 */ public class ThreadInterruptedByMethod { private static class MyRunnable implements Runnable { @Override public void run() { // 成员方法判断当前线程是否被中断 while (!Thread.currentThread().isInterrupted()) { System.out.println(Thread.currentThread().getName() + "正在转账"); try { Thread.sleep(1000); } catch (InterruptedException e) { // 当线程被中断时,会抛出中断异常 // 抛出中断异常之后,中断状态就会还原! System.err.println("转账失败"); break; //此处捕获异常后,会跳出循环 } } System.out.println(Thread.currentThread().getName() + "系统故障"); } } public static void main(String[] args) throws InterruptedException { MyRunnable mt = new MyRunnable(); Thread thread = new Thread(mt, "转账线程"); System.out.println("准备转账"); thread.start(); Thread.sleep(5 * 1000); // 中断子线程 // 调用此方法就会将子线程的状态置为中断状态 thread.interrupt(); } }
线程收到内置的中断通知有两种方式:
a. 当线程调用sleep / wait / join等方法处在阻塞状态时,收到中断通知thread.interrupt()就会抛出一个中断异常InterruptedException,当抛出异常后,当前线程的中断状态会被清除。
类似一个按下去会弹起的开关,按下则关,弹起则开。
/** * 通过内置的属性中断线程 */ public class ThreadInterruptedByMethod { private static class MyRunnable implements Runnable { @Override public void run() { // 静态方法,判断当前线程是否被中断 // while (!Thread.interrupted()) { // 成员方法,判断当前线程对象是否被中断了 while (!Thread.currentThread().isInterrupted()) { System.out.println(Thread.currentThread().getName() + "正在转账"); try { Thread.sleep(1000); } catch (InterruptedException e) { // 当线程被中断时,会抛出中断异常 // 抛出中断异常之后,中断状态就会还原! System.err.println("转账失败"); // break; } } System.out.println(Thread.currentThread().getName() + "系统故障"); } } public static void main(String[] args) throws InterruptedException { MyRunnable mt = new MyRunnable(); Thread thread = new Thread(mt, "转账线程"); System.out.println("准备转账"); thread.start(); Thread.sleep(5 * 1000); // 中断子线程 // 调用此方法就会将子线程的状态置为中断状态 thread.interrupt(); } }
b. 线程没有调用以上三种方法时,处在正常运行状态,收到中断通知thread.interrupt()
Thread.interrupted():判断当前线程是否被中断,若中断状态为true,清除中断标志。
/** * 两种中断方法的区别 */ public class ThreadDoubleMethod { private static class MyRun implements Runnable { @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println(Thread.interrupted()); } } } public static void main(String[] args) { MyRun myRun = new MyRun(); Thread thread = new Thread(myRun); thread.start(); thread.interrupt(); } }
Thread.currentThread().isInterrupted():判断指定线程对象是否状态为中断状态,若状态为ture,不会清除中断标志。
类似按下去不会弹起的开关。
/** * 两种中断方法的区别 */ public class ThreadDoubleMethod { private static class MyRun implements Runnable { @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().isInterrupted()); } } } public static void main(String[] args) { MyRun myRun = new MyRun(); Thread thread = new Thread(myRun); thread.start(); thread.interrupt(); } }
中断则状态一直为true
2.5 等待一个线程——join()
eg:主线程中调用thread1.join(),主线程就会进入阻塞状态,直到thread1执行结束,主线程才会继续向后执行
/** * 线程等待 join方法-成员方法 */ public class ThreadJoin { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + "还在学习JavaSE部分"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }, "JavaSE线程"); Thread t2 = new Thread(() -> { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + "进入数据结构的学习部分"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }, "数据结构线程"); System.out.println("先学习JavaSE"); t1.start(); // 主线程死等t1,直到t1执行结束主线程再恢复执行 t1.join(); // 此时走到这里,t1线程已经执行结束,再启动t2线程 t2.start(); // 在哪调用别的线程的join方法,阻塞的是调用join的线程 // main -> 调用t2.join() 阻塞主线程,直到t2完全执行结束再恢复主线程的执行 // 主线程只等t2 2000ms - 2s,若t2在2s之内还没结束,主线程就会恢复执行 t2.join(2000); // t2线程也执行结束了,继续执行主线程 System.out.println("开始学习JavaEE部分"); System.out.println(Thread.currentThread().getName()); } }
2.6 获取当前线程对象的引用——currentThread()
/** * 获取当前线程对象 */ public class ThreadCurrent { public static void main(String[] args) throws InterruptedException { System.out.println(Thread.currentThread().getName()); } }
2.7 休眠当前线程——sleep()
Thread.sleep(long millis):在哪调用,就休眠哪个线程
3. 线程的状态
3.1 观察线程的所有状态
/** * 线程状态 */ public class ThreadState { public static void main(String[] args) { for (Thread.State state: Thread.State.values()) { System.out.println(state); } } }
3.2 线程状态和状态转移的意义
观察 1:关注 NEW 、 RUNNABLE 、 TERMINATED 状态的转换
NEW
新建线程对象就是new状态
/** * 线程的新建,运行和终止状态 */ public class NewAndRunnableState { public static void main(String[] args) { // 产生一个线程对象,该对象默认的状态就是新建状态 - NEW Thread t = new Thread(() -> { for (int i = 0; i < 1000; i++) { } }, "子线程"); System.out.println(t.getName() + " : " + t.getState()); t.start(); while (t.isAlive()) { System.out.println(t.getName() + " : " + t.getState()); } System.out.println(t.getName() + " : " + t.getState()); } }
使用 isAlive 方法判定线程的存活状态。
RUNNABLE
就绪和运行都是runnable状态
TERMINATED
线程的run方法执行完毕,或者抛出异常、不正常执行完毕都会进入终止状态
观察 2:关注 WAITING 、 BLOCKED 、 TIMED_WAITING 状态的转换
三种都属于线程的阻塞状态(该线程需要暂缓执行,这三个造成的暂缓执行的原因不同)
线程间等待与唤醒机制。wait和notify是Object类的方法,用于线程的等待与唤醒,必须搭配synchronized锁来使用。
WAITING
等待被另一个线程唤醒(notify方法)
/** * 三种阻塞状态 */ public class BlockedState { public static void main(String[] args) { Object lock = new Object(); Thread t1 = new Thread(() -> { synchronized (lock) { while (true) { try { // WAITING 线程等待 lock.wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } } } }, "t1"); t1.start(); Thread t2 = new Thread(() -> { synchronized (lock) { // BLOCKED 等待获取锁对象 System.out.println("waiting......"); } }, "t2"); t2.start(); } }
notify方法,唤醒线程
/** * 三种阻塞状态 */ public class BlockedState { public static void main(String[] args) { Object lock = new Object(); Thread t1 = new Thread(() -> { synchronized (lock) { try { // WAITING 线程等待 lock.wait(); System.out.println("被唤醒"); } catch (InterruptedException e) { throw new RuntimeException(e); } } }, "t1"); t1.start(); Thread t2 = new Thread(() -> { synchronized (lock) { // BLOCKED 等待获取锁对象 System.out.println("waiting......"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } lock.notify(); } }, "t2"); t2.start(); } }
TIMED_WAITING
超时等待,需要等待一段时间后自动唤醒
BLOCKED
锁等待,需要等待其他线程释放锁对象
/** * 三种阻塞状态 */ public class BlockedState { public static void main(String[] args) { Object lock = new Object(); Thread t1 = new Thread(() -> { synchronized (lock) { while (true) { try { // TIMED_WAITING 等待时间到了自动唤醒 Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } }, "t1"); t1.start(); Thread t2 = new Thread(() -> { synchronized (lock) { // BLOCKED 等待获取锁对象 System.out.println("waiting......"); } }, "t2"); t2.start(); } }
超时等待,该线程需要等待一段时间之后再恢复执行
该线程在等待别的线程释放资源
观察 3:yield()方法——从运行状态转为就绪状态
/** * yield方法 */ public class YieldTest { public static void main(String[] args) { Thread t1 = new Thread(() -> { while (true) { System.out.println(Thread.currentThread().getName()); // 1号线程就会让出CPU,进入就绪态,等待被CPU继续调度 Thread.yield(); } }, "1号线程"); t1.start(); Thread t2 = new Thread(() -> { while (true) { System.out.println(Thread.currentThread().getName()); } }, "2号线程"); t2.start(); } }
调用yield方法的线程会主动让出CPU资源,从运行态转为就绪态,等待被CPU继续调度。
什么时候让出CPU,又是什么时候被CPU再次调度,都是os调度的,我们无权选择:
a. 让出之后立马又被调度了
b. 让出之后,很久都不调度yield和sleep都会导致线程让出CPU,当线程再次调度回CPU时,有可能会重新读主存。
JVM规范明确表示,yield和sleep方法不一定会强制刷新工作内存,读取主存。