JVM

JavaComplier : 就是我们常见的javac指令, 将Java代码转化为字节码文件.
JVM: java虚拟机, 负责 将字节码文件解释为本机机器语言运行
JIT: JVM内部的实时编译器(动态编译器), 在字节码转换为机器指令时工作, 主要目的为性能优化, 对JVM发现的某个频繁的代码块或者方法认定为“Hot Spot”热点代码 https://www.cnblogs.com/insistence/p/5901457.html

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VHGE9SIR-1636525498139)(image-20211025101730662.png)]

  • 当程序需要首次启动和执行的时候,解释器可以首先发挥作用,一行一行直接转译运行,但效率低下。 当多次调用方法或循环体时JIT编译器可以发挥作用,把越来越多的代码编译成本地机器码,之后可以获得更高的效率(占内存)

  • HotSpot虚拟机中内置了两个即时编译器,分别称为Client ComplierServer Complier,它会根据自身版本与宿主机器的硬件性能自动选择运行模式,用户也可以使用"-client"或"-server"参数去强制指定虚拟机运行在Client模式或Server模式

    • Client Complier这种compiler是主要跑在客户端本地的。特点是使用资源少启动快速。

    • Server Complier跑在服务器上,因为服务器上程序本身是长时间运行的,而且对启动时间没有严格的要求。那么就可以牺牲启动时间获得深度的优化。

    • 热点代码的判断

      • 方法计数器

      • 默认阀值,在Client模式下是1500次,Server是10000次,可以通过参数“-XX:CompileThreshold”来设定

      • 当一个方法被调用时会首先检查是否存在被JIT编译过得版本,如果存在则使用此本地代码来执行;如果不存在,则将方法计数器+1,然后判断“方法计数器和回边计数器之和”是否超过阀值,如果是则会向编译器提交一个方法编译请求

      • 默认情况下,执行引擎并不会同步等待上面的编译完成,而是会继续解释执行。当编译完成后,此方法的调用入口地址会被系统自动改写为新的本地代码地址

      • 热度是会衰减的,也就是说不是仅仅+,也会-,热度衰减动作是在虚拟机的GC执行时顺便进行的

      • 回边计数器

        • 回边,顾名思义,只有执行到大括号”}”时才算+1
        • 默认阀值,Client下13995,Server下10700
        • 它的调用逻辑和方法计数器差不多,只不过遇到回边指令时+1、超过阀值时会提交OSR编译请求以及这里没有热度衰减
  • 解释器与编译器搭配使用的方式:
    1、默认(java -version混合模式)
    2、解释模式(java -Xint -version)仅使用解释器方式执行
    3、编译模式(java -Xcomp -version)优先采用编译方式执行程序,但解释器要在编译无法进行的情况下介入执行过程

==和equals

==是比较的内存地址是否相等 就是是否是同一个对象

如果类没有覆盖equals方法 equals 使用跟==一样都是对地址的比较

但是如果类覆盖了equals方法就是比较对象的值是否一样

序列化

是拿来保存在内存中的各种对象的状态,并且可以把保存的对象状态再读出来。

什么情况下使用

想把内存中的对象保存到一个文件或者数据库中的时候

想用套接字再网络上传送对象的时候

对象序列化 发生了什么

没有序列化之前,每个保存在堆中的对象都有相应的状态,即实例变量

当对象被序列化后,对象的数据被保存到foo.ser文件中, 这样可以直接在文件中读取数据,重新在堆中船舰与拿来的对象

a)当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;
b)当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化;
c)并非所有的对象都可以序列化,,至于为什么不可以,有很多原因了

hashCode()与equals()

hashCode()定义在 JDK 的 Object 类中 Java 中的任何类都包含有 hashCode() 函数 Object 的 hashcode 方法是本地方法,也就是用 c 语言或 c++ 实现的,

hashcode是通过对象的地址 进行hash计算 作用是确定对象再哈希表中的索引位置

hashset就是通过hashcode判断对象的hashcode是否一样,一样的话再继续通过equals判断两个对象是否真的相同

如果重写equals()但是不重写hashCode()就会出现两个相同值的对象 但是hashcode不一样 导致 HashSet会把两个值相同的类放到集合里面去

所以重写equals的话hashcode也需要重写

Object的equals方法容易抛空指针异常:不能用null的引用类型变量来调用非静态方法,但是可以用一个非空的变量和null比较 false

java.util.Objects#equals

这个包下的用null变量比较不会报异常

源码:

public static boolean equals(Object a, Object b) {// 可以避免空指针异常。如果a=null的话此时a.equals(b)就不会得到执行,避免出现空指针异常。return (a == b) || (a != null && a.equals(b));
}
  • 每种原始类型都有默认值一样,如int默认值为 0,boolean 的默认值为 false,null 是任何引用类型的默认值,不严格的说是所有 Object 类型的默认值。
  • 可以使用 == 或者 != 操作来比较null值,但是不能使用其他算法或者逻辑操作。在Java中null == null将返回true。
  • 不能使用一个值为null的引用类型变量来调用非静态方法,否则会抛出异常

静态方法和非静态方法

  • 静态方法是同类一起加载,不管是静态方法还是静态成员,都是类级别存在的,也就是说随着类的加载而加载,优先于对象的存在

    • 在外部调用静态方法时,可以使用”类名.方法名”的方式,也可以使用”对象名.方法名”的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。
    • 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制
  • 非静态成员方法对象级别的存在所以在静态方法中调用非静态的成员或方法(此时还不存在对象),是不可能的,但是反过来是可以的:非静态中调用静态。于是也就有静态方法中不能使用this和super关键字

java中的值传递

  • 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)。
  • 一个方法可以改变一个对象参数的状态。
  • 一个方法不能让对象参数引用一个新的对象。

重写

  1. 返回值类型、方法名、参数列表必须相同,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类。
  2. 如果返回值的类型有继承关系才考虑大小,子类返回值必须小于等于父类返回值。
  3. 如果父类方法访问修饰符为 private/final/static 则子类就不能重写该方法,但是被 static 修饰的方法能够被再次声明。
  4. 构造方法无法被重写

浅拷贝和深拷贝

基本数据类型传值,引用数据类型浅拷贝只是引用传递,只是拷贝内存地址,所以对拷贝的引用更改会影响原来的对象

深拷贝的就是申请新的内存地址进行拷贝一个新的对象

String、StringBuilder、StringBuffer

String 是不可变的 因为是用final关键字修饰字符数组来保存的字符串

private final char value[]

java 9过后变成了

private final byte[] value

StringBuilderStringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串char[]value 但是没有用 final 关键字修饰,所以这两种对象都是可变的。

线程安全性

String 中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilderStringBuilderStringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacityappendinsertindexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。

键盘输入

2种方式

Scanner sc = new Scanner(System.in);
String s = sc.nextLine();
BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
String s = input.readLine();

泛型*

Enum枚举

常量实例,对象就是常量,可以有构造方法,成员方法(必须用private),属性

枚举对象之间的比较使用== 因为枚举的引用变量都是常量,在内存中只有一份,只用比较值

final

*final关键字可以用来修饰类、方法、变量。各有不同。*

**A、修饰类(class)。

** 1、该类不能被继承。
2、类中的方法不会被覆盖,因此默认都是final的。
3、**用途:**设计类时,如果该类不需要有子类,不必要被扩展,类的实现细节不允许被改变,那么就设计成final类

B、修饰方法(method)
1、该方法可以被继承,但是不能被覆盖。
2、**用途:**一个类不允许子类覆盖该方法,则用final来修饰
3、**好处:**可以防止继承它的子类修改该方法的意义和实现;更为高效,编译器在遇到调用fianal方法转入内嵌机制,提高了执行效率。
4、**注意:**父类中的private成员方法不能被子类覆盖,因此,private方法默认是final型的(可以查看编译后的class文件)

**C、修饰变量(variable)

** 1、用final修饰后变为常量。包括静态变量、实例变量和局部变量这三种。
2、**特点:**可以先声明,不给初值,这种叫做final空白。但是使用前必须被初始化。一旦被赋值,将不能再被改变。

**D、修饰参数(arguments)

** 1、用final修饰参数时,可以读取该参数,但是不能对其作出修改

final关键字不能用来抽象类和接口。

static

static 关键字主要有以下四种使用场景:

  1. 修饰成员变量和成员方法: 被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。被 static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存区域的方法区。调用格式:类名.静态变量名 类名.静态方法名()
  2. 静态代码块: 静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—>非静态代码块—>构造方法)。 该类不管创建多少对象,静态代码块只执行一次.
  3. 静态内部类(static 修饰类的话只能修饰内部类): 静态内部类与非静态内部类之间存在一个最大的区别: 非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:1. 它的创建是不需要依赖外围类的创建。2. 它不能使用任何外围类的非 static 成员变量和方法。
  4. 静态导包(用来导入类中的静态资源,1.5 之后的新特性): 格式为:import static 这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法。

this、super

使用 this 和 super 要注意的问题:

  • 在构造器中使用 super() 调用父类中的其他构造方法时,该语句必须处于构造器的首行,否则编译器会报错。另外,this 调用本类中的其他构造方法时,也要放在首行。
  • this、super 不能用在 static 方法中。
    • 被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享。而 this 代表对本类对象的引用,指向本类对象;而 super 代表对父类对象的引用,指向父类对象;所以, this 和 super 是属于对象范畴的东西,而静态方法是属于类范畴的东西

成员变量、局部变量

成员变量可以不初始化会有默认赋值、局部变量必须要初始话

反射**

代理****

静态代理:手动实现对目标对象的方法进行代理

  1. 定义一个接口及其实现类;
  2. 创建一个代理类同样实现这个接口
  3. 将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法。这样的话,我们就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情。

动态代理:

  • JDK 动态代理

  • InvocationHandler 接口和 Proxy 类是核心。

    • public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException{......}
      
    • loader :类加载器,用于加载代理对象。

    • interfaces : 被代理类实现的一些接口;

    • h : 实现了 InvocationHandler 接口的对象;

  • public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {//调用方法之前,我们可以添加自己的操作System.out.println("before method " + method.getName());Object result = method.invoke(target, args);//调用方法之后,我们同样可以添加自己的操作System.out.println("after method " + method.getName());return result;}
    
  • CGLIB动态代理

集合

Collection

Map

https://gitee.com/SnailClimb/JavaGuide/blob/master/docs/java/collection/Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98.md#https://www.imooc.com/article/22931

队列***

PriorityQueue

  • PriorityQueue 利用了二叉堆的数据结构来实现的,底层使用可变长的数组来存储数据
  • PriorityQueue 通过堆元素的上浮和下沉,实现了在 O(logn) 的时间复杂度内插入元素和删除堆顶元素。
  • PriorityQueue 是非线程安全的,且不支持存储 NULLnon-comparable 的对象。
  • PriorityQueue 默认是小顶堆,但可以接收一个 Comparator 作为构造参数,从而来自定义元素优先级的先后。

并发编程的艺术

1、并发挑战

1.1上下文切换

单核处理器也支持多线程,cpu通过给每个线程分配cpu时间片实现,时间片非常短,让我们感觉多个线程是同时执行的

当前任务执行一个时间片后会切换到下一个任务,但是再切换前会保存上一个任务的状态,以便下次切换会这个任务是,可以再加载这个任务的状态,这个任务从保存到再加载的过程就是一次上下文切换

1.1.1 多线程一定快吗

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7wkb7l69-1636525498142)(image-20211101101258492.png)]

因为线程有创建和上下文切换的开销

1.1.3 如何减少上下文切换

减少上下文切换的方法有无锁并发编程、CAS算法、使用最少线程和使用协程。

·无锁并发编程。多线程竞争锁时,会引起上下文切换,所以多线程处理数据时,可以用一 些办法来避免使用锁,如将数据的ID按照Hash算法取模分段,不同的线程处理不同段的数据。

·CAS算法。Java的Atomic包使用CAS算法来更新数据,而不需要加锁。

·使用最少线程。避免创建不需要的线程,比如任务很少,但是创建了很多线程来处理,这 样会造成大量线程都处于等待状态。

·协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。

1.2死锁

现在我们介绍避免死锁的几个常见方法。

·避免一个线程同时获取多个锁。

·避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。

·尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。

·对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。

2、Java并发机制的底层实现原理

2.1 volatile的应用

在多线程并发编程中synchronized和volatile都扮演着重要的角色,volatile是轻量级的 synchronized,它在多处理器开发中保证了共享变量的“可见性”。

1.volatile的定义与实现原理

下面来具体讲解volatile的两条实现原则(硬件层面)

1)Lock前缀指令会引起处理器缓存回写到内存。这个是在汇编阶段汇编代码的前缀是lock(通过jvm调用cpu指令完成)

2)一个处理器的缓存回写到内存会导致其他处理器的缓存无效(缓存一致性)

有序性

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6wJb8xZm-1636525498147)(image-20211101154740299.png)]

·在每个volatile写操作的前面插入一个StoreStore屏障。

·在每个volatile写操作的后面插入一个StoreLoad屏障。

·在每个volatile读操作的后面插入一个LoadLoad屏障。

·在每个volatile读操作的后面插入一个LoadStore屏障。

2.2 synchronized的实现原理与应用

在多线程并发编程中synchronized一直是元老级角色,很多人都会称呼它为重量级锁。但 是,随着Java SE 1.6对synchronized进行了各种优化之后,有些情况下它就并不那么重了。本文 详细介绍Java SE 1.6中为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级 锁

JVM对于同步方法和同步代码块的处理方式不同,对于同步方法,JVM采用ACC_SYNCHRONIZED标记符来实现同步,而对于同步代码块,JVM则采用 monitorentermonitorexit 这两个指令实现同步。

方法级的同步是隐式的。同步方法的常量池中会有一个ACC_SYNCHRONIZED标志。当某个线程要访问某个方法的时候,会检查是否有ACC_SYNCHRONIZED标志,如果有设置,则需要先获得监视器锁,然后开始执行方法,方法执行之后再释放监视器锁。这时如果其他线程来请求执行方法,会因为无法获得监视器锁而被阻断住。值得注意的是,如果在方法执行过程中,发生了异常,并且方法内部并没有处理该异常,那么在异常被抛到方法外面之前监视器锁会被自动释放。

同步代码块

可以把执行monitorenter指令理解为加锁,执行monitorexit理解为释放锁。 每个对象维护着一个记录着被锁次数的计数器。未被锁定的对象的该计数器为0,当一个线程获得锁(执行monitorenter)后,该计数器自增变为 1 ,当同一个线程再次获得该对象的锁的时候,计数器再次自增。当同一个线程释放锁(执行monitorexit指令)的时候,计数器再自减。当计数器为0的时候。锁将被释放,其他线程便可以获得锁。

  • 原子性: 即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行
    Java内存模型提供了字节码指令monitorentermonitorexit来隐式的使用这两个操作,在synchronized块之间的操作是具备原子性的。

  • 有序性: 程序执行的顺序按照代码的先后顺序执行。
    在并发时,程序的执行可能会出现乱序。给人的直观感觉就是:写在前面的代码,会在后面执行。但是synchronized提供了有序性保证,这其实和as-if-serial语义有关。
    as-if-serial语义是指不管怎么重排序(编译器和处理器为了提高并行度),单线程程序的执行结果都不能被改变。编译器和处理器无论如何优化,都必须遵守as-if-serial语义。只要编译器和处理器都遵守了这个语义,那么就可以认为单线程程序是按照顺序执行的,由于synchronized修饰的代码,同一时间只能被同一线程访问。那么可以认为是单线程执行的。所以可以保证其有序性。
    但是需要注意的是synchronized虽然能够保证有序性,但是无法禁止指令重排和处理器优化的

  • 可见性: 当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
    被synchronized修饰的代码,在开始执行时会加锁,执行完成后会进行解锁,但在一个变量解锁之前,必须先把此变量同步回主存中,这样解锁后,后续其它线程就可以访问到被修改后的值,从而保证可见性。

先来看下利用synchronized实现同步的基础:Java中的每一个对象都可以作为锁。

具体表现 为以下3种形式。

·对于普通同步方法,锁是当前实例对象。

·对于静态同步方法,锁是当前类的Class对象。

·对于同步方法块,锁是Synchonized括号里配置的对象。

as-if-serial

不管怎么重排序(编译器和处理器为了提高并行度),(单线程) 程序的执行结果不能被改变。编译器、runtime和处理器都必须遵守as-if-serial语义。

为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操作做重排序,因 为这种重排序会改变执行结果。但是,如果操作之间不存在数据依赖关系,这些操作就可能被 编译器和处理器重排序。

happens-before

1)如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作 可见,而且第一个操作的执行顺序排在第二个操作之前。

2)两个操作之间存在happens-before关系,并不意味着Java平台的具体实现必须要按照 happens-before关系指定的顺序来执行。如果重排序之后的执行结果,与按happens-before关系 来执行的结果一致,那么这种重排序并不非法(也就是说,JMM允许这种重排序)。

as-if-serial语义和happens-before这么做的目的,都是为了在不改变程序执行结果的前提 下,尽可能地提高程序执行的并行度。

规则

1)程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。

2)监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。

3)volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。

4)传递性:如果A happens-before B,且B happens-before C,那么A happens-before C。

5)start()规则:如果线程A执行操作ThreadB.start()(启动线程B),那么A线程的 ThreadB.start()操作happens-before于线程B中的任意操作。

6)join()规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作 happens-before于线程A从ThreadB.join()操作成功返回。

假设线程A执行writer()方法,随后线程B执行reader()方法。

根据happens-before规则,这个 过程包含的happens-before关系可以分为3类。

1)根据程序次序规则,1 happens-before 2,2 happens-before 3;4 happens-before 5,5 happensbefore 6。

2)根据监视器锁规则,3 happens-before 4。

3)根据happens-before的传递性,2 happens-before 5。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1wQ8ee4z-1636525498151)(image-20211101155638556.png)]

在图3-24中,每一个箭头链接的两个节点,代表了一个happens-before关系。黑色箭头表示 程序顺序规则;橙色箭头表示监视器锁规则;蓝色箭头表示组合这些规则后提供的happensbefore保证。

2.2.1 Java对象头

synchronized用的锁是存在Java对象头里的。

如果对象是数组类型,则虚拟机用3个字宽 (Word)存储对象头,如果对象是非数组类型,则用2字宽存储对象头。

在32位虚拟机中,1字宽 等于4字节,即32bit

数组对象:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aSnMctUE-1636525498160)(image-20211101134704415.png)]

Java对象头里的Mark Word里默认存储对象的HashCode、分代年龄和锁标记位。

32位

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fNI0Mhr9-1636525498162)(image-20211101135043553.png)]

64位

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6nrZAlC7-1636525498167)(image-20211101135235924.png)]

CAS Compare and Swap 乐观锁

就是比较是否符合请求条件,比如2个线程同时要去操作一个对象,线程要去比较对象标识是0的时候才能进行操作,a线程得到对象过后表示变成1,b线程就只能在原地等待对象的标识转变为0,但是这也会出现两个线程同时得到0的标识然后同时把0改为了1,所以也必须要保证表示改变的原子性

这就用到了cpu底层 对原子性的支持,cpu在硬件级别上加锁了,不用通过操作系统去操作cpu不用再用户态和内核态之间的转换,上层直接调用

1)ABA问题。因为CAS需要在操作值的时候,检查值有没有发生变化,如果没有发生变化 则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它 的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面 追加上版本号,每次变量更新的时候把版本号加1,那么A→B→A就会变成1A→2B→3A。

Java实现cas AtomicInteger类

调用native方法,对汇编命令的调用 cmpxchg cpu指令

2.2.2 锁的升级与对比

Java SE 1.6为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”,在 Java SE 1.6中,锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状 态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏 向锁升级成轻量级锁后不能降级成偏向锁。

1.偏向锁

当一个线程访问同步块并 获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出 同步块时不需要进行CAS操作来加锁和解锁,只需简单地测试一下对象头的Mark Word里是否 存储着指向当前线程的偏向锁。

(1)偏向锁的撤销

偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时, 持有偏向锁的线程才会释放锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有正 在执行的字节码)。

2.轻量级锁

(1)轻量级锁加锁 线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并 将对象头中的Mark Word复制到锁记录中,官方称为Displaced Mark Word。然后线程尝试使用 CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失 败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。

(2)轻量级锁解锁 轻量级解锁时,会使用原子的CAS操作将Displaced Mark Word替换回到对象头,如果成 功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。图2-2是 两个线程同时争夺锁,导致锁膨胀的流程图。

3、JMM

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KTIPBhBs-1636525498173)(image-20211101150240978.png)]

3.5.4concurrent包的实现

Java的CAS会使用现代处理器上提供的高效机器级别的原子指令,这些原子指令以原子 方式对内存执行读-改-写操作,这是在多处理器中实现同步的关键(从本质上来说,能够支持 原子性读-改-写指令的计算机,是顺序计算图灵机的异步等价机器,因此任何现代的多处理器 都会去支持某种能对内存执行原子性读-改-写操作的原子指令)。同时,volatile变量的读/写和 CAS可以实现线程之间的通信。把这些特性整合在一起,就形成了整个concurrent包得以实现 的基石。如果我们仔细分析concurrent包的源代码实现,会发现一个通用化的实现模式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jQiXq1UL-1636525498175)(image-20211101160742516.png)]

4.1线程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vVYok1AT-1636525498178)(image-20211101164113135.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Fk8lu398-1636525498183)(image-20211101164146362.png)]

由图中可以看到,线程创建之后,调用start()方法开始运行。当线程执行wait()方法之 后,线程进入等待状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状 态,而超时等待状态相当于在等待状态的基础上增加了超时限制,也就是超时时间到达时将 会返回到运行状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到阻塞 状态。线程在执行Runnable的run()方法之后将会进入到终止状态。

Daemon线程

Daemon线程是一种支持型线程,因为它主要被用作程序中后台调度以及支持性工作。这 意味着,当一个Java虚拟机中不存在非Daemon线程的时候,Java虚拟机将会退出。可以通过调 用Thread.setDaemon(true)将线程设置为Daemon线程

注意 Daemon属性需要在启动线程之前设置,不能在启动线程之后设置。

Thread源码

在运行线程之前首先要构造一个线程对象,线程对象在构造的时候需要提供线程所需要 的属性,如线程所属的线程组、线程优先级、是否是Daemon线程等信息。

初始化

private void init(ThreadGroup g, Runnable target, String name,long stackSize,
AccessControlContext acc) {if (name == null) {throw new NullPointerException("name cannot be null");
}
// 当前线程就是该线程的父线程
Thread parent = currentThread();
this.group = g;
// 将daemon、priority属性设置为父线程的对应属性
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
this.name = name.toCharArray();
this.target = target;
setPriority(priority);
// 将父线程的InheritableThreadLocal复制过来
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals=ThreadLocal.createInheritedMap(parent.
inheritableThreadLocals);
// 分配一个线程ID
tid = nextThreadID();
}

在上述过程中,一个新构造的线程对象是由其parent线程来进行空间分配的,而child线程 继承了parent是否为Daemon、优先级和加载资源的contextClassLoader以及可继承的 ThreadLocal,同时还会分配一个唯一的ID来标识这个child线程。至此,一个能够运行的线程对 象就初始化好了,在堆内存中等待着运行。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7Z1Eh7D3-1636525498186)(image-20211101173734149.png)]

4.4 线程应用实例

4.4.1 等待超时模式

即使方法执行时间过长,也不会“永久”阻塞调用者,而是会按 照调用者的要求“按时”返回。

4.4.2 一个简单的数据库连接池示例

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P2x9Sn9m-1636525498189)(image-20211102140555707.png)]

从表中的数据统计可以看出,在资源一定的情况下(连接池中的10个连接),随着客户端 线程的逐步增加,客户端出现超时无法获取连接的比率不断升高。虽然客户端线程在这种超 时获取的模式下会出现连接无法获取的情况,但是它能够保证客户端线程不会一直挂在连接 获取的操作上,而是“按时”返回,并告知客户端连接获取出现问题,是系统的一种自我保护机 制。数据库连接池的设计也可以复用到其他的资源获取的场景,针对昂贵资源(比如数据库连 接)的获取都应该加以超时限制。

5、 Java中的锁

5.1 Lock接口

能显式的进行上锁和释放锁,比synchronized可操作性更高

线程池

ThreadPoolExecutor类

ThreadPoolExecutor类中提供的四个构造方法。我们来看最长的那个,其余三个都是在这个构造方法的基础上产生(其他几个构造方法说白点都是给定某些默认参数的构造方法比如默认制定拒绝策略是什么)。

public ThreadPoolExecutor(int corePoolSize,//线程池的核心线程数量int maximumPoolSize,//线程池的最大线程数long keepAliveTime,//当线程数大于核心线程数时,多余的空闲线程存活的最长时间TimeUnit unit,//时间单位BlockingQueue<Runnable> workQueue,//任务队列,用来储存等待执行任务的队列ThreadFactory threadFactory,//线程工厂,用来创建线程,一般默认即可RejectedExecutionHandler handler//拒绝策略,当提交的任务过多而不能及时处理时,我们可以定制策略来处理任务)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wRRsh4fj-1636525498191)(image-20211029143813557-16357323742512-16357323966593.png)]

核心进程数:只有工作队列满了的情况下才会创建超出这个数量的线程

最大线程数:线程池中的当前线程数目不会超过该值

FixedThreadPool

被称为可重用固定线程数的线程池。通过 Executors 类中的相关源代码来看一下相关实现

  public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(),threadFactory);}

不推荐使用

FixedThreadPool 使用无界队列 LinkedBlockingQueue(队列的容量为 Integer.MAX_VALUE)作为线程池的工作队列会对线程池带来如下影响 :

  1. 当线程池中的线程数达到 corePoolSize 后,新任务将在无界队列中等待,因此线程池中的线程数不会超过 corePoolSize;
  2. 由于使用无界队列时 maximumPoolSize 将是一个无效参数,因为不可能存在任务队列满的情况。所以,通过创建 FixedThreadPool的源码可以看出创建的 FixedThreadPoolcorePoolSizemaximumPoolSize 被设置为同一个值。
  3. 由于 1 和 2,使用无界队列时 keepAliveTime 将是一个无效参数;
  4. 运行中的 FixedThreadPool(未执行 shutdown()shutdownNow())不会拒绝任务,在任务比较多的时候会导致 OOM(内存溢出)。
SingleThreadExecutor

SingleThreadExecutor 是只有一个线程的线程池。下面看看SingleThreadExecutor 的实现:

 public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(),threadFactory));}

不推荐使用

原因:

SingleThreadExecutor 使用无界队列 LinkedBlockingQueue 作为线程池的工作队列(队列的容量为 Intger.MAX_VALUE)。SingleThreadExecutor 使用无界队列作为线程池的工作队列会对线程池带来的影响与 FixedThreadPool 相同。说简单点就是可能会导致 OOM,

CachedThreadPool
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>(),threadFactory);}
ScheduledThreadPoolExecutor

2021-11-10相关推荐

  1. 【不忘初心】Windows11 22000.318 X64 四合一[纯净精简版][2.62G](2021.11.10)

    此版可正常更新补丁,WIN11全新的UI界面出炉!可以说这一次Windows 11全新升级,无论是从Logo上还是UI界面设计,都有很大的变化,母版来自UUP WIN11_22000.318,为了保证 ...

  2. 2021.11.10 - 145.提莫攻击

    文章目录 1. 题目 2. 思路 (1) 模拟法 3. 代码 1. 题目 2. 思路 (1) 模拟法 若本次攻击时间大于上一次中毒的结束时间,则直接记录本次中毒的持续时间:否则,记录两次攻击时间的增量 ...

  3. 2021/11/10

    docker-compose构建mysql services:mysql:image: mysql:8.0.27container_name: mysqlcommand:# MySQL8的密码验证方式 ...

  4. python每日一练(2021/11/10)字符串类型的cookie转化为字典类型

    将一段字符串类型的cookie转化为字典类型 思路:将cookie用:分割,然后遍历它用'='再次分割存入数组.最后将下标为0的作为键,下标为1的作为值 知识点: 1.字典推导式格式:{键:值 for ...

  5. js节点和元素区别【2021.11.10】

    元素:指的是html文档里的各种标签,如<div>.<p>.<span>,所以元素是一个统称. 节点:节点分为元素节点.文本节点.属性节点,节点是唯一的,是为了对h ...

  6. 对清华学子独白的评论及后续收藏,以自勉 自省 —— 2021.11.10 晚9点

    收藏这个版本的回复,更方便以后自己阅读: 看了一遍他的树洞独白,只能用感动 敬佩来形容,正如评论所说:当代版的送东阳马生序,求学之路如此艰辛,却依然心怀感恩,这样的人无论是在什么地方,都是能照亮周围人 ...

  7. 2021/11/07-2021/11/11

    # -*- coding = utf-8 -*- # @Time:2021/11/7 16:09 # @Author:zhangchuhan # @File:demo01.py # @Software ...

  8. 计算机视觉最新进展概览2021年10月31日到2021年11月6日

    参考计算机视觉最新进展概览2021年10月31日到2021年11月6日 - 云+社区 - 腾讯云 1.Learning Distilled Collaboration Graph for Multi- ...

  9. 2021年10月11月总结12月计划

    2021年10月11月总结12月计划 综合评价: 面子并不值钱.不要动不动就觉得伤自尊了.不要让面子问题成为自己的负资产.什么事情理性一些,做自己觉得最正确的事情,不要为了所谓的面子,而让自己吃亏,反 ...

  10. 2021年10月、11月总结

    心里还是有点空唠唠的,每周一篇的文章中断了两个月,吉他也没学多少新的,10月总结也没及时写. 还债,10 11月总结一起写了,重新做人. 拒绝完美主义,大多数人做事失败5次才能成功. 2021.10月 ...

最新文章

  1. 强化学习:如何处理大规模离散动作空间
  2. Quake3服务器客户端架构参考
  3. 4.2 深层网络中的前向传播-深度学习-Stanford吴恩达教授
  4. torch expand
  5. redis创建集群报错can‘t connect to node 192.168.163.203
  6. ios php ide,最好的PHP IDE for Mac? (最好免费!)
  7. 前端学习(1776):前端调试之indexDB原理和查看
  8. AVS游程解码、反扫描、反量化和反变换优化设计
  9. 几何深度深度学习的学习之路
  10. STM32L562开发板开箱记 STM32L562E-DK Discovery kit电路功能分析
  11. C#开发WPF/Silverlight动画及游戏系列教程(Game Tutorial):(四十九) 落雷!治疗!陷阱!连锁闪电!多段群伤!魔法之终极五重奏②...
  12. my97Date如何多选日期且无重复日期
  13. coderforces Gym 100803A/Aizu 1345/CSU 1536/UVALive 6832 Bit String Reordering(贪心证明缺)
  14. gsp计算机管理权限,新gsp计算机权限设置
  15. IDEA 2022 CPU占用100%问题解决
  16. C++通信录管理系统
  17. 团队作业收官——项目产品宣传文案和推广方案
  18. 案例-背景图片的使用(background)
  19. 数据传输完整性_基于IBIS模型的FPGA信号完整性仿真验证方法
  20. 深入浅出filament Android编译脚本

热门文章

  1. Hibernate5的学习笔记(二)
  2. 7-12 两个数的简单计算器
  3. java-net-php-python-jspm人力外包服务公司招聘管理系统计算机毕业设计程序
  4. 罗升阳:那两年炼就的Android内功修养
  5. git 本地分支和远端分支有什么区别?
  6. su vs sudo的区别
  7. 黑苹果必备:Intel核显platform ID整理
  8. CDO安装指南(centos7)
  9. Preftest测试
  10. 探索AI助手ChatGPT实际应用场景