当前位置: 首页 > news >正文

【JAVA EE】多线程、锁、线程池的使用

目录

创建线程

方法一:继承Thread类来创建一个线程类

方法二:实现Runnable,重写run

线程等待

获取当前线程引用

休眠当前线程

线程的状态

synchronized

synchronized的特性

1、互斥

2、刷新内存

死锁

死锁的四个必要条件

避免死锁

volatile关键字

wait方法

notify方法

定时器

线程池 

线程池的创建

往线程池里添加任务


创建线程

方法一:继承Thread类来创建一个线程类

class MyThread extends Thread{
    @Override
    public void run() {
        while(true)
        {
            System.out.println("1111");
        }
    }
}

public class Main {

    public static void main(String[] args) {

        //创建Mythread类的实例
        MyThread myThread=new MyThread();

        //调用start方法启动线程
        myThread.start();
    }

继承Thread,使用匿名内部类

Thread t =new Thread(){
    @Override
    public void run() {
        System.out.println("111");
    }
};
t.start();

方法二:实现Runnable,重写run

class MyRunnable implements  Runnable{
    @Override
    public void run() {
        while (true){
            System.out.println("hello thread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
public class Main {

    public static void main(String[] args) {
        Runnable runnable=new MyRunnable();
        Thread t =new Thread(runnable);
        t.start();
    }
}

使用Runnable和继承Thread方法的区别在于:解耦合

TIPS

.start 和 .run方法的区别:

.run代码中不会创建出新的线程,只有一个主线程,这个主线程里面只能依次执行循环,执行完一个循环再执行另一个

我们可以使用IDEA或者jconsole来观察进程里多线程的情况

jconsole是jdk自带的工具,我们在jdk的bin目录里可以找到 

在启动jconsole前,确保idea程序已经跑起来了,如下会列出当前机器上运行的所有java进程

Thread-0就是我们新建的线程

 如果要更明显的找到新线程,我们在创建线程的时候,给它设置一下名字

Thread t =new Thread(()->{
    while(true){
        System.out.println("hello");
    }
},"这是新线程名字");
t.start();

线程属性

属性获取方法
IDgetid()
名称

getName()

状态getState()
优先级getPriority()
是否后台线程isDaemon()
是否存活isAlive()
是否被中断isinterrupted()
  • 这里的id是java分配的,不是系统api提供的
  • 后台线程不结束,不影响整个进程结束,设置后台线程 t.setDaemon(true)
  • 前台线程没有执行结束,整个进程是一定不会结束的
  • isAlive判定内核线程是不是已经没了,回调方法执行完毕,线程就没了,但是Thread对象可能还在。

lambda表达式有一个语法规则,变量捕获,是可以自动捕获到上层作用域中涉及到的局部变量,所谓变量捕获,就是让lambda表达式,把当前作用域中的变量在lambda内部复制了一份

只能捕获一个final或者实际上是final的常量(变量没有使用final,但是没有进行修改)

在java中并不能像C++一样,直接中断一个线程,只能让线程任务做的快一点,依靠标志位来决定,具体示例如下:

Thread t =new Thread(()->{
    while(!Thread.currentThread().isInterrupted()){
        System.out.println("hello");
    }
},"这是新线程名字");
t.start();
System.out.println("让t线程结束");
t.interrupt();

设置标志位的相关方法

方法说明

public void interrupt()

中断对象关联的线程,如果线程正在阻塞,则以异常方式通知,否则设置标志位
publi static boolean interrupted()判断当前线程的中断标志位是否设置,调用后清除标志位
public boolean isinterrupted()判断对象关联的线程的标志位是否设置,调用后不清除标志位

线程等待

让一个线程等待另一个线程执行结束,再继续执行,本质就是控制线程结束的顺序

join线程等待核心方法

方法说明
public void join()等待线程结束
public void join(long millis)等待线程结束,最多等millis毫秒
public void join(long millis,int nanos)等待时间精度更高

主线程中,调用t.join() 此时就是主线程等待t线程先结束

Thread t =new Thread(()->{
    for(int i=0;i<5;i++)
    {
        System.out.println("t线程工作");
    }
},"这是新线程名字");
t.start();
System.out.println("主线程开始等待");
t.join();
System.out.println("主线程等待结束");

一旦调用join,主线程就会发生阻塞,一直阻塞到t执行完毕了(没有设置等待时间的话),join才会解除阻塞,主线程才会继续执行 

获取当前线程引用

方法说明
public static Thread currentThread()返回当前线程对象的引用

休眠当前线程

方法说明
public static void sleep(long millis)休眠当前线程millis毫秒
public static void sleep(long millis,int nanos)更高精度

线程的状态

JAVA中有六个线程状态:

  • NEW:安排了任务,还未开始行动
  • RUNNABLE:可工作的,又可以分为正在工作中和即将开始工作(也就是就绪状态)
  • BLOCKED:排队等待,由于锁竞争导致的阻塞
  • WAITING:排队等待,由wait这种不固定时间的方式产生的阻塞
  • TIMED_WAITING:排队等待,由于sleep这种固定时间的方式产生的阻塞
  • TERMINATED:工作完成了,Thread对象还在,内核中的线程已经没了

获取线程状态可以通过getState方法,如下:

System.out.println(t.getState());
t.start();
t.join();
System.out.println(t.getState());

synchronized

因为线程的调度顺序是不确定的,这会导致线程不安全的情况,因此我们需要引入锁,再Java的一个对象对应的内存空间中,除了自己定义的一些属性之外,还有一些自带的属性,对象头,对象头中,其中就有属性表示当前对象是否已经加锁

synchronized的特性

1、互斥

synchronized会起到互斥效果,某个线程执行到某个对象的synchronized中时,其他线程如果也执行到同一个对象,synchronized就会阻塞等待。

  • 进入synchronized修饰的代码块,相当于加锁
  • 退出synchronized修饰的代码块,相当于解锁

synchronized除了修饰代码块之外,还可以修饰一个实例方法,或者修饰一个静态方法

对于普通方法,有两种写法:

第一种:

synchronized public void increase()
{
    count++;
}

第二种:

public void increase()
{
    synchronized (this)
    {
        count++;
    }
}

这两种方法是一样的,第一种可以看成第二种的简写 。

如果是修饰静态方法,相当于是针对类对象加锁

class Counter{
    public int count;

    //第一种方法
    synchronized public static void increase()
    {

    }

    //第二种方法
    public static void increase()
    {
        synchronized (Counter.class)
        {
            count++;
        }
    }
}

 两种方法也是等价的。

2、刷新内存

synchronized的工作过程:

  1. 获得互斥锁
  2. 从主内存拷贝变量到最新副本工作的内存
  3. 执行代码
  4. 将更改后的共享变量的值刷新到主内存
  5. 释放互斥锁

当出现如下代码:

synchronized (locker)

 {
       synchronized (locker){

        ……

        }
 }

3、可重入

       第一次加锁,假设能够加锁成功,此时的locker就属于是“被锁定的”状态,进行第二次加锁,很明显,locker已经是锁定状态了,第二次加锁操作,原则上是应该要“阻塞等待”的,应该要等待到锁被释放,才能加锁成功的, 但是实际上,一旦第二次加锁的时候阻塞了,就会出现死锁情况。

        因此synchronized的可重入性,也就是记录当前持有锁的线程,如果是持有锁的线程再进行加锁,则允许加锁。但是释放锁是要到最外层结束才释放,这里使用了引用计数,锁对象中,不仅要记录谁拿到了锁,还要记录,锁被加了几次,每加锁一次,计数器就+1,每解锁一次,计数器就-1,直到0,才真正释放锁

死锁

死锁的四个必要条件

  1. 互斥使用(锁的基本特性):当一个线程持有一把锁之后,另一个线程也想获取到锁,就需要阻塞等待
  2. 不可抢占(锁的基本特性):当锁已经被线程1拿到之后,线程2只能等线程1主动释放,不能强行抢占过来
  3. 请求保持(代码结构):一个线程尝试获取多把锁,先拿到锁1,再拿锁2的时候,锁1不释放
  4. 循环等待/环路等待:等待的依赖关系,形成环

避免死锁

解决死锁,破坏上面三、四条件即可

  • 对于3来说,调整代码结构,避免写“锁嵌套”逻辑
  • 对于4来说,可以约定加锁顺序,就可以避免循环等待,针对锁进行编号,比如约定加多把锁的时候,先加编号小的锁,后加编号大的锁

volatile关键字

1、保证内存可见性

        用volatile修饰的变量,每次都从内存中读取值,而不会因为编译器优化,将常访问未修改的变量值读取到寄存器,内存中对应变量值修改后,程序还是访问的寄存器(java中叫做工作内存)的值,从而导致“内存可见性”问题(也是线程安全的问题)。

        关于内存可见性,还涉及到一个关键概念,JMM(Java内存模型)

2、禁止指令重排序

TIPS

        volatile和synchronized都能对线程安全起到一定的积极作用,但是volatile不能保证原子性,而synchronized保证原子性,synchronized也能保证内存可见性。

wait方法

wait做的事情:

  • 使当前执行代码的线程进行等待,把线程放到等待队列中
  • 释放当前的锁
  • 满足一定条件时被唤醒,重新尝试获取这个锁

notify方法

notify时唤醒等待的线程

  • 方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其他线程,对其发出通知,并使它们重新获取该对象的锁
  • 如果多个线程等待,则由线程调度器随机挑选出一个wait状态的线程(没有先来后到)

定时器

在标准库中java.util.Timer

如下是一个简单的使用示例:

主线程执行schedule方法的时候,就是把这个任务给放到timer对象中,与此同时,timer里头也包含一个线程,这个线程叫做“扫描线程”,一旦时间到,扫描线程就会执行刚才安排的任务了,Timer里可以安排多个任务的。

Timer timer=new Timer();
timer.schedule(new TimerTask() {
    @Override
    public void run() {
        System.out.println("时间到了");
    }
},2000);
System.out.println("程序开始");

此处传参使用匿名内部类的写法,继承了TimerTask,并且创建出一个实例,TimerTask实现了Runnable接口,以此来重写run方法,通过run描述任务的详细情况。

public abstract class TimerTask implements Runnable 

线程池 

线程池就是把线程创建好,放到池子里,后续用的时候直接从池子里取,这样效率会比我们每次都新创建线程,效率更高。

那么为什么从线程池里取的效率会比新创建线程效率更高呢?

线程池的创建

线程池对象不是我们直接new的,而是通过一个专门的方法,返回一个线程池对象,如下:

ExecutorService service= Executors.newCachedThreadPool();

这种方法就是采用工厂模式 ,Executors是一个工厂类,newCachedThreadPool()是一个工厂方法。

newCachedThreadPool线程池里的线程用过之后不着急释放,以备随时再使用,此时构造出来的线程池对象,有一个基本的特点:线程数目是能够动态适应的,随着往线程池里添加任务,这个线程中的线程会根据需要自动被创建出来。

newFixedThreadPool这个工厂方法就是创建一个固定大小的线程池,如下是创建一个包含3个线程的线程池

ExecutorService service= Executors.newFixedThreadPool(3);

除了上述两种工厂方法,还有 newScheduledThreadPool(),newSingleThreadExecutor(),这两种并不常用,这里便不再介绍。

这几个工厂方法生成的线程池,本质上都是对一个类(ThreadPoolExecutor)进行的封装。

ThreadPoolExecutor构造参数:

  • int corePoolSize 核心线程数
  • int maximumPoolSize 最大线程数

这两个参数描述了线程池中,线程的数目,这个线程池里线程的数目是可以动态变化的,变化范围就是[corePoolSize,maximumPoolSize]

  • long keepAliveTime  允许非核心线程数存留时间
  • TimeUnit unit 时间单位 ms,s,min
  • BlockingQueue<Runable> workQueue 阻塞队列,用来存放线程池中的任务,可以根据需要,灵活设置这里的队列是啥,需要优先级就可以设置PriorityBlockingQueue,如果不需要优先级,并且任务数目是相对恒定的,可以使用ArrayBlockingQueue,如果不需要优先级,并且任务数目变动较大的,使用LinkedBlockingQueue
  • ThreadFactory threadFactory 工厂模式的体现,不用手动设置属性
  • RejectedExecutionHandler handler 线程池的拒绝策略,一个线程池能容纳的任务数量,有上限,当到达上线的时候,做出什么样的反应,如下是常用拒绝策略:
ThreadPoolExecutor.AbortPolicy直接抛出异常
ThreadPoolExecutor.CallerRunsPolicy新添加的任务,由添加任务的线程负责执行
ThreadPoolExecutor.DiscardOldestPolicy丢弃任务队列中最老的任务
ThreadPoolExecutor.DiscardPolicy丢弃当前新加的任务

常见问题:

使用线程池,需要设置线程的数目,数目设置多少合适?

        使用实验的方式,对程序进行性能测试,测试过程中尝试修改不同的线程池的线程数目,看哪种情况更符合要求

往线程池里添加任务

通过submit往线程池里注册任务

service.submit(new Runnable() {
    @Override
    public void run() {
        System.out.println("hello");
    }
});

相关文章:

【JAVA EE】多线程、锁、线程池的使用

目录 创建线程 方法一&#xff1a;继承Thread类来创建一个线程类 方法二&#xff1a;实现Runnable&#xff0c;重写run 线程等待 获取当前线程引用 休眠当前线程 线程的状态 synchronized synchronized的特性 1、互斥 2、刷新内存 死锁 死锁的四个必要条件 避免死…...

云计算:定义、类型及对企业的影响

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 云计算&#xff1a;定义、类型及对企业的影响 云计算&#xff1a;定义、类型及对企业的影响 云计算&#xff1a;定义、类型及对企…...

大数据面试题--kafka夺命连环问

1、kafka消息发送的流程&#xff1f; 在消息发送过程中涉及到两个线程&#xff1a;一个是 main 线程和一个 sender 线程。在 main 线程中创建了一个双端队列 RecordAccumulator。main 线程将消息发送给双端队列&#xff0c;sender 线程不断从双端队列 RecordAccumulator 中拉取…...

【大模型】Spring AI Alibaba 对接百炼平台大模型使用详解

目录 一、前言 二、Spring AI概述 2.1 spring ai是什么 2.2 Spring AI 核心能力 2.3 Spring AI 应用场景 三、Spring AI Alibaba 介绍 3.1 Spring AI Alibaba 是什么 3.2 Spring AI Alibaba 核心特点 3.3 Spring AI Alibaba 应用场景 四、SpringBoot 对接Spring AI Al…...

Zabbix如何配置将信息发送到邮件

申请邮箱&#xff0c;下面以QQ邮箱为例 1.1登录邮箱后&#xff0c;点击设置->账号 1.2开启服务 1.3点击生成授权码&#xff08;这里生成的授权码记得保存&#xff0c;后面要用到&#xff09; 创建报警媒介类型 2.1点击管理/媒介/Email 2.2编辑 Email&#xff0c;这里配置的…...

jmeter常用配置元件介绍总结之前置处理器、测试片段

系列文章目录 安装jmeter jmeter常用配置元件介绍总结之前置处理器、测试片段 6.前置处理器6.1用户参数6.2取样器超时6.3.测试片段6.4JSR223 PreProcessor6.5.JDBC PreProcessor 6.前置处理器 在取样器请求之前执行的操作&#xff0c;优先级比取样器高&#xff0c;用来处理一些…...

Redis2:Redis数据结构介绍、通用命令、String类型、Key的层级格式

欢迎来到“雪碧聊技术”CSDN博客&#xff01; 在这里&#xff0c;您将踏入一个专注于Java开发技术的知识殿堂。无论您是Java编程的初学者&#xff0c;还是具有一定经验的开发者&#xff0c;相信我的博客都能为您提供宝贵的学习资源和实用技巧。作为您的技术向导&#xff0c;我将…...

jenkins流水线pipeline

创建项目 1. 新建item 并选择pipeline 1.1 和普通项目配置的区别 普通项目配置目录&#xff1a; pipeline项目目录&#xff1a; pipeline的两种语法 声明式语法 2. 配置 2.1 流水线配置 2.2 选择声明式 声明式需要添加一个名为Jenkinsfile的文件实现流水线 Jenkinsfile的…...

【计网】基于TCP协议的Echo Server程序实现与多版本测试

目录 前言&#xff1a; 1、InitServer类的实现 1.1. 创建流式套接字 1.2. bind 绑定一个固定的网络地址和端口号 1.3.listen监听机制 1.4.完整代码 2. 循环接收接口与服务接口 2.1.accept函数讲解 讲个商场拉客的故事方便我们理解&#xff1a; 2.2.服务接口实现 3.服…...

「QT」几何数据类 之 QVector2D 二维向量类

✨博客主页何曾参静谧的博客&#x1f4cc;文章专栏「QT」QT5程序设计&#x1f4da;全部专栏「VS」Visual Studio「C/C」C/C程序设计「UG/NX」BlockUI集合「Win」Windows程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「PK」Parasolid…...

深度学习——权重初始化、评估指标、梯度消失和梯度爆炸

文章目录 &#x1f33a;深度学习面试八股汇总&#x1f33a;权重初始化零初始化 (Zero Initialization)随机初始化 (Random Initialization)Xavier 初始化&#xff08;Glorot 初始化&#xff09;He 初始化正交初始化&#xff08;Orthogonal Initialization&#xff09;预训练模型…...

Rust: 利用 chrono 库实现日期和字符串互相转换

在 Rust 中&#xff0c;日期和字符串之间的转换通常涉及 chrono 库&#xff0c;这是一个功能强大的日期和时间处理库。以下是如何在 Rust 中将日期转换为字符串以及将字符串转换为日期的示例。 首先&#xff0c;你需要在你的 Cargo.toml 文件中添加 chrono 依赖&#xff1a; …...

【IEEE/EI会议】第八届先进电子材料、计算机与软件工程国际学术会议(AEMCSE 2025)

会议通知 会议时间&#xff1a;2025年4月25-27日 会议地点&#xff1a;中国南京 会议官网&#xff1a;www.aemcse.org 会议简介 第八届先进电子材料、计算机与软件工程国际学术会议&#xff08;AEMCSE 2025&#xff09;由南京信息工程大学主办&#xff0c;将于2025年4月25日…...

volatile 关键字的作用学习

转自: chatgpt 1.介绍 在 C 中&#xff0c;volatile 关键字的作用是告诉编译器一个变量的值可能在任何时刻被外部因素改变&#xff0c;编译器不能对其进行优化。这对于那些可能由硬件、其他线程或信号处理程序更改的变量特别有用。 当一个变量被声明为 volatile 时&#xff…...

数据字典是什么?和数据库、数据仓库有什么关系?

一、数据字典的定义及作用 数据字典是一种对数据的定义和描述的集合&#xff0c;它包含了数据的名称、类型、长度、取值范围、业务含义、数据来源等详细信息。 数据字典的主要作用如下&#xff1a; 1. 对于数据开发者来说&#xff0c;数据字典包含了关于数据结构和内容的清晰…...

Docker平台搭建方法

Docker平台搭建方法 1.1在VMware中创建两个虚拟机&#xff0c;只需要1个网卡&#xff0c;连接192.168.200.0网络。 虚拟机分配2个CPU,2G内存&#xff0c;60G硬盘&#xff0c;主机名分别为server和client,IP地址分别为192.168.200.137和192.168.200.138。server节点还兼做regis…...

JavaScript数组

目录 1. 基本概念 2. 创建数组对象 2.1 字面量创建 2.2 构造函数创建 2.3 静态方法Array.of() 2.4 静态方法Array.from() 2.5 空槽位 3. 常用实例方法 3.1 修改方法 3.11 push 和 unshift 3.12 shift 和 pop 3.13 fill 3.14 cpoyWithin 3.15 reverse 3.16 sort …...

产品经理如何使用项目管理软件推进复杂项目按时上线

前言 相信很多产品同学或多或少都有过这样的经历&#xff1a;平时没有听到任何项目延期风险&#xff0c;但到了计划时间却迟迟无法提测……评审时没有任何argue&#xff0c;提测后发现开发的功能不是自己想要的……费劲九牛二虎之力终于让项目上线了&#xff0c;然而发现成果达…...

构建基于 DCGM-Exporter, Node exporter,PROMETHEUS 和 GRAFANA 构建算力监控系统

目录 引言工具作用概述DCGM-ExporterNode exporterPROMETHEUSGRAFANA小结 部署单容器DCGM-ExporterNode exporterPROMETHEUSGRAFANANode exporterDCGM-Exporter 多容器Node exporterDCGM-ExporterDocker Compose 参考 引言 本文的是适用对象&#xff0c;是希望通过完全基于Doc…...

第13章 聚合函数

一、聚合函数介绍 实际上 SQL 函数还有一类&#xff0c;叫做聚合&#xff08;或聚集、分组&#xff09;函数&#xff0c;它是对一组数据进行汇总的函数&#xff0c;输入的是一组数据的集合&#xff0c;输出的是单个值。&#xff08;可以是一个字段的数据&#xff0c;也可以是通…...

基于大模型的 UI 自动化系统

基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...

突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合

强化学习&#xff08;Reinforcement Learning, RL&#xff09;是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程&#xff0c;然后使用强化学习的Actor-Critic机制&#xff08;中文译作“知行互动”机制&#xff09;&#xff0c;逐步迭代求解…...

在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module

1、为什么要修改 CONNECT 报文&#xff1f; 多租户隔离&#xff1a;自动为接入设备追加租户前缀&#xff0c;后端按 ClientID 拆分队列。零代码鉴权&#xff1a;将入站用户名替换为 OAuth Access-Token&#xff0c;后端 Broker 统一校验。灰度发布&#xff1a;根据 IP/地理位写…...

数据链路层的主要功能是什么

数据链路层&#xff08;OSI模型第2层&#xff09;的核心功能是在相邻网络节点&#xff08;如交换机、主机&#xff09;间提供可靠的数据帧传输服务&#xff0c;主要职责包括&#xff1a; &#x1f511; 核心功能详解&#xff1a; 帧封装与解封装 封装&#xff1a; 将网络层下发…...

select、poll、epoll 与 Reactor 模式

在高并发网络编程领域&#xff0c;高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表&#xff0c;以及基于它们实现的 Reactor 模式&#xff0c;为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。​ 一、I…...

10-Oracle 23 ai Vector Search 概述和参数

一、Oracle AI Vector Search 概述 企业和个人都在尝试各种AI&#xff0c;使用客户端或是内部自己搭建集成大模型的终端&#xff0c;加速与大型语言模型&#xff08;LLM&#xff09;的结合&#xff0c;同时使用检索增强生成&#xff08;Retrieval Augmented Generation &#…...

使用LangGraph和LangSmith构建多智能体人工智能系统

现在&#xff0c;通过组合几个较小的子智能体来创建一个强大的人工智能智能体正成为一种趋势。但这也带来了一些挑战&#xff0c;比如减少幻觉、管理对话流程、在测试期间留意智能体的工作方式、允许人工介入以及评估其性能。你需要进行大量的反复试验。 在这篇博客〔原作者&a…...

Linux nano命令的基本使用

参考资料 GNU nanoを使いこなすnano基础 目录 一. 简介二. 文件打开2.1 普通方式打开文件2.2 只读方式打开文件 三. 文件查看3.1 打开文件时&#xff0c;显示行号3.2 翻页查看 四. 文件编辑4.1 Ctrl K 复制 和 Ctrl U 粘贴4.2 Alt/Esc U 撤回 五. 文件保存与退出5.1 Ctrl …...

GO协程(Goroutine)问题总结

在使用Go语言来编写代码时&#xff0c;遇到的一些问题总结一下 [参考文档]&#xff1a;https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现&#xff1a; 今天在看到这个教程的时候&#xff0c;在自己的电…...

【从零开始学习JVM | 第四篇】类加载器和双亲委派机制(高频面试题)

前言&#xff1a; 双亲委派机制对于面试这块来说非常重要&#xff0c;在实际开发中也是经常遇见需要打破双亲委派的需求&#xff0c;今天我们一起来探索一下什么是双亲委派机制&#xff0c;在此之前我们先介绍一下类的加载器。 目录 ​编辑 前言&#xff1a; 类加载器 1. …...