java线程(1)https://developer.aliyun.com/article/1530842
3.3.1 start 与 run
调用start(能不能运行任务调度器说了算)
public static void main(String[] args) { Thread thread = new Thread(){ @Override public void run(){ log.debug("我是一个新建的线程正在运行中"); FileReader.read(fileName); } }; thread.setName("新建线程"); thread.start(); log.debug("主线程"); }
输出:程序在 t1 线程运行, run()
方法里面内容的调用是异步的 Test4.java
11:59:40.711 [main] DEBUG com.concurrent.test.Test4 - 主线程 11:59:40.711 [新建线程] DEBUG com.concurrent.test.Test4 - 我是一个新建的线程正在运行中 11:59:40.732 [新建线程] DEBUG com.concurrent.test.FileReader - read [test] start ... 11:59:40.735 [新建线程] DEBUG com.concurrent.test.FileReader - read [test] end ... cost: 3 ms
调用run
将上面代码的thread.start();
改为 thread.run();
输出结果如下:程序仍在 main 线程运行, run()
方法里面内容的调用还是同步的
12:03:46.711 [main] DEBUG com.concurrent.test.Test4 - 我是一个新建的线程正在运行中 12:03:46.727 [main] DEBUG com.concurrent.test.FileReader - read [test] start ... 12:03:46.729 [main] DEBUG com.concurrent.test.FileReader - read [test] end ... cost: 2 ms 12:03:46.730 [main] DEBUG com.concurrent.test.Test4 - 主线程
小结
直接调用 run()
是在主线程中执行了 run()
,没有启动新的线程 使用 start()
是启动新的线程,通过新的线程间接执行 run()
方法 中的代码
当调用start方法后,线程状态会由“NEW”变为“RUNABLE”,此时再次调用start方法会报错 IllegalThreadStateExceptio n(非法的状态异常)
3.3.2 sleep 与 yield
sleep
- 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
- 其它线程可以使用 interrupt 方法打断正在睡眠的线程,那么被打断的线程这时就会抛出
InterruptedException
异常【注意:这里打断的是正在休眠的线程,而不是其它状态的线程】 - 睡眠结束后的线程未必会立刻得到执行(需要分配到cpu时间片)
- 建议用 TimeUnit 的
sleep()
代替 Thread 的sleep()
来获得更好的可读性
//休眠一秒 TimeUnit.SECONDS.sleep(1); //休眠一分钟 TimeUnit.MINUTES.sleep(1);
yield
- 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程
- 具体的实现依赖于操作系统的任务调度器(就是可能没有其它的线程正在执行,虽然调用了yield方法,但是也没有用)
小结
yield使cpu调用其它线程,但是cpu可能会再分配时间片给该线 ;而sleep需要等过了休眠时间之后才有可能被分配cpu时间片(阻塞状态)
3.3.3 线程优先级
线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它 如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用
- 设置方法:
thread1.setPriority(Thread.MAX_PRIORITY); //设置为优先级最高Copy
3.3.4 join
在主线程中调用t1.join,则主线程会等待t1线程执行完之后再继续执行 Test10.java
private static void test1() throws InterruptedException { log.debug("开始"); Thread t1 = new Thread(() -> { log.debug("开始"); sleep(1); log.debug("结束"); r = 10; },"t1"); t1.start(); t1.join(); log.debug("结果为:{}", r); log.debug("结束"); }
用于等待某个线程结束。哪个线程内调用join()方法,就等待哪个线程结束,然后再去执行其他线程。
如在主线程中调用ti.join(),则是主线程等待t1线程结束
Thread thread = new Thread(); //等待thread线程执行结束 thread.join(); //最多等待1000ms,如果1000ms内线程执行完毕,则会直接执行下面的语句,不会等够1000ms thread.join(1000);Copy
3.3.5 interrupt 方法详解
打断 sleep,wait,join 的线程
先了解一些interrupt()方法的相关知识:博客地址
sleep,wait,join 的线程,这几个方法都会让线程进入阻塞状态,以 sleep 为例Test7.java
public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread() { @Override public void run() { log.debug("线程任务执行"); try { Thread.sleep(10000); // wait, join } catch (InterruptedException e) { //e.printStackTrace(); log.debug("被打断"); } } }; t1.start(); Thread.sleep(500); log.debug("111是否被打断?{}",t1.isInterrupted()); t1.interrupt(); log.debug("222是否被打断?{}",t1.isInterrupted()); Thread.sleep(500); log.debug("222是否被打断?{}",t1.isInterrupted()); log.debug("主线程"); }
输出结果:(我下面将中断和打断两个词混用)可以看到,打断 sleep 的线程, 会清空中断状态,刚被中断完之后t1.isInterrupted()
的值为true
,后来变为false
,即中断状态会被清除。那么线程是否被中断过可以通过异常来判断。【同时要注意如果打断被join()
,wait()
blocked的线程也是一样会被清除,被清除(interrupt status will be cleared)的意思即中断状态设置为false
,被设置( interrupt status will be set)的意思就是中断状态设置为true
】
17:06:11.890 [Thread-0] DEBUG com.concurrent.test.Test7 - 线程任务执行 17:06:12.387 [main] DEBUG com.concurrent.test.Test7 - 111是否被打断?false 17:06:12.390 [Thread-0] DEBUG com.concurrent.test.Test7 - 被打断 17:06:12.390 [main] DEBUG com.concurrent.test.Test7 - 222是否被打断?true 17:06:12.890 [main] DEBUG com.concurrent.test.Test7 - 222是否被打断?false 17:06:12.890 [main] DEBUG com.concurrent.test.Test7 - 主线程
打断正常运行的线程
打断正常运行的线程, 线程并不会暂停,只是调用方法Thread.currentThread().isInterrupted();
的返回值为true,可以判断Thread.currentThread().isInterrupted();
的值来手动停止线程
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(); }
终止模式之两阶段终止模式
Two Phase Termination,就是考虑在一个线程T1中如何优雅地终止另一个线程T2?这里的优雅指的是给T2一个料理后事的机会(如释放锁)。
如下所示:那么线程的isInterrupted()
方法可以取得线程的打断标记,如果线程在睡眠sleep
期间被打断,打断标记是不会变的,为false,但是sleep
期间被打断会抛出异常,我们据此手动设置打断标记为true
;如果是在程序正常运行期间被打断的,那么打断标记就被自动设置为true
。处理好这两种情况那我们就可以放心地来料理后事啦!
代码实现如下:
@Slf4j public class Test11 { public static void main(String[] args) throws InterruptedException { TwoParseTermination twoParseTermination = new TwoParseTermination(); twoParseTermination.start(); Thread.sleep(3000); // 让监控线程执行一会儿 twoParseTermination.stop(); // 停止监控线程 } } @Slf4j class TwoParseTermination{ Thread thread ; public void start(){ thread = new Thread(()->{ while(true){ if (Thread.currentThread().isInterrupted()){ log.debug("线程结束。。正在料理后事中"); break; } try { Thread.sleep(500); log.debug("正在执行监控的功能"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); e.printStackTrace(); } } }); thread.start(); } public void stop(){ thread.interrupt(); } }
打断 park 线程
打断 park 线程, 不会清空打断状态
private static void test3() throws InterruptedException { Thread t1 = new Thread(() -> { log.debug("park..."); LockSupport.park(); log.debug("unpark..."); log.debug("打断状态:{}", Thread.currentThread().isInterrupted()); }, "t1"); t1.start(); sleep(0.5); t1.interrupt(); }
输出
21:11:52.795 [t1] c.TestInterrupt - park... 21:11:53.295 [t1] c.TestInterrupt - unpark... 21:11:53.295 [t1] c.TestInterrupt - 打断状态:true
如果打断标记已经是 true, 则 park 会失效
private static void test4() { Thread t1 = new Thread(() -> { for (int i = 0; i < 5; i++) { log.debug("park..."); LockSupport.park(); log.debug("打断状态:{}", Thread.currentThread().isInterrupted()); } }); t1.start(); sleep(1); t1.interrupt(); }
3.3.6 sleep,yiled,wait,join 对比
3.4 守护线程
默认情况下,java进程需要等待所有的线程结束后才会停止,但是有一种特殊的线程,叫做守护线程,在其他线程全部结束的时候即使守护线程还未结束代码未执行完java进程也会停止。普通线程t1可以调用t1.setDeamon(true);
方法变成守护线程
注意 垃圾回收器线程就是一种守护线程 Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等 待它们处理完当前请求
3.5 线程状态之五种状态
五种状态的划分主要是从操作系统的层面进行划分的
- 初始状态,仅仅是在语言层面上创建了线程对象,即
Thead thread = new Thead();
,还未与操作系统线程关联 - 可运行状态,也称就绪状态,指该线程已经被创建,与操作系统相关联,等待cpu给它分配时间片就可运行
- 运行状态,指线程获取了CPU时间片,正在运行
- 当CPU时间片用完,线程会转换至【可运行状态】,等待 CPU再次分配时间片,会导致我们前面讲到的上下文切换
- 阻塞状态
- 如果调用了阻塞API,如BIO读写文件,那么线程实际上不会用到CPU,不会分配CPU时间片,会导致上下文切换,进入【阻塞状态】
- 等待BIO操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】
- 与【可运行状态】的区别是,只要操作系统一直不唤醒线程,调度器就一直不会考虑调度它们,CPU就一直不会分配时间片
- 终止状态,表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态
3.6 线程状态之六种状态
这是从 Java API 层面来描述的,我们主要研究的就是这种。状态转换详情图:地址 根据 Thread.State 枚举,分为六种状态 Test12.java
- NEW 跟五种状态里的初始状态是一个意思
- RUNNABLE 是当调用了
start()
方法之后的状态,注意,Java API 层面的RUNNABLE
状态涵盖了操作系统层面的【可运行状态】、【运行状态】和【io阻塞状态】(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为是可运行) BLOCKED
,WAITING
,TIMED_WAITING
都是 Java API 层面对【阻塞状态】的细分,后面会在状态转换一节 详述