:::tips 作者:LemonCat🐱
时间:2023/8/1 :::

膜拜大神

Java 并发相关的包

  • java.util.concurrent
  • java.util.concurrent.atomic
  • java.util.concurrent.locks

image.png

Doug Lea

百度百科:百度百科-Doug Lea

  • java.util.concurrent 包的作者 -> 并发包创始人!!! 跪拜!!!

image.png

为什么要学习多线程

硬件方面

  • 摩尔定律失效
    • 单核性能翻倍 -> 遇到了瓶颈
    • 单核主频提高有限 -> 倡导多核处理器
  • Java 多线程更好利用CPU资源 -> 想要程序运行更快更好 - 需要我们的并行和3并发编程

软件方面

  1. 可以用来吹牛X o.O
  2. 充分利用多核处理器
  3. 提高程序性能,高并发系统
  4. 提高程序吞吐量,异步+回调等生产需求

弊端及问题

  1. 线程安全问题 -> 比如熟知的买票问题
  2. 线程锁问题
  3. 线程性能问题

简单的 start

下载 OpenJDK8 源代码

OpenJDK OpenJDK

为什么需要下载 OpenJDK

  • 在我们追源码的时候 我们发现一些方法我们追不进去 或者 是 native 方法
  • 此时我们就需要 通过下载 OpenJDK 来看底层 C/C++ 的具体的操作

    下载流程:

  1. 进入官网 下载

image.png

  1. 找到 Java8 SE -> 点击下载

image.png

  1. 解压完成

image.png

start 线程解读

  • 我们看看究竟是怎样启动(start)一个线程的

调用步骤:

  1. 线程初始Java程序

    1. public static void main(String[] args) {
    2. Thread t1 = new Thread(() -> {
    3. }, "t1");
    4. t1.start();
    5. }
  2. 进入 start 方法(表示线程开始的方法)

    1. 我们发现 start方法中 真正开启线程的方法是-> start0
    2. 调用完 start0 方法我们看到 started = true; 表示线程已经开始 ```java public synchronized void start() { /**

      • This method is not invoked for the main method thread or “system”
      • group threads created/set up by the VM. Any new functionality added
      • to this method in the future may have to also be added to the VM. *
      • A zero status value corresponds to state “NEW”. */ if (threadStatus != 0) throw new IllegalThreadStateException();

      /* Notify the group that this thread is about to be started

      • so that it can be added to the group’s list of threads
      • and the group’s unstarted count can be decremented. */ group.add(this);

      boolean started = false; try { start0(); started = true; } finally { try {

      1. if (!started) {
      2. group.threadStartFailed(this);
      3. }

      } catch (Throwable ignore) {

      1. /* do nothing. If start0 threw a Throwable then
      2. it will be passed up the call stack */

      } } }

  1. 3. 进入 `start0` -> 我们发现这是个 native 方法 我们进不去了 底层是 C/C++
  2. ```java
  3. private native void start0();
  1. native 是调用了本地方法, 找到 OpenJDK 查看其源码 -> ../openjdk-8\hotspot

image.png

  • 这里 java -version 显示的版本 中 Java HotSpot
  • 就是右边的对应的 hotspot
  • 就是 C/C++对 Java 底层方法的描绘

image.png

  1. 接下来 6 7 8 我们详细的研究一下 private native void start0(); -> 底层究竟做了什么!
  2. Thread.c -> 路径:..\openjdk-8\jdk\src\share\native\java\lang
    1. Java 线程是通过 start 的方法启动执行的,主要内容在native方法start0
    2. OpenJDK 的写 JNI 一般是一一对应的 即 Thread.java 对应的就是 Thread.c
    3. start0 其实就是JVM_StartThread。此时查看源代码可以看到在jvm.h中找到了声明,jvm.cpp中有实现

image.png

  1. 我们可以看到 start0 方法没有返回值 调用了 JVM_StartThread 即调用 JVM 去开启一个线程
    1. jvm.cpp -> 路径·:..\openjdk-8\hotspot\src\share\vm\prims
  2. Thread::start(native_thread); -> 操作系统给我们提供了 一个线程

image.png
image.png

  1. Thread.cpp
    1. 我们有 Thread.java -> 就会有 Thread.cpp
    2. 终于在这里调用了 操作系统的线程启动 os::start_thread(thread);
    3. 通过注释我们能知道 -> 操作系统提供的原生线程!帮助我们完成线程的启动~

image.png

Java 多线程相关概念

一把锁

  • synchronized 关键字

    两个并

  • 并发 和 并行

    并发(concurent)

    是在同一实体上的多个事件,是在同一台处理器上“同时”处理多个任务,同一时刻,其实是只有一个事件在发生。

    并行(parallel)

    是在不同实体上的多个事件,是在多台处理器上同时处理多个任务,同一时刻,大家都真的在做事情,你做你的,我做我的

    并发 VS 并行

  • Concurrent -> 两队抢一个咖啡机

  • Parallel -> 两队各自有一台咖啡机

image.png

三个程

  • 进程 线程 管程
  • 通过上面 start 线程的分析,确定了其实 进程线程都来源于操作系统给分配的os::start_thread(thread);线程

    进程:

    • 系统中运行的一个应用程序就是一个进程,每一个进程都有它自己的内存空间和系统资源。

      线程:

    • 也被称为轻量级进程,在同一个进程内基本会有一个或多个线程,是大多数操作系统进行调度的基本单元

      管程:

    • 管程 -> Monitor(监视器),也就是我们平时说的锁

    • Monitor 其实是一种同步机制,他的义务是保证(同一时间)只有一个线程可以访问被保护的数据和代码。
    • JVM 中同步是基于进入和退出监视器对象(Monitor 管程对象)来实现的,每个对象实例都会有一个 Monitor 对象,
    • Monitor 对象会和 Java 对象一同创建并销毁,它底层是由 C++ 语言来实现的。

简而言之:管程就是一个对象对应的那个 Monitor 监视器 对象

参考资料:JVM 第三版 image.png

理解:

  • o 这个对象加了 synchronized 这个锁 -> 就变成了一个 Monitor 监视器
  • 哪个线程抢到了这把锁o 才能执行(即现在 t2 有了这把锁 其他线程只能等待)
    • 可以类比成 -> 排队上厕所 人就是线程 o 就是厕所门的锁 ```java Object o = new Object();

new Thread(() -> { synchronized (o) { // 监视器 o(一把锁)

  1. }

}, “t2”).start();

  1. <a name="kfVRx"></a>
  2. ### 用户线程和守护线程
  3. Java 线程分为用户线程(User Thread) 和 守护线程(Daemon Thread)
  4. - 不做过多配置 - 默认是用户线程
  5. <a name="lBi8W"></a>
  6. #### 用户线程
  7. 是系统的工作线程,它会完成这个程序需要完成的业务操作
  8. <a name="Ezi4H"></a>
  9. #### 守护线程
  10. 是一种特殊的线程,为其他线程服务的,在后台默默地完成一些系统性的服务 比如 垃圾回收线程。
  11. <a name="UaQJO"></a>
  12. #### 示例 1
  13. - main 线程 -> 主线程 用户线程
  14. - 垃圾回收线程 -> 隐藏的 守护线程
  15. ```java
  16. public class ThreadStart {
  17. public static void main(String[] args) { // Java 一切方法的入口
  18. }
  19. }

Daemon 属性

线程的 daemon 属性决定了是哪种线程
Thread 类 源码中有两个方法可以设置线程属性

  1. setDaemon() -> 设置守护线程
  2. isDaemon() -> 判断是否为守护线程
    • true 表示是 守护线程
    • false 表示是 用户线程

image.png

示例 2

  • 两个线程 分别是 main 线程 和 t1 线程

Code:

  1. public class ThreadStart02 {
  2. // main 线程
  3. public static void main(String[] args) throws InterruptedException {
  4. System.out.println(Thread.currentThread().getName() + ":线程开始工作---" +
  5. (Thread.currentThread().isDaemon() ? "守护线程" : "用户线程"));
  6. // t1 线程
  7. Thread t1 = new Thread(() -> {
  8. System.out.println(Thread.currentThread().getName() + ":线程开始工作---" +
  9. (Thread.currentThread().isDaemon() ? "守护线程" : "用户线程"));
  10. while (1 == 1) {
  11. }
  12. }, "t1");
  13. t1.start();
  14. Thread.sleep(3000); // main 线程休息 3s
  15. System.out.println(Thread.currentThread().getName() + ":线程结束工作");
  16. }
  17. }

Show:
image.png

  • 两者默认都是 用户线程 - 两者互不打扰
  • main 线程即便结束 程序不会结束 因为有 t1 这个用户线程 仍在工作

    示例 3

  • 在示例 2 的基础上 将 t1 设置成守护线程 t1.setDaemon(true);

Code:

  1. public class ThreadStart02 {
  2. // main 线程
  3. public static void main(String[] args) throws InterruptedException {
  4. System.out.println(Thread.currentThread().getName() + ":线程开始工作---" +
  5. (Thread.currentThread().isDaemon() ? "守护线程" : "用户线程"));
  6. // t1 线程
  7. Thread t1 = new Thread(() -> {
  8. System.out.println(Thread.currentThread().getName() + ":线程开始工作---" +
  9. (Thread.currentThread().isDaemon() ? "守护线程" : "用户线程"));
  10. while (1 == 1) {
  11. }
  12. }, "t1");
  13. t1.setDaemon(true);
  14. t1.start();
  15. Thread.sleep(3000); // main 线程休息 3s
  16. System.out.println(Thread.currentThread().getName() + ":线程结束工作");
  17. }
  18. }

Show:
image.png

  • t1 是守护线程,当main线程结束是,作为一个服务线程,没有服务对象就没有必要继续运行了
    • 如果用户线程全部结束了,意味着程序需要完成的业务操作已经结束了,系统可退出了。
    • 假如当系统只剩下守护线程的时候,java虚拟机会 自动退出
  • 即当用户线程全部消亡 - 守护线程也自动嗝屁~

    示例 4

  • 我们将示例 3 中设置守护线程的代码放到开始后

    • t1.start();
    • t1.setDaemon(true); // 守护线程

Code:

  1. public class ThreadStart02 {
  2. // main 线程
  3. public static void main(String[] args) throws InterruptedException {
  4. System.out.println(Thread.currentThread().getName() + ":线程开始工作---" +
  5. (Thread.currentThread().isDaemon() ? "守护线程" : "用户线程"));
  6. // t1 线程
  7. Thread t1 = new Thread(() -> {
  8. System.out.println(Thread.currentThread().getName() + ":线程开始工作---" +
  9. (Thread.currentThread().isDaemon() ? "守护线程" : "用户线程"));
  10. while (1 == 1) {
  11. }
  12. }, "t1");
  13. t1.start();
  14. t1.setDaemon(true); // 守护线程
  15. Thread.sleep(3000); // main 线程休息 3s
  16. System.out.println(Thread.currentThread().getName() + ":线程结束工作");
  17. }
  18. }

Show:
image.png

  • setDaemon(true)方法必须在start()之前设置,否则报IIIegalThreadStateException异常

image.png

  • setDaemon() 的源码中我们也能知道 这个方法必须放在 线程开始之前