2.7.2 yield
调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程
具体的实现依赖于操作系统的任务调度器
对比 yield与sleep
相同点:两者都是让当前线程不再执行,调度执行其他线程
不同点:
调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)。 此时当前线程不会被执行,直至达到sleep的时间或线程被唤醒;调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程,此时线程也就可能被再次调用,这依赖于任务调度器。
sleep()可以有时间参数,yield()中没有时间参数。
2.7.3 线程优先级
线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它
如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用
关于yield和优先级的对比
@Slf4j(topic = "c.Test9") public class Test9 { public static void main(String[] args) { Runnable task1 = () -> { int count = 0; for (;;) { System.out.println("---->1 " + count++); } }; Runnable task2 = () -> { int count = 0; for (;;) { // Thread.yield(); System.out.println(" ---->2 " + count++); } }; Thread t1 = new Thread(task1, "t1"); Thread t2 = new Thread(task2, "t2"); // t1.setPriority(Thread.MIN_PRIORITY); // t2.setPriority(Thread.MAX_PRIORITY); t1.start(); t2.start(); } }
2.7.4 sleep和yield使用案例
使用sleep和yield可以防止CPU占用达到 100 %
2.8 join 方法详解
2.8.1 为什么需要 join
下面的代码执行,打印 r 是什么?
@Slf4j(topic = "c.Test10") public class Test10 { static int r = 0; public static void main(String[] args) throws InterruptedException { test1(); } private static void test1() throws InterruptedException { log.debug("开始"); Thread t1 = new Thread(() -> { log.debug("开始"); sleep(1); log.debug("结束"); r = 10; },"t1"); t1.start(); log.debug("结果为:{}", r); log.debug("结束"); } }
打印结果为:0。
分析
因为主线程和线程 t1 是并行执行的,t1 线程需要 1 秒之后才能算出 r=10
而主线程一开始就要打印 r 的结果,所以只能打印出 r=0
一句话:因为异步性,后面的程序可能先执行,因此当进程睡觉的时候,就把r=0输出了 因此结果为0
解决方法
用 sleep 行不行?为什么? 可行,但是main线程需要和t1线程 sleep相同的时间。不推荐使用
用 join,加在 t1.start() 之后即可
2.8.2 应用之同步(案例1)
以调用方角度来讲,如果
需要等待结果返回,才能继续运行就是同步
不需要等待结果返回,就能继续运行就是异步
2.8.3 应用之同步(等待多个结果)
问,下面代码 cost 大约多少秒?
@Slf4j(topic = "c.TestJoin") public class TestJoin { static int r1 = 0; static int r2 = 0; public static void main(String[] args) throws InterruptedException { test2(); } private static void test2() throws InterruptedException { Thread t1 = new Thread(() -> { sleep(1); r1 = 10; }); Thread t2 = new Thread(() -> { sleep(2); r2 = 20; }); t1.start(); t2.start(); long start = System.currentTimeMillis(); log.debug("join begin"); t1.join(); log.debug("t1 join end"); t2.join(); log.debug("t2 join end"); long end = System.currentTimeMillis(); log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);// 2s } }
分析如下
第一个 join:等待 t1 时, t2 并没有停止, 而在运行
第二个 join:1s 后, 执行到此, t2 也运行了 1s, 因此也只需再等待 1s
如果颠倒两个 join 呢?
最终输出的仍然是 2s
图解分析:
2.8.4 有时效的 join
没等够时间
@Slf4j(topic = "c.TestJoin") public class TestJoin { static int r = 0; static int r1 = 0; static int r2 = 0; public static void main(String[] args) throws InterruptedException { test3(); } public static void test3() throws InterruptedException { Thread t1 = new Thread(() -> { sleep(2); r1 = 10; }); long start = System.currentTimeMillis(); t1.start(); // 线程执行结束会导致 join 结束 log.debug("join begin"); long end = System.currentTimeMillis(); log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start); } }
等够时间
t1.join(3000);
2.9 interrupt 方法详解
2.9.1 打断 sleep,wait,join 的线程
这几个方法都会让线程进入阻塞状态,但是打断后,会清空打断状态
以打断 sleep 的线程为例:
@Slf4j(topic = "c.Test11") public class Test11 { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { log.debug("sleep..."); try { Thread.sleep(5000); // wait, join } catch (InterruptedException e) { e.printStackTrace(); } },"t1"); t1.start(); Thread.sleep(1000); log.debug("interrupt"); t1.interrupt(); log.debug("打断标记:{}", t1.isInterrupted()); } }
运行结果:
2.9.2 打断正常运行的线程
打断正常运行的线程, 不会清空打断状态 ,所以我们可以利用标记让线程进行结束。
@Slf4j(topic = "c.Test12") public class Test12 { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { while (true) { boolean interrupted = Thread.currentThread().isInterrupted(); if(interrupted){//可以通过打断标记 进行打断 log.debug("被打断了,退出循环"); break; } } }, "t1"); t1.start(); Thread.sleep(1000); log.debug("interrupt"); t1.interrupt();//t1的打断标记变为true } }
通过标记打断线程的好处:因为直接把线程终结了,人家线程事情都没干完。不如跟他说一声,说我要打断你,他处理完事情后自行了断不更好。很优雅