前言

在JavaSE阶段,我们已经学习了并发编程的基础知识,包括了解一点进程线程、并发并行、同步异步等等概念,回顾一下:

并发编程

该知识库,要更深入的去了解这方面的知识,以达到面试的知识厚度。

  1. 单核 cpu 下,多线程不能实际提高程序运行效率,只是为了能够在不同的任务之间切换,达到不同线程轮流使用cpu的作用,也就是说,单核cpu不存在并行
  2. 多核 cpu 可以并行跑多个线程,但能否提高程序运行效率还是要分情况的,如“计算密集型”和“IO密集型”

线程

创建线程的三种方法

  1. Thread原生的run方法
  2. 调用Runnable接口
  3. 使用FutureTask(Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。) ```java

@Slf4j(topic = “c.Test1”) public class demo1创建线程 {

  1. /**
  2. * 覆盖原生run方法
  3. */
  4. public void test() {

// Thread t = new Thread() { // @Override // public void run() { // System.out.println(“running”); // } // };

  1. // lambda简化写法
  2. Thread t = new Thread( () -> System.out.println("running"));
  3. t.setName("t1");
  4. t.start();
  5. }
  6. /**
  7. * 使用runnable接口
  8. */
  9. public void test2(){
  10. Runnable target = new Runnable() {
  11. @Override
  12. public void run() {
  13. System.out.println("running...");
  14. }
  15. };
  16. Thread t = new Thread(target);
  17. t.setName("t1");
  18. t.start();
  19. }
  20. /**
  21. * 使用FutureTask(类似Runnable,但是他有返回值,以及具备抛出异常的功能)
  22. Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。
  23. */
  24. public void test3() throws ExecutionException, InterruptedException {
  25. FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {
  26. @Override
  27. public Integer call() throws Exception {
  28. System.out.println("running...");
  29. Thread.sleep(1000);
  30. return null;
  31. }
  32. });
  33. Thread t1 = new Thread(task);
  34. t1.start();
  35. task.get(); // 必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。
  36. }

}

  1. **小结:**<br />用Runnable接口可以更容易与线程池等高级 API 配合,但是方法二其实到底还是通过方法一执行的!
  2. > <a name="NQQ6G"></a>
  3. #### 线程运行诊断
  4. > 当发送CPU占用过高或者迟迟未得到运算结果时(比如死锁)
  5. > 我们这时需要定位占用CPU过高的线程
  6. > 1. **"top" **命令,查看是哪个**进程**占用CPU过高
  7. > 1. **"ps H -eo pid, tid(线程id), %cpu | grep 刚才通过top查到的进程号 "**通过ps命令进一步查看是哪个线程占用CPU过高
  8. > 1. "**jstack 进程id** "通过查看进程中的线程的nid,刚才通过ps命令看到的tid来对比定位,注意jstack查找出的线程id16进制的,需要转换
  9. <a name="RUTDt"></a>
  10. ### 线程上下文切换
  11. 因为以下一些原因导致cpu不再执行当前的线程,转而执行另一个线程的代码
  12. - 线程的cpu时间片用完
  13. - 垃圾回收
  14. - 有更高优先级的线程需要运行
  15. - 线程自己调用了sleepyieldwaitjoinparksynchronized、.lock等方法
  16. 当发生上下文切换时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java中对应的概念就是程序计数器(Program Counter Register)
  17. - 状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等
  18. - 上下文频繁切换会导致性能下降
  19. <a name="Crhlm"></a>
  20. ###
  21. <a name="qczIa"></a>
  22. ### 线程组
  23. 每个Thread必然存在于⼀个ThreadGroup中,Thread不能独⽴于ThreadGroup存在。<br />如果在new Thread时没有显式指定线程组,那么默认将⽗线程 (当前执⾏new Thread的线程)线程组设置为⾃⼰的线程组。
  24. > ThreadGroup是⼀个标准的向下引⽤的树状结构,这样设计的原因是防⽌"上级"线程被"下级"线程引⽤⽽⽆法有效地被GC回收。
  25. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/598646/1650125014092-1103fc23-5263-4c68-9bb0-b46eff0d88fa.png#clientId=u3f2bf911-a7e6-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=94&id=u76d560de&margin=%5Bobject%20Object%5D&name=image.png&originHeight=140&originWidth=874&originalType=binary&ratio=1&rotation=0&showTitle=false&size=19996&status=done&style=none&taskId=uf4072472-b587-4f76-97da-99c79db9daf&title=&width=585.2000122070312)
  26. <a name="Hkmdu"></a>
  27. ### 线程优先级
  28. Java中线程优先级可以指定,范围是1~10,默认是5<br /> Java只是给操作系统⼀个优先级的建议,线程最终在操作系统的优先级是多少还是由操作系统决定。
  29. <a name="XMMnp"></a>
  30. ### 守护线程
  31. 默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,当其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。
  32. 守护线程默认的优先级比较低
  33. ```java
  34. log.debug("开始运行...");
  35. Thread t1 = new Thread(() -> {
  36. log.debug("开始运行...");
  37. sleep(2);
  38. log.debug("运行结束...");
  39. }, "daemon");
  40. t1.setDaemon(true); // 设置该线程为守护线程
  41. t1.start();
  42. sleep(1);
  43. log.debug("运行结束...");

注意:

  • 垃圾回收器、JIT线程就是一种守护线程

线程状态

线程状态从两个角度观察,分为五种状态(操作系统角度)和六种状态(JavaAPI角度)
image.png
【初始状态】仅是在语言层面创建了线程对象,还未与操作系统的线程进行关联

【可运行状态】执行start之后(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执行

【运行状态】指获取了 CPU 时间片运行中的状态

  • 当 CPU 时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换

【阻塞状态】
有两种情况会进入阻塞:

  1. 读取文件:
    • 如果调用了阻塞 API,如 BIO 读写文件,这时该线程实际不会用到 CPU,会导致线程上下文切换,进入 【阻塞状态】
    • 等 BIO 操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】
    • 与【可运行状态】的区别是,对【阻塞状态】的线程来说只要它们一直不唤醒,调度器就一直不会考虑 调度它们
  2. 被notify/ interrupt 唤醒然后进行竞争锁,竞争失败者

【终止状态】表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态

从操作系统层面,线程可分为三种状态,分别是:NEW、RUNNABLE、TERMINATED
image.png

  • NEW 线程刚被创建,但是还没有调用 start() 方法
  • RUNNABLE 当调用了 start() 方法之后,注意,Java API 层面的 RUNNABLE 状态涵盖了 操作系统 层面的 【可运行状态】、【运行状态】和【阻塞状态】(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为 是可运行)
  • BLOCKED , WAITING , TIMED_WAITING 都是 Java API 层面对【阻塞状态】的细分
  • TERMINATED 线程代码运行结束