• (二)Java 线程


    一、创建和运行线程

    1. 方法一,直接使用 Thread

    1. @Slf4j(topic = "c.Test1")
    2. public class Demo {
    3. public static void main(String[] args) {
    4. Thread t = new Thread(){
    5. @Override
    6. public void run() {
    7. log.debug("running");
    8. }
    9. };
    10. t.setName("t1");
    11. t.start();
    12. }
    13. }

    2. 方法二,使用 Runnable 配合 Thread

    把【线程】和【任务】(要执行的代码)分开
    (1)Thread 代表线程
    (2)Runnable 可运行的任务(线程要执行的代码)
    1. @Slf4j(topic = "c.Test2")
    2. public class Test2 {
    3. public static void main(String[] args) {
    4. Runnable r = () -> log.debug("running");
    5. Thread t = new Thread(r, "t2");
    6. t.start();
    7. }
    8. }

    3. 原理之 Thread Runnable 的关系

    小结
    (1)方法 1 是把线程和任务合并在了一起,方法 2 是把线程和任务分开了
    (2)用 Runnable 更容易与线程池等高级 API 配合
    (3)用 Runnable 让任务类脱离了 Thread 继承体系,更灵活

    4. 方法三,FutureTask 配合 Thread

    FutureTask 能够接收 Callable 类型的参数,用来处理有返回结果的情况
    1. @Slf4j(topic = "c.Test1")
    2. public class Demo {
    3. public static void main(String[] args) {
    4. FutureTask task = new FutureTask<>(new Callable() {
    5. @Override
    6. public Integer call() throws Exception {
    7. log.debug("running...");
    8. Thread.sleep(2000);
    9. return 100;
    10. }
    11. });
    12. Thread t1 = new Thread(task, "t1");
    13. t1.start();
    14. }
    15. }

    二、查看进程线程的方法

    1. windows

    任务管理器可以查看进程和线程数,也可以用来杀死进程
    (1)tasklist: 查看进程

    (2)taskkill: 杀死进程

      

    2. linux

    (1)ps -fe :查看所有进程

    (2)ps - fT - p 查看某个进程( PID )的所有线程
    (3)kill : 杀死进程

    (4)top 按大写 H 切换是否显示线程
    (5)top - H - p 查看某个进程( PID )的所有线程

    3. Java

    (1)jps : 命令查看所有 Java 进程
    (2)jstack 查看某个 Java 进程( PID )的所有线程状态
    (3)jconsole : 来查看某个 Java 进程中线程的运行情况(图形界面)

    4. jconsole 远程监控配置

    需要以如下方式运行你的 java
    1. java -Djava.rmi.server.hostname=`ip地址` -Dcom.sun.management.jmxremote -
    2. Dcom.sun.management.jmxremote.port=`连接端口` -Dcom.sun.management.jmxremote.ssl=是否安全连接 -
    3. Dcom.sun.management.jmxremote.authenticate=是否认证 java类

    三、常见方法

    1. start run

    (1)直接调用 run 是在主线程中执行了 run,没有启动新的线程

    (2)使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码

    2. sleep yield

    2.1 sleep

    (1)调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)

    (2)其他线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException

    (3)睡眠结束后的线程未必会立刻得到执行

    (4)建议用 TimeUnit 的 sleep 代替 Thread 的sleep 来获得更好的可读性

    1. @Slf4j(topic = "c.Test8")
    2. public class Test8 {
    3. public static void main(String[] args) throws InterruptedException {
    4. Thread.sleep(1000);
    5. TimeUnit.SECONDS.sleep(1);
    6. TimeUnit.DAYS.sleep(1);
    7. }
    8. }

    2.2 yield

    (1)调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其他线程

    (2)具体的实现依赖于操作系统的任务调度

    2.3 线程优先级

    (1)线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它。

    (2)如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲,优先级几乎没作用。

    3. join 方法详解

    3.1 为什么需要 join

    1. @Slf4j(topic = "c.Test10")
    2. public class Test10 {
    3. static int r = 0;
    4. public static void main(String[] args) throws InterruptedException {
    5. test1();
    6. }
    7. private static void test1() throws InterruptedException {
    8. log.debug("开始");
    9. Thread t1 = new Thread(() -> {
    10. log.debug("开始");
    11. sleep(1);
    12. log.debug("结束");
    13. r = 10;
    14. },"t1");
    15. t1.start();
    16. t1.join();
    17. log.debug("结果为:{}", r);
    18. log.debug("结束");
    19. }
    20. }
    分析
    (1)因为主线程和线程 t1 是并行执行的, t1 线程需要 1 秒之后才能算出 r=10
    (2)而主线程一开始就要打印 r 的结果,所以只能打印出 r=0
    解决方法
    (1) join ,加在 t1.start() 之后即可

    3.2 等待多个结果

    3.3 有时效的 join 

     等待线程运行结束,最多等待 n 毫秒。

    4. interrupt 方法详解

    4.1 打断 sleep、wait、join 的线程

    1. @Slf4j
    2. public class Demo {
    3. public static void main(String[] args) throws InterruptedException {
    4. Thread t1 = new Thread(() -> {
    5. log.debug("sleep...");
    6. try {
    7. Thread.sleep(5000); // wait, join
    8. } catch (InterruptedException e) {
    9. e.printStackTrace();
    10. }
    11. },"t1");
    12. t1.start();
    13. Thread.sleep(1000);
    14. log.debug("interrupt");
    15. t1.interrupt();
    16. log.debug("打断标记:{}", t1.isInterrupted());//打断标记:false
    17. }
    18. }

    4.2 打断正常运行的线程

    1. @Slf4j
    2. public class Demo {
    3. public static void main(String[] args) throws InterruptedException {
    4. Thread t1 = new Thread(() -> {
    5. while(true) {
    6. boolean interrupted = Thread.currentThread().isInterrupted();
    7. if(interrupted) {
    8. log.debug("被打断了, 退出循环");
    9. break;
    10. }
    11. }
    12. }, "t1");
    13. t1.start();
    14. Thread.sleep(1000);
    15. log.debug("interrupt");
    16. t1.interrupt();
    17. }
    18. }

    打断正常运行的线程, Thread.currentThread().isInterrupted() 会变成 true 。

    4.3 模式之两阶段终止

    在一个线程 T1 中如何 优雅 终止线程 T2
    这里的【优雅】指的是给 T2 一个料理后事的机会。

    错误思路:

    (1)使用线程对象的 stop() 方法停止线程

            stop 方法会真正杀死线程,如果这时线程锁住了共享资源,那么当它被杀死后就再也没有机会释放锁,其他线程将永远无法获取锁。

    (2)使用 System.exit(int) 方法停止线程

            目的仅是停止一个线程,但这种做法会让整个程序都停止。

     

     

    1. @Slf4j(topic = "c.Test3")
    2. public class Test {
    3. public static void main(String[] args) throws InterruptedException {
    4. TwoPhaseTermination tpt = new TwoPhaseTermination();
    5. tpt.start();
    6. Thread.sleep(3500);
    7. tpt.stop();
    8. }
    9. }
    10. @Slf4j(topic = "c.TwoPhaseTermination")
    11. class TwoPhaseTermination{
    12. private Thread monitor;
    13. // 启动监控线程
    14. public void start(){
    15. monitor = new Thread(()->{
    16. while (true){
    17. Thread current = Thread.currentThread();
    18. if (current.isInterrupted()){
    19. log.debug("料理后事");
    20. break;
    21. }
    22. try {
    23. Thread.sleep(1000);
    24. log.debug("执行监控记录");
    25. } catch (InterruptedException e) {
    26. e.printStackTrace();
    27. // 重新设置打断标记
    28. current.interrupt();
    29. }
    30. }
    31. });
    32. monitor.start();
    33. }
    34. // 停止监控线程
    35. public void stop(){
    36. monitor.interrupt();
    37. }
    38. }

    4.4 打断 park 线程

    1. @Slf4j(topic = "c.Test14")
    2. public class Test14 {
    3. private static void test4() {
    4. Thread t1 = new Thread(() -> {
    5. for (int i = 0; i < 5; i++) {
    6. log.debug("park...");
    7. LockSupport.park();
    8. log.debug("打断状态:{}", Thread.interrupted());
    9. }
    10. });
    11. t1.start();
    12. sleep(1);
    13. t1.interrupt();
    14. }
    15. private static void test3() throws InterruptedException {
    16. Thread t1 = new Thread(() -> {
    17. log.debug("park...");
    18. LockSupport.park();
    19. log.debug("unpark...");
    20. log.debug("打断状态:{}", Thread.currentThread().isInterrupted());
    21. }, "t1");
    22. t1.start();
    23. sleep(1);
    24. t1.interrupt();
    25. }
    26. public static void main(String[] args) throws InterruptedException {
    27. test4();
    28. }
    29. }

    test3:如果打断标记已经是 true, park 会失效

    test4:可以使用 Thread.interrupted() 清除打断状态。Thread.interrupted()会重置打断标记为 false

    4.5 不推荐的方法

    还有一些不推荐使用的方法,这些方法已过时,容易破坏同步代码块,造成线程死锁

     

    四、主线程与守护线程

    默认情况下, Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。
    1. @Slf4j(topic = "c.Test15")
    2. public class Test15 {
    3. public static void main(String[] args) throws InterruptedException {
    4. Thread t1 = new Thread(() -> {
    5. while (true) {
    6. if (Thread.currentThread().isInterrupted()) {
    7. break;
    8. }
    9. }
    10. log.debug("结束");
    11. }, "t1");
    12. t1.setDaemon(true);
    13. t1.start();
    14. Thread.sleep(1000);
    15. log.debug("结束");
    16. }
    17. }
    注意
    (1)垃圾回收器线程就是一种守护线程
    (2)Tomcat 中的 Acceptor Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等待它们处理完当前请求

    五、五种状态

    这是从 操作系统 层面来描述的
    (1)【初始状态】仅是在语言层面创建了线程对象,还未与操作系统线程关联
    (2)【可运行状态】(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执行
    (3)【运行状态】指获取了 CPU 时间片运行中的状态
    1️⃣当 CPU 时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换
    (4)【阻塞状态】
    1️⃣如果调用了阻塞 API ,如 BIO 读写文件,这时该线程实际不会用到 CPU,会导致线程上下文切换,进入【阻塞状态】
    2️⃣等 BIO 操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】
    3️⃣与【可运行状态】的区别是,对【阻塞状态】的线程来说只要它们一直不唤醒,调度器就一直不会考虑调度它们
    (5)【终止状态】表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态

     

    六、六种状态

    这是从 Java API 层面来描述的
    根据 Thread.State 枚举,分为六种状态
    (1)NEW 线程刚被创建,但是还没有调用 start() 方法
    (2)RUNNABLE 当调用了 start() 方法之后,注意, Java API 层面的 RUNNABLE 状态涵盖了 操作系统 层面的 【可运行状态】、【运行状态】和【阻塞状态】(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为是可运行)
    (3)BLOCKED WAITING TIMED_WAITING 都是 Java API 层面对【阻塞状态】的细分,后面会在状态转换一节详述
    (4)TERMINATED 当线程代码运行结束

     

  • 相关阅读:
    学习网址,持续更新
    Redis五种数据结构-常用命令
    LeetCode: 数组峰值与谷值问题总结 - Python
    帮你搜遍GitHub!一次性解决面试中常见并发编程问题,附笔记合集
    利用epoll创建自己的后台服务,实现对各个客户端网络通信(含示例代码)
    基于docker搭建code-server(浏览器中的VScode)
    Mongodb 安装脚本(附服务器自启动)
    python3.9运行出现编码问题,在其他电脑上可以正常运行
    Excel 转为 PDF,PNG,HTML等文件
    c语言:深度学习递归
  • 原文地址:https://blog.csdn.net/yirenyuan/article/details/128093118