浅谈: 计算机—JVM—Java线程—池

news2024/12/25 9:55:00

计算机的基本组成 

计算机的基本组成

计算机存储模型(CPU、寄存器、高速缓存、内存、外存)

现代计算机系统CPU和内存之间其实是有一个cache的层级结构的。比内存速度更快的存储介质(SRAM),普通内存一般是DRAM,这种读写速度更快的介质充当CPU和内存之间的Cache,这就是缓存。当CPU写cache时,cache中没有相应的内存数据需要把该数据从内存加载到cache中,然后更新cache,再更新内存。

寄存器

 CPU的一部分,空间在kb级别,用来暂存指令、数据和地址。CPU处理数据时先把数据取到寄存器中,再作处理。

内存

计算机运行过程中的存储主力,存储指令(编译好的代码段),运行中的各个静态,动态,临时变量,外部文件的指针等。程序的运行都是在内存中进行,内存的大小决定可运行程序的大小。

硬盘

机械硬盘(HDD): 磁性碟片
    机械硬盘主要由磁盘盘片、磁头、主轴与传动轴等组成,数据就存放在磁盘盘片中,与留声机上的唱片相似(硬盘是上下双磁头)。
固态硬盘: 闪存颗粒、DRAM
    闪存的固态硬盘(SSD): 可以移动,数据保护不受电源控制,寿命较长,能适应于各种环境:笔记本硬盘、微硬盘、存储卡、U盘等。
    DRAM: 仿效传统硬盘的设计,高性能的存储器,理论上可以无限写入,需要独立电源来保护数据安全。可被大部分操作系统的文件系统工具进行卷设置和管理,非主流的设备。

数据与二进制的对应规则

计算机编码:
ASCII: 一个字节byte有八个二进制位可组合出256种状态,每个状态对应一个符号,有256个符号。ASCII规定了128个字符(对英语字符与二进制位间做了统一规定)
非ASCII码: 不同国家,使用的字符不一致,仅128个字符不够用,所以针对本国字符特点,使用了256个字符。
Unicode: 该数字码全球统一,属于字符编码的标准,即字符集。nicode并未明确编码规则,根据Unicode字符集有常用的编码规则: UTF-8、UTF-16等。
UTF-8: 作为Unicode一种实现方式,使用1-4个字节进行编码。UTF-8与ASCII码表示的二进制数完全一致。

JVM

JVM内存模型

 堆:

堆: 新生代、老年代。
老年代(Old)占2/3,年轻代(Young)占1/3,
年轻代中包含Eden区和Survivor区,Survivor区包含From(S0区)区和To(S1区),默认Eden区、From区、To区的比例为8:1:1,
当Eden区内存不足时会触发Minor gc,没有被回收的对象进入到Survivor区,同时分代年龄+1,
再次触发Minor gc时,From区中的对象会移动到To区,Minor gc会回收Eden区和From区中的垃圾对象,对象的分代年龄会再次增加,
当分代年龄增加到15以后,对象会进入到老年代。当老年代内存不足时,会触发Full gc,
如果Full gc无法释放足够的空间,会触发OOM内存溢出,在进行Minor gc或Full gc时,会触发STW(Stop The World)即停止用户线程。

非堆(永久代/元空间区):

常量(final修饰)、静态变量(static修饰)、类信信息(版本、字段等)、运行时常量池,操作的是直接内存。 

运行时数据区(方法区、堆的生命周期与JVM一致,与线程无关)

1.程序计数器

存放下一条指令所在单元的地址的地方。

2.线程栈(虚拟机栈)

JVM的每一个线程对应一个线程栈,一个线程的每个方法会分配一块栈帧内存空间。栈帧超过了栈的深度,会报StackOverflowError
局部变量表:  存储基本数据类型(int、float、byte等),如果是引用数据类型,则存储的是其在堆中的内存地址,也就是指向对象的一个指针。
操作数栈:  主要用来存计算过程的中间结果,计算过程中变量临时的存储空间。CPU会从操作数栈中弹出所需的操作数,计算后压入到操作数栈顶。
i++ 和 ++i 的区别:
1、i++: 从局部变量表取出 i 并压入操作栈(load memory),对局部变量表中的 i 自增 1(add&store memory),将操作栈栈顶值取出使用,如此线程从操作栈读到的是自增之前的值。
2、++i: 先对局部变量表的 i 自增 1(load memory&add&store memory),然后取出并压入操作栈(load memory),再将操作栈栈顶值取出使用,线程从操作栈读到的是自增之后的值。
i++ 不是原子操作, 1、i 从局部变量表(内存)取出,2、压入操作栈(寄存器),操作栈中自增,3、使用栈顶值更新局部变量表(寄存器更新写入内存),这3步可能被另一个线程打断,产生数据互相覆盖问题。
动态链接:  在Java源文件被编译成字节码文件时,所有的变量和方法引用都作为符号引用存到class文件的常量池中。通过常量池中指向方法的符号引用来表示一个方法调用了其他方法时。动态链接是将符号引用转换为直接引用。
方法出口:  
方法执行时有两种退出情况:
    1.正常退出,即正常执行到任何方法的返回字节码指令,如 return、ireturn、areturn 等;
    2.异常退出。
方法退出就返回至方法当前被调用的位置。
    1.返回值压入上层调用栈帧。
    2.异常信息抛给能够处理的栈帧。
    3.PC计数器指向方法调用后的下一条指令。

3.本地方法栈

与虚拟机栈结构一致,本地方法栈执行的是Java底层由C++编写的native方法。

4.方法区

在JDK8之前Perm Space(永久代),JDK8中就是Metaspace(元空间)。
方法区中存储的有: 常量、静态变量、类信息、运行时常量池,操作的是直接内存。
方法区内存不足,会报OutOfMemoryError

5.堆

堆是JVM所管理内存的最大一部分,在虚拟机启动时启动。
对象实例和数组都在堆上分配。堆内存不足,也会报OutOfMemoryError

JVM中对象及常量、局部变量、全局变量的存储位置

1.局部变量

基本数据类型:  变量名和变量值存储在方法栈中。
引用数据类型:  变量值存在栈中(堆中对象的地址),指向的对象存储在堆中(如new出来的对象)。

2.全局变量

基本数据类型:  变量名和变量值存储在堆内存中。
引用数据类型:  变量名存储的是所引用对象的内存地址,变量名和变量值存储在堆内存中。

static关键字小结:

(1)特点:
    1、static是一个修饰符,用于修饰成员(成员变量,成员函数)static修饰的成员变量 称之为静态变量或类变量.
    2、static修饰的成员被所有的对象共享.
    3、static优先于对象存在, 因为static的成员随着类的加载就已经存在.
    4、static修饰的成员多了一种调用方式, 可以直接被类名所调用,(类名.静态成员).
    5、static修饰的数据是共享数据, 对象中的存储的是特有的数据.
(2)成员变量和静态变量的区别:
    1、生命周期的不同:
        成员变量随着对象的创建而存在随着对象的回收而释放.
        静态变量随着类的加载而存在随着类的消失而消失.
    2、调用方式不同:
        成员变量只能被对象调用.
        静态变量可以被对象调用,也可以用类名调用(推荐用类名调用).
    3、别名不同:
        成员变量也称为实例变量.
        静态变量称为类变量.
    4、数据存储位置不同:
        成员变量数据存储在堆内存的对象中, 所以也叫对象的特有数据.
        静态变量数据存储在方法区(共享数据区)的静态区, 所以也叫对象的共享数据.
(3)静态使用时需要注意的事项:
    1、静态方法只能访问静态成员(非静态既可以访问静态,又可以访问非静态).
    2、静态方法中不可以使用this或者super关键字.
    3、主函数是静态的.
类的初始化顺序: 静态代码 --> 父类构造函数 --> 变量初始化 --> 实例代码块 --> 自身构造函数

JVM的加载

Java文件的编译

Java文件通过JDK中的javac指令进行编译成成一个字节码(.class)文件
.java → 词法分析器 → tokens流 → 语法分析器 → 语法树/抽象语法树 → 语义分析器→ 注解抽象语法树 → 字节码生成器 → .class文件

类加载器的分类 

启动类加载器(Bootstrap ClassLoader):负责加载%JAVA_HOME%/lib目录下的 jar 包和类。它只能加载自己能够识别的类。
扩展类加载器(Extendtion ClassLoader):它负责加载<JAVA_HOME>\lib\ext目录中的或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以使用扩展类加载器。
应用程序类加载器(Application ClassLoader):负责加载ClassPath上所指定的类库,如果应用程序没有自定义过自己的类加载器,一般情况下这就是程序的默认类加载器。
自定义类加载器(CustomerClassLoader):自己定义的类加载器,如tomcat、jboss都会根据j2ee规范自行实现ClassLoader。

双亲委派加载

类加载机制:

JVM 把.class文件加载到虚拟机的内存中,并对数据进行验证、准备、解析和初始化,将这些内容转换成方法区中的运行时数据结构, 可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。
装载–>链接(验证、准备、解析)–>初始化
类加载机制
1.装载(运行期完成)
    通过全限定名获取类的二进制字节流。
    将二进制字节流转化成方法区的运行时数据结构。
    实例化对象,放入堆中作为这个类的访问入口。
2.连接
    验证: 保证不会危害虚拟机的安全,包括文件格式验证、元数据验证、字节码验证、符号引用验证。
    准备: 给类变量(static修饰的变量)在方法区中分配内存,初始化默认值,各个数据类型的默认值如下。
        byte   (byte)0
        short  (short)0
        int    0
        long   0L
        float  0.0f
        double 0.0d
        char   '\u0000'
        boolean    false
        reference  null
    解析: 将符号引用替换为直接引用
        符号引用: 用一组符号来描述目标,可以是任何的字面量。
        直接引用: 直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
3.初始化: 为静态变量赋值,执行静态代码块。

Java线程

线程(thread): 操作系统能够进行运算调度的最小单位。

Java线程的创建:

Java创建线程是采用内核线程: 由操作系统内核支持的线程,通过操纵调度器对线程进行调度(内核切换线程),将线程的任务映射到各个处理器上。
1.new Thread: 创建了一个对象
2.运行start()方法: 调native方法 —> 调C++ -> 调操作系统(通过操作系统的库函数创建的线程)。
Thread.c
jvm.h
jvm.cpp
thread.cpp
os_linux.cpp
优点:一个线程阻塞,不影响另一个线程的执行;
缺点:
1、由于是基于内核线程实现的,所以各种线程操作,如创建、休眠及同步,都需要进行系统调用,而系统调用的代价相对较高;
2、操作系统内核支持的线程数量是有限的,不能无限制地创建线程;
Go语言采是用户线程: 内核线程,都可认为是用户线程。用户线程的创建、同步、销毁和调度完全在用户自己的程序代码中完成,不需要内核的帮助,系统内核不能感知到用户线程的存在。
优点:在用户自己的程序中实现,不需要调用内核,操作非常快速且低消耗,能够支持规模更大的线程数量;
缺点:所有的线程操作都要由用户自己处理,线程的创建、销毁、切换和调度等,实现起来较复杂;

线程基本信息  


    线程ID(Thread ID): 线程的唯一标识符,同一个进程内不同线程ID不会重叠.
    线程名称: 方便用户识别,系统会自动分配名称,也可以用户指定.
    线程优先级: 表示线程调度的优先级,优先级越高活的CPU执行的机会就越大.
    线程状态: 标识线程的执行状态,为新建/就绪/运行/阻塞/结束等状态的一种.
    其他: 是否为守护线程等等.
    程序计数器: 在线程的结构中,程序计数器很重要,它记录着线程下一条指令的代码段内存地址.
    栈内存信息: 在线程的结构中,程序计数器很重要,它记录着线程下一条指令的代码段内存地址.

public class Thread implements Runnable {
    private long tid; 线程的ID, 由线程自己分配
    private volatile String name; 线程的名称, 可以手动设置(setName())
    private int priority; 线程的优先级, 默认5, 最小1, 最大10
    private ThreadGroup group; 线程所属的线程组
    private ClassLoader contextClassLoader; 次线程上下文ClassLoader
    private boolean daemon = false; 是否是守护线程
    private volatile int threadStatus = 0; 线程的状态. 对应静态枚举类Stete

    public void run(){} 线程的功能方法
    public synchronized void start(){} 线程准备就绪,等待程序调用run()方法. 异步操作, 调用顺序不代表实际启动的顺序.
    public final void stop(){} 终止当前线程, 不建议使用
    public static native void sleep(long var0){} 指定毫秒数让正在执行的线程暂停, 具体取决于系统定时器和调度程序的精度和准确性
    public final void setPriority(int var1){} 设置优先级, 不一定有用, 影响线程执行优先级的因素很多
    public final void setDaemon(boolean var1){} 设置守护线程标识
    public static native void yield(){} 放弃当前CPU执行资源,放弃时间不确定
    public final native boolean isAlive(){} 判断线程是否处于活动状态. 活动状态: 启动未终止
    public final void resume(){} 恢复线程, 不建议使用
    public final void suspend(){} 暂停线程, 不建议使用
    public void interrupt(){} 中断线程
    public static boolean interrupted(){} 测试当前线程是否中断, 并且具备清除状态的功能
    public boolean isInterrupted(){} 测试当前线程是否中断
    public final void join(){} 等待调用的这个线程终止
    public Thread.State getState(){} 获取线程状态
}
线程的生命周期

 线程状态

新建(New)状态: 创建后尚未启动(未调用start()方法)的线程处于这种状态。
就绪(Ready)状态: 当调用线程对象的start()方法,线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行。
运行(Running)状态: 运行状态(统线程状态中的 Ready 和 Running),该状态可能正在CPU执行,也可能在等待CPU执行。
    注意: 线程要想进入运行状态执行,首先必须处于就绪状态中。
无限期等待(Waiting)状态: 处于这种状态的线程不会被分配CPU执行时间,它们要等待被其他线程显式地唤醒。以下方法会让线程陷入无限期的等待状态。
    没有设置Timeout参数的Object.wait()方法
    没有设置Timeout参数的Object.wait()方法
限期等待(Timed Waiting)状态: 处于这种状态的线程也不会被分配CPU执行时间,不过无须等待其他线程显式地唤醒,在一定时间之后它们会由系统自动唤醒。以下方法会让线程进入限期等待状态:
    设置了Timeout参数的Object.wait(long millis)方法
    设置了Timeout参数的Object.wait(long millis)方法
阻塞(Blocked)状态: 线程在获取synchronized排他锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
死亡(Dead)状态: 线程执行完了或者因异常退出了run()方法,该线程生命周期结束。
    public static enum State {
        NEW, 新建
        RUNNABLE, 就绪, 运行
        BLOCKED, 阻塞
        WAITING, 等待
        TIMED_WAITING, 计时等待
        TERMINATED; 结束
        private State() {
        }
    }

 线程池

Java线程的设计

1、工作任务/逻辑单元:(Runnable 和 Callable)
2、执行机制/载体:(Thread、Executor 框架)
线程池的好处
  一: 降低资源消耗.
  二: 提高性能.
  三: 方便线程管理.

线程池核心流程


线程池七参数:


    1.核心线程数(corePoolSize): 线程池中的核心线程数量(最少的线程个数),即使这些线程处理空闲状态,它们也不会被销毁,除非设置了 allowCoreThreadTimeOut; 默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。
    2.任务队列(workQueue): 任务队列,当核心线程全部繁忙时,由 execute/submit 方法提交的 Runnable 任务存放到该任务队列中,等待被核心线程来执行。
    3.最大线程数(maximumPoolSize): 线程池中允许的最大线程数,当核心线程全部繁忙且任务队列存满之后,线程池会临时追加线程,直到总线程数达到 maximumPoolSize。
    4.线程空闲超时时间(keepAliveTime): 如果一个线程处于空闲状态,并且当前的线程数量大于 corePoolSize,那么在指定时间后,这个空闲线程会被销毁。
    5.时间单位(unit): keepAliveTime 的时间单位(天、小时、分、秒......)。
    6.线程工厂(threadFactory): 用于创建线程,可默认也可以自定义实现。
    7.拒绝策略(handler): 核心线程 corePoolSize 正在执行任务、任务队列workQueue 已满、并且线程池中的线程数达到 maximumPoolSize 时,就需"拒绝"掉新提交过来的任务。

JDK提供的四种内置的拒绝策略

AbortPolicy:  异常中止策略,异常中止,无特殊场景;
DiscardPolicy:  丢弃策略,无关紧要的任务(文章点击量、商品浏览量等);
DiscardOldestPolicy:  弃老策略,允许丢掉老数据的场景;
CallerRunsPolicy:  调用者运行策略,不允许失败场景(对性能要求不高、并发量较小的场景);

线程池的大小

1、CPU 密集型:  任务需要大量使用 CPU 进行运算,但是没有阻塞,CPU一直高速运行;该情况下应尽可能减少 CPU 对线程的切换,所以要使线程数尽可能少;
线程数 = CPU 核心数 + 1;
2、IO 密集型:  任务需要进行大量的 IO 操作,那么就会有大量的阻塞等待,CPU大部分时间是在阻塞,该情况下应尽可能减少阻塞所带来的消耗,所以要使线程数尽可能多;
线程数 = CPU 核心数 * 2;

线程池的底层状态切换(位运算)

private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
COUNT_BITS = 29
CAPACITY = (1 << COUNT_BITS) - 1
线程池源码-线程池状态值
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
RUNNING = -1 << COUNT_BITS;

线程池关闭

shutdownNow()
   1、停止接收新的任务请求;
   2、将正在执行的任务 interrupt 中断;
   3、忽略任务队列里面的任务,也就是任务队列里面的任务不会执行了;4、返回没有执行的任务集合;
shutdown()
   1、停止接收新的任务请求;
   2、内部正在执行的任务和任务队列中等待的任务会继续执行直至完成;
   3、等所有任务都执行完成后才会关闭线程池;

execute(Runnable command)方法: 

/*
 * 任务提交执行,在未来的某个时间执行给定的任务,任务可以在新线程或现有池线程中执行;
 * 如果任务无法提交执行,要么因为此执行器已关闭或因为其容量已达到,则该任务由当前RejectedExecutionHandler处理;
 */
public void execute(Runnable command) {
    //如果command是null,则抛出空指针异常;
    if (command == null)
        throw new NullPointerException();

    //获取clt控制变量的值,clt控制变量记录着runState和workerCount的值;
    int c = ctl.get();
    /*
     * workerCountOf方法获取控制变量ctl低29位的值,表示当前活动的线程数;
     * 如果当前活动线程数小于核心线程数corePoolSize,则新建一个线程放入线程池中,并把任务添加到该线程中运行;
     */
    if (workerCountOf(c) < corePoolSize) {
        /*
         * addWorker()方法:
         * 第一个参数是要提交的工作任务;
         * 第二个参数:
         * 如果是true,根据corePoolSize来判断,表示添加核心线程;(保持稳定的线程数来处理任务)
         * 如果是false,根据maximumPoolSize来判断,表示添加非核心线程;(应对突发的任务处理)
         */
        if (addWorker(command, true)) //addWorker()方法会检查运行状态和工作线程数,如果返回false则说明线程没有创建成功;
            //添加成功则返回;
            return;
        //如果添加失败,则重新获取控制变量ctl的值;
        c = ctl.get();
    }

    //到这里了,说明workerCountOf(c) >= corePoolSize,并且如果当前线程池是运行状态并且工作任务添加到任务队列成功
    if (isRunning(c) && workQueue.offer(command)) {
        // 重新获取ctl值
        int recheck = ctl.get();
        // 再次判断线程池是否是运行状态,如果不是运行状态,由于之前已经把command添加到workQueue中了,此时需要移除该command;
        if (! isRunning(recheck) && remove(command))
            //线程池不是运行状态并且移除该command成功,则使用拒绝策略对该任务进行处理;
            reject(command);
        /*
         * 线程池是运行状态,获取一下线程池中的有效线程数,如果是0,则执行addWorker()方法创建一个线程;
         * addWorker()方法:
         * 第一个参数为null,表示在线程池中创建一个线程,但不启动;
         * 第二个参数为false,表示是非核心线程;
         * 接下来这里没有写else,表示如果判断workerCount大于0,则不需要做什么处理,直接返回,
         * 加入到workQueue中的command会在将来的某个时刻被执行;
         */
        else if (workerCountOf(recheck) == 0)
            //此处是创建一个线程,但并没有传入任务,因为任务已经被添加到workQueue中了,到时候线程会从从workQueue中获取任务来执行;
            //所以当workerCountOf(recheck) == 0时执行addWorker(null, false);
            //是为了保证线程池在RUNNING状态下必须要有一个线程来执行任务;
            addWorker(null, false);
    }

    /*
     * 如果执行到这里,有两种情况:
     * 1. 线程池已经不是RUNNING状态;
     * 2. 线程池是RUNNING状态,但往workQueue已经放不进去,即workerCount >= corePoolSize,并且workQueue已满;
     * 此时再次调用addWorker()方法,第二个参数为false,表示非核心线程,如果失败则拒绝该任务;
     */
    else if (!addWorker(command, false))
        reject(command);
}

addWorker(Runnable firstTask, boolean core)方法: 

private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
	// 外层for循环
    for (;;) {
		// 获取线程池控制变量的值
        int c = ctl.get();
        // 获取线程池运行状态
        int rs = runStateOf(c);
        
        // if判断,如果rs >= SHUTDOWN 并且 (判断3个条件,只要有1个不满足),返回false;
        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;
			
		// 内层for循环
        for (;;) {
            // 获取线程池线程数
            int wc = workerCountOf(c);
            // 如果wc超过CAPACITY,也就是ctl的低29位的最大值(二进制是29个1),返回false;
            // core为addWorker方法的第二个参数,若为true根据corePoolSize比较,若为false根据maximumPoolSize比较;
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            // 尝试增加workerCount,如果成功,则跳出外层for循环
            if (compareAndIncrementWorkerCount(c))
                break retry;
            // 如果增加workerCount失败,则重新获取控制变量ctl的值
            c = ctl.get();  // Re-read ctl
            // 如果当前线程池的运行状态不等于rs,说明线程池运行状态已被改变,返回外层for循环继续执行
            if (runStateOf(c) != rs)
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }
	
	// Worker线程是否启动
    boolean workerStarted = false;
	// Worker线程是否添加
    boolean workerAdded = false;
	// 是一个线程类,该类实现了Runnable接口的
    Worker w = null;
    try {
        // 根据firstTask来创建Worker对象
        w = new Worker(firstTask);
        // 每一个Worker对象都会创建一个线程
        final Thread t = w.thread;  // Thread t = new Thread(worker);
        if (t != null) {
			// 创建一个lock锁
            final ReentrantLock mainLock = this.mainLock;
			// 上锁
            mainLock.lock();
            try {
				// 检查线程池运行状态
                // Recheck while holding lock.
                // Back out on ThreadFactory failure or if
                // shut down before lock acquired.
                int rs = runStateOf(ctl.get());
                // rs < SHUTDOWN表示是RUNNING状态;
                // 如果rs是RUNNING状态或者rs是SHUTDOWN状态并且firstTask为null,向线程池中添加线程。
                // 因为在SHUTDOWN时不会在添加新的任务,但还是会执行workQueue中的任务
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
					// 检查线程已经是运行状态,抛出非法线程状态异常
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    // workers是一个HashSet
                    workers.add(w);
                    int s = workers.size();
                    // largestPoolSize记录着线程池中出现过的最大线程数量
                    if (s > largestPoolSize)
						// 把历史上出现过的最大线程数的值更新一下
                        largestPoolSize = s;
					// Worker线程添加成功
                    workerAdded = true;
                }
            } finally {
				// 释放ReentrantLock锁
                mainLock.unlock();
            }
            if (workerAdded) {
                // 启动线程
                t.start(); //因为这个t线程里面包装一个worker对象,所以启动后就会执行worker的run()方法
				// Worker线程已经启动
                workerStarted = true;
            }
        }
    } finally {
		// Worker线程没有启动成功
        if (! workerStarted)
            addWorkerFailed(w);
    }
	// 返回Worker线程是否启动成功
    return workerStarted;
}


//Worker类
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {

	/** Thread this worker is running in.  Null if factory fails. */
	final Thread thread;
	
	/** Initial task to run.  Possibly null. */
	Runnable firstTask;

	Worker(Runnable firstTask) {
		setState(-1); // inhibit interrupts until runWorker
		this.firstTask = firstTask;
		this.thread = getThreadFactory().newThread(this);
		//Thread t = new Thread(worker);
		//t.start(); ==> worker.run()
	}

	@Override
	public void run() {
		runWorker(this);
	}
	//.........................	省略
}

getTask()方法: 

private Runnable getTask() {
    // 表示上一次从任务队列中取任务时是否超时
    boolean timedOut = false; // Did the last poll() time out?
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);
        // Check if queue empty only if necessary.
        /*
         如果线程池为`SHUTDOWN`状态且任务队列为空(线程池shutdown状态可以处理任务队列中的任务,不再接受新任务)
         或者 线程池状态>=STOP,则意味着线程池不必再获取任务了,将当前工作线程数量-1并返回null;
        */
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }
        //线程池的线程个数
        int wc = workerCountOf(c);
        // Are workers subject to culling?
        /*
         timed变量用于判断是否需要进行超时控制;
         allowCoreThreadTimeOut默认是false,也就是核心线程不允许进行超时;
         wc > corePoolSize,表示当前线程池中的线程数量大于核心线程数量;
         表示对于超过核心线程数量的这些线程,需要进行超时控制(默认情况)
        */
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        /*
         * 两个条件全部为true,则通过CAS使工作线程数-1,即去除工作线程:
         * 条件1:工作线程数大于maximumPoolSize,或(工作线程需要超时控制且上次在任务队列拉取任务超时)
         * 条件2:wc > 1或任务队列为空
         * 如果减1失败,则返回重试;
         */
        if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }
		
        try {
            /*
             * 执行到这里,说明已经经过前面的校验,开始真正获取task;
             * 根据timed来判断,如果工作线程有超时时间,则通过任务队列的poll方法进行超时等待方式获取任务,
             * 如果在keepAliveTime时间内没有获取到任务,则返回null,否则通过take方法;
             * take方法表示如果这时任务队列为空,则会阻塞直到任务队列不为空;
             * 一般poll()用于普通线程、take()用于核心线程
             */
            Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();
			// r不为空,则返回该Runnable
            if (r != null)   
				return r;
            // 如果 r == null,说明已经超时得不到任务,timedOut设置为true
            timedOut = true;
        } catch (InterruptedException retry) {
            // 如果获取任务时当前线程发生了中断,则设置timedOut为false并返回循环重试
            timedOut = false;
        }
    }
}

processWorkerExit(Worker w, boolean completedAbruptly)方法: 

private void processWorkerExit(Worker w, boolean completedAbruptly) {
	//completedAbruptly为true表示线程异常执行结束
	//completedAbruptly为false表示线程正常执行结束
	if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
		decrementWorkerCount();
	//从线程set集合中移除工作线程,该过程需要加锁,因为HashSet是线程不安全的集合
	final ReentrantLock mainLock = this.mainLock;
	mainLock.lock();
	try {
		//统计完成的任务数:将该worker已完成的任务数追加到线程池已完成的任务数
		completedTaskCount += w.completedTasks;
		//从HashSet<Worker>中移除该worker
		workers.remove(w); 
	} finally {
		//释放锁
		mainLock.unlock();
	}
	//根据线程池状态进行判断是否结束线程池
	tryTerminate();
	int c = ctl.get();
	//当线程池是RUNNING或SHUTDOWN状态时
	if (runStateLessThan(c, STOP)) {
		//如果worker不是异常结束:
		if (!completedAbruptly) {
			//如果allowCoreThreadTimeOut=true,最小线程个数就可以变为0;
			int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
			//但是,如果等待队列有任务,至少保留一个worker来处理任务;
			if (min == 0 && ! workQueue.isEmpty())
				min = 1;
			//如果工作线程大于等于核心线程,直接return就行了,否则就需要添加一个线程;
			if (workerCountOf(c) >= min)
				return; // replacement not needed
		}
		//是异常执行结束的,添加一个线程去执行任务
		addWorker(null, false);
	}
}

runWorker(Worker w)方法: 

final void runWorker(Worker w){
    //获取当前线程
    Thread wt = Thread.currentThread();
	//Worker的一个成员变量,第一个任务firstTask,是用户提交过来的
    Runnable task = w.firstTask;
	//然后把w.firstTask置为null
    w.firstTask = null;
    //允许响应中断
    w.unlock();
    // 线程退出的原因,true是任务导致,false是线程正常退出
    boolean completedAbruptly = true;
    try{
        // 当前任务为空,且当前任务队列为空,停止循环
        while (task != null || (task = getTask()) != null) {
            // 上锁处理并发问题,防止在shutdown()时终止正在运行的worker
            w.lock();
            // 如果线程池是stop状态,并且线程没有被中断,就要确保线程被中断,如果线程池不是,确保线程池没有被中断;
            // 清除当前线程的中断标志,做一个recheck来应对shutdownNow方法;
            if ((runStateAtLeast(ctl.get(),STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(),STOP))) 
				&& !wt.isInterrupted())
                wt.interrupt(); //中断
            try {
                // 执行前(空方法,由子类重写实现)
                beforeExecute(wt, task);
				Throwable thrown = null;
				try {
					// 执行Runnable类的run()方法
					task.run();
				} catch (RuntimeException x) {
					thrown = x; throw x;
				} catch (Error x) {
					thrown = x; throw x;
				} catch (Throwable x) {
					thrown = x; throw new Error(x);
				} finally {
					// 执行后(空方法,由子类重写实现)
					afterExecute(task, thrown);
				}
            } finally {
                task = null;
                // 完成的任务数+1
                w.completedTasks++;
                // 释放锁
                w.unlock();
            }
        }
        // 到此,线程是正常退出
        completedAbruptly = false;
    } finally {
        // 处理worker的退出
        processWorkerExit(w,completedAbruptly);
    }
}

Java对象内存布局

重量级锁加锁

 synchronized

加锁:线程在进入 synchronized 代码块前会自动获取内部锁,其它线程访问该代码块时会被阻塞。
解锁:拿到内部锁的线程在正常退出代码块、抛出异常后、在同步代码块内调用了该内置锁资源的 wait 系列方法时,释放该内置锁。
1.作用在方法上:
  修饰非静态方法(普通方法)
  修饰静态方法
2.作用在代码块上(更细,更灵活):
  synchronized(this|object) {...}
  synchronized(类.class) {...}
对象锁
  1、两个线程同时访问同一个对象的同步方法,会互斥;
  2、两个线程同时访问同一个对象的同步方法和非同步方法,不互斥;
  3、两个线程同时访问两个对象的同步方法,不互斥;
  4、两个线程同时访问两个对象的同步方法和非同步方法,不互斥;
类锁
  1、两个线程同时访问同一个类的静态同步方法,会互斥;
  2、两个线程同时访问不同类的静态同步方法,不互斥;
  3、两个线程同时访问同一个类的静态同步方法和静态非同步方法,不互斥;
  4、两个线程同时访问同一个类的静态同步方法和非静态同步方法,不互斥;
偏向锁: 
  通过CAS 替换MarkWord中的偏向锁标记和锁标记,并设置线程 ID;当其他线程尝试竞争偏向锁时,发生锁的升级/膨胀.
轻量级锁: 
    没有抢占到锁的线程,进行一定次数的重试,也叫自旋(while(...)),不阻塞,不要唤醒。
轻量级锁加锁:
    1.在线程进入同步块时,如果同步对象锁状态为无锁状态(偏向锁为0,锁标志位为01),虚拟机先将在当前线程的栈帧中创建一个锁记录(Lock Record)空间,用于存储锁对象目前的 Mark Word 拷贝,
    2.复制对象头中的 Mark Word 到锁记录(Lock Record)中.
    3.复制完后,虚拟机将使用 CAS 操作尝试将对象的 Mark Word 更新为指向LockRecord的指针,并将 Lock Record 里的 owner 指针指向对象头的 mark word.
    4.如果1-3成功了,那么这个线程就拥有了该对象的锁,并将对象MarkWord的锁标记设置为 00,即表示此对象处于轻量级锁定状态.
    5.如果1-3失败了,虚拟机首先会检查对象的Mark Word 是否指向当前线程的栈帧,不是则有多个线程竞争锁。进行自旋,等待占用锁的线程释放锁,通过 CAS 后即可立即获取锁,否则轻量级锁就要升级为重量。
轻量级锁释放:
    轻量级锁释放锁(解锁)时,会使用 CAS 将之前复制在栈桢中的Displaced Mard Word替换回 Mark Word 中。
重量级锁:
    把等待想要获得锁的线程进行阻塞,被阻塞的线程不会消耗 CPU。阻塞或者唤醒一个线程时,要操作系统来调度,用户态转换到内核态比较耗时。
重量级锁底层实现原理:
    synchronized(this){ ...... }膨胀为重量级锁,那么 this 对象会与一个monitor关联,this 对象头里面的 mark word 指向 monitor 的指针。monitor(管程,就是锁)提供了一种排它互斥访问机制,保证在每个时间点上最多只有一个线程会执行同步方法。
重量级锁加锁
  重量级锁是通过对象内部的监视器(monitor)来实现,也就是通过ObjectMonitor 实现,ObjectMonitor.hpp 有几个重要属性:
    _Owner:保存当前持有锁的线程;
    _EntryList:等待获取锁的线程;
    _WaitSet:调用 Object 的 wait()方法等待时,此时将该等待的线程保存到_WaitSet中;
    _cxq:存储没有获取到锁的线程;
    _recursions:记录重入次数;
  当多个线程同时访问某段同步代码时:
    1、首先会进入_EntryList 集合;
    2、当线程获取到对象的 monitor 之后,就会进入_Owner 区域,并把ObjectMonitor对象的_Owner 指向为当前线程,并将_count + 1;
    3、如果调用了释放锁(比如 wait()方法)操作,就会释放当前持有的monitor,即_owner= null,_count - 1,同时这个线程会进入到_WaitSet 列表中等待被唤醒;
    4、如果当前线程执行完毕,则释放 monitor 锁,复位_count 的值(_count-1),不过此时不会进入_WaitSet 列表;
重量级锁释放
    锁释放是同步代码块执行结束后触发,ObjectMonitor::exit;
    1、ObjectMonitor 中持有的锁的_owner 对象置为 null;
    2、从_cxq 队列中唤醒一个被挂起的线程:根据 QMode 模式判断是从_cxq 还是 EntryList 中获取头节点的线程进行唤醒,通过ObjectMonitor::ExitEpilog 方法唤醒该节点封装的线程,唤醒操作最终由unpark完成:_cxq(竞争列表)EntryList(锁候选者列表)
    3、被唤醒的线程重新竞争重量级锁,被唤醒的线程不一定能抢到锁,未抢到锁的线程将继续挂起,synchronized 是非公平锁;
用户态->内核态->用户态
    因为每个进程的栈有两个,用户态栈、内核态栈。从用户态栈进入内核态栈的时,要保存用户态的寄存器,在内核态返回用户态的时候会恢复这些寄存器的内容,相对而言这是一个很大的开销且耗时.

死锁

产生死锁的四个条件
​​​​   1、互斥条件: 当资源被一个线程使用或者占用时,别的线程就不能使用该资源,比如资源X已经被 T1 线程占用,那么 T2 线程就不能再占用 X 资源;(系统条件)
    2、请求和保持条件: 资源请求者在请求别的资源时,同时保持对已有资源的占有,比如T1线程已经取得 X 资源,在等待获取 Y 资源的时候,不释放对X 资源的占用;
    3、不可抢占条件: 获取资源的一方,不能从正在使用资源的一方强行抢占资源,资源只能被使用者主动释放,比如 T1 线程已经取得 X 资源并正在使用,那么T2 线程不能强行抢占X资源;
    4、循环等待条件: 一个线程正在等待另一个线程占用的资源,而对方又在等待自己占用的资源,从而造成环路等待的情况,比如 T1 线程等待 T2 线程占用的资源,T2 线程等待T1线程占用的资源;

解决死锁
    1、互斥条件: 不能破坏;
    2、请求和保持条件: 可破坏,一次性获取所有锁资源,避免等待;
    3、不可抢占条件: 可破坏,占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源,ReentrantLock: lock.unlock();
    4、循环等待条件: 可破坏,按顺序获取锁;

volatile:


只能修饰类变量、实例变量
    Java线程内存模型会确保所有线程看到这个变量的值都是一致的;
    当线程更新数据时(不能保证原子性,不加锁),会把主存数据加到缓存中,更新完将数据从缓存加到主存中。
    单线程读取数据时,会把数据从主存加到缓存中。

程序重排序: Java在编译和运行期对代码的优化,以提高代码执行效率;
重排序只会对不存在数据依赖性的指令重排序,在单线程下无论怎么重排序可以保证最终执行的结果与程序顺序执行的结果一致,在多线程下就可能出现问题;
被 volatile 修饰的共享变量相当于屏障,屏障的作用是不允许指令随意重排;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/453974.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

3 连续模块(二)

3.5 零极点增益模块 在控制系统设计和分析中&#xff0c;常用的函数包括 传递函数&#xff08;tf&#xff09;、零极点&#xff08;zpk&#xff09;和状态空间&#xff08;ss&#xff09;函数 传递函数&#xff08;tf&#xff09;&#xff1a;用于表示线性时不变系统的输入输出…

SQL Compliance Manager Crack

SQL Compliance Manager Crack 新的SQL CM云代理-扩展了当前SQL CM代理的功能&#xff0c;以支持EC2上Microsoft SQL服务器的远程审核。允许用户添加在共享网络位置上活动的SQL Server&#xff0c;以写入/读取数据并支持DBaaS SQL Server实例。云代理包含与当前SQL代理相同的行…

VS code 插件之中英文间自动添加空格

前言 不知道大家在开发过程中是不是会遇到写代码注释或者文本内容时中英文之间没有空格的情况&#xff0c;很多时候在写代码尤其是写注释的时候容易忘记加空格&#xff0c;但回过头来看又难以忍受&#xff0c;于是我就想着自己写一个 vscode 插件来解决这个问题&#xff0c;希…

跟我一起开启 linux 的学习吧

跟我学 CentOS 的安装 一、安装 VMware二、创建虚拟机三、安装 CentOS 7四、linux 的登录 一、安装 VMware VMware 计算机虚拟化软件 从官网 https://www.vmware.com/cn.html 下载并安装 这里就不再展示安装过程啦&#xff01; 有需要的可以 点击这里 →→→ VMware 下载安装过…

postgresql 源码结构分析

专栏内容&#xff1a;postgresql内核源码分析个人主页&#xff1a;我的主页座右铭&#xff1a;天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物&#xff0e; 目录 前言 源码下载 源码结构 核心代码目录 结尾 前言 本文对postgresql源码目…

进销存管理系统和ERP的工作原理和实现方式有何不同?

一、ERP是什么&#xff1f; ERP即企业资源规划&#xff08;Enterprise Resource Planning&#xff09;&#xff0c;是一种集成管理软件系统。它的目的是整合和规划各种企业资源和业务流程&#xff0c;包括财务、物流、采购、生产、销售等&#xff0c;以提高企业的管理效率和业…

Java -- IO流

IO流 主要用于读写数据 IO流按照流的方向可以分为以下两种&#xff1a; 输入流输出流 IO流按照操作文件类型可以分为以下两种&#xff1a; 字节流字符流 字节流可以操作所有类型的文件&#xff0c;而字符流只可以操作纯文本文件 #mermaid-svg-tfFZjSluOmEFUpyc {font-fam…

SVM-老师讲的真的很好!

支持向量机(Support Vector Machine) 如同逻辑回归一样,SVM是一个分类模型 目标 SVM解决分类模型的基本思路:什么是一个好的分类边界? SVM认为,好的分类决策边界应当是:类别边界的距离应当尽可能的远 目标函数 拉格朗日乘子法 那么我们的公式就可以写为

SIP协议之通话转接

一、介绍 在SIP协议应用中&#xff0c;有一个常用的功能叫通话转接&#xff0c;用于将接通后的通话转给第三方接听处理。 二、原理及流程 转接是通过SIP协议的一个扩展请求方法REFER实现的。呼叫转接由RFC5589(Session Initiation Protocol (SIP) Call Control - Transfer)定义…

(数字图像处理MATLAB+Python)第六章图像平滑-第一节:图像平滑概述和空间域平滑滤波

文章目录 一&#xff1a;图像中的噪声&#xff08;1&#xff09;图像噪声分类&#xff08;2&#xff09;图像噪声的数学模型&#xff08;3&#xff09;程序 二&#xff1a;空间域平滑滤波&#xff08;1&#xff09;均值滤波A&#xff1a;均值滤波原理B&#xff1a;示例C&#x…

203、【栈与队列】leetcode ——剑指 Offer II 040. 矩阵中最大的矩形 / 85. 最大矩形:暴力+单调栈(C++/Pyhont版本)

题目描述 Problem: 剑指 Offer II 040. 矩阵中最大的矩形 文章目录 题目描述解法一&#xff1a;暴力解法思路解题方法复杂度Code 解法二&#xff1a;单调栈解法 解法一&#xff1a;暴力解法 思路 首先&#xff0c; 按行获取到达某一元素位置时&#xff0c;之前与当前连续1的个…

【JUC高并发编程】—— 初见JUC

一、JUC 概述 什么是JUC JUC 是 Java并发编程的缩写&#xff0c;指的是 Java.util.concurrent 即Java工具集下的并发编程库 【说白了就是处理线程的工具包】 JUC提供了一套并发编程工具&#xff0c;这些工具是Java 5以后引入的&#xff0c;使得Java开发者可以更加方便地编写…

86页2023年新型智慧城市顶层设计规划解决方案(ppt可编辑)

本资料来源公开网络&#xff0c;仅供个人学习&#xff0c;请勿商用&#xff0c;如有侵权请联系删除 整体架构 智慧城市建设从顶层设计入手&#xff0c;结合软硬件资源整合能力&#xff0c;为公众提供全生命周期一站式服务管理。智慧城市的整体框架分为发展战略层、技术实施层…

HuggingFace入门教程--环境搭建

HuggingFace中文直译为”拥抱脸“&#xff0c;是最近非常火爆的一个人工智能社区&#xff0c;官网地址是&#xff1a;https://huggingface.co/ .关于HuggingFace的相关介绍大家可以自行百度。本文主要为刚入人工智能坑的小白指下路&#xff0c;同时也是逼着自己记录下学习过程中…

Cuckoo Filter

其他判重数据结构 Bloom Filter 无法支持删除和计数的功能&#xff0c;需要更多的存储空间来存储数据 因为在CS中&#xff0c;删除和计数是常见的操作&#xff0c;但是这会对布隆过滤器的存储空间产生影响&#xff0c;同样为了实现这一操作&#xff0c;需要更多的存储空间 数…

vue---父子、兄弟、跨层级关系组件通信

目录 1、props / $emit &#xff08;父子通信&#xff09; 2、$root、 $parent / $children(vue3废弃) 3、ref/$refs 4、EventBus &#xff08;$emit / $on&#xff09; 5、provideinject&#xff08;依赖注入&#xff09; 6、$attrs $listeners&#xff08;透传&#x…

电子政务网络智慧运维方案

随着电子政务建设的深入&#xff0c;我国政府的信息化建设已经具备了一定的规模&#xff0c;形成了部、省、市、县四级体系&#xff1b;大型政务服务事项、公文流转、行政审批、费税征缴、信息公开、网格化社会管理、三网融合、智慧城市建设等大量政府核心业务越来越依赖信息化…

Linux 静态库的制作与使用

目录 静态库1、 什么是库2、 静态库的制作2.1 命名规则与制作规则 3、 静态库的使用 静态库 1、 什么是库 库文件是计算机上的一类文件&#xff0c;可以简单的把库文件看成一种代码仓库&#xff0c;它提供使用者可以直接拿来用的变量、函数或类。库是一种特殊的程序&#xff…

atbetaflight——指定commit号编译固件

一、说明 在开发过程中&#xff0c;比如成员A上传了一次code,而成员B需要测试本次提交的code&#xff0c;但是由于没有搭建ci,成员B就需要自己拉code编译&#xff0c;本文将详细说明编译步骤&#xff1b; 二、步骤 1、使用vscode打开code 在使用git clone下载到code后的文件…

HDCTF2023复盘

文章目录 前言 CryptoNormal_rsaNormal_rsa(Revenge)(低指数e攻击)Math_RSA(二次剩余)爬过小山的看云(hill,云影) MischardMisc(base64)MasterMisc(crc,wav)ExtremeMisc(zip爆破,明文攻击) Reverseeasy_re(Upx,base64)easyasm(xor) WebWelcome To HDCTF 2023(JSFuck)SearchMast…