29 7 2020 ### 文本的知识点: Integer常量池 jdk1.6以后对Synchronize锁优化 Java内存模型 ## 一、神奇的Integer ``` @Test public void testDemo() { Integer a = 1; Integer b = 2; Integer c = 3; Integer d = 3; Integer e = 321; Integer f = 321; Long g = 3L; Long h = 2L; System.out.println(c == d); System.out.println(e == f); System.out.println(c == (a + b)); System.out.println(c.equals(a + b)); System.out.println(g == (a + b)); System.out.println(g.equals(a + b)); System.out.println(g.equals(a + h)); } ``` #### 1.1 解题思路 在解这道题之前,相信很多人都已经知道了,在Java中会有一个Integer缓存池,缓存的大小是:**-128~127** 答案是: 1. true 2. false 3. true 4. true 5. true 6. false 7. true #### 简单解释一下: ** 使用==的情况:** 如果比较Integer变量,默认比较的是地址值。 Java的Integer维护了从`-128~127`的缓存池 如果比较的某一边有操作表达式(例如a+b),那么比较的是具体数值 ** 使用equals()的情况:** 无论是Integer还是Long中的`equals()`默认比较的是数值。 Long的`equals()`方法,JDK的默认实现:会判断是否是Long类型 **注意自动拆箱,自动装箱问题。** ``` // 缓存池 System.out.println(c == d); // 超出缓存池范围 System.out.println(e == f); // 存在a+b数值表达式,比较的是数值 System.out.println(c == (a + b)); // equals比较的是数值 System.out.println(c.equals(a + b)); // 存在a+b数值表达式,比较的是数值 System.out.println(g == (a + b)); // Long的equals()先判断传递进来的是不是Long类型,而a+b自动装箱的是Integer类型 System.out.println(g.equals(a + b)); // Long的equals()先判断传递进来的是不是Long类型,而a+b自动装箱的是Long类型 System.out.println(g.equals(a + h)); ``` ## 二、Synchronize锁优化手段有哪些 #### 2.1适应自旋锁 ###### 锁竞争是kernal mode下的,会经过user mode(用户态)到kernal mode(内核态) 的切换,是比较花时间的。 自旋锁出现的原因是人们发现大多数时候锁的占用只会持续很短的时间,甚至低于切换到kernal mode所花的时间,所以在进入kernal mode前让线程等待有限的时间,如果在此时间内能够获取到锁就避免了很多无谓的时间,若不能则再进入kernal mode竞争锁。 在JDK 1.6中引入了自适应的自旋锁,说明自旋的时间不固定,要不要自旋变得越来越聪明。 自旋锁在JDK1.4.2中就已经引入,只不过默认是关闭的,可以使用-XX:+UseSpinning参数来开启,在JDK1.6中就已经改为默认开启了。 #### 2.2锁消除 ** 如果JVM明显检测到某段代码是线程安全的(言外之意:无锁也是安全的),JVM会安全地原有的锁消除掉!** 比如说: ``` public void vectorTest(){ Vector vector = new Vector(); for(int i = 0 ; i < 10 ; i++){ vector.add(i + ""); } System.out.println(vector); } ``` ** Vector是默认加锁的,但JVM如果发现vector变量仅仅在vectorTest()方法中使用,那该vector是线程安全的。JVM会把vector内部加的锁去除,这个优化就叫做:锁消除。** #### 2.3锁粗化 ** 默认情况下,总是推荐将同步块的作用范围限制得尽量小。** 但是如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作是出现在循环体中的,频繁地进行互斥同步操作也会导致不必要的性能损耗。 **JVM会将加锁的范围扩展(粗化),这就叫做锁粗化。** #### 2.4轻量级锁 轻量级锁能提升程序同步性能的依据是“对于绝大部分的锁,在整个同步周期内都是不存在竞争的”,这是一个经验数据。 如果没有竞争,轻量级锁使用CAS操作避免了使用互斥量的开销 但如果存在锁竞争,除了互斥量的开销外,还额外发生了CAS操作,因此在有竞争的情况下,轻量级锁会比传统的重量级锁更慢。 ###### 简单来说:如果发现同步周期内都是不存在竞争,JVM会使用CAS操作来替代操作系统互斥量。这个优化就被叫做轻量级锁。 #### 2.5偏向锁 偏向锁就是在无竞争的情况下把整个同步都消除掉,连CAS操作都不做了! 偏向锁可以提高带有同步但无竞争的程序性能。它同样是一个带有效益权衡(Trade Off)性质的优化,也就是说,它并不一定总是对程序运行有利,如果程序中大多数的锁总是被多个不同的线程访问,那偏向模式就是多余的。在具体问题具体分析的前提下,有时候使用参数`-XX:-UseBiasedLocking`来禁止偏向锁优化反而可以提升性能。 #### 2.6简单总结各种锁优化 自适应偏向锁:自旋时间不固定 锁消除:如果发现代码是线程安全的,将锁去掉 锁粗化:加锁范围过小(重复加锁),将加锁的范围扩展 轻量级锁:在无竞争的情况下使用CAS操作去消除同步使用的互斥量 偏向锁:在无竞争环境下,把整个同步都消除,CAS也不做。 ## 三、Java内存模型 ###### JVM内存结构: ![](https://yuqingblog-upload.oss-cn-shanghai.aliyuncs.com/jiegou2020072911440089) ###### Java内存模型: ![](https://yuqingblog-upload.oss-cn-shanghai.aliyuncs.com/moxing20200729114400262) #### 操作变量时的规则: 1. Java内存模型规定了所有的变量都存储在主内存。 2. 线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝。 3. 线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量。 #### 从工作内存同步回主内存实现是通过以下的8种操作来完成: * lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态。 * unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。 * read(读取):作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用 * load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。 * use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。 * assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。 * store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。 * write(写入):作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。 #### Java内存模型是围绕着在并发过程中如何处理原子性、可见性和有序性这3个特征来建立的 **保证原子性的操作:** read、load、assign、use、store和write synchronized锁 ** 保证有序性(重排序导致无序)的操作:** volatile synchronized锁 ** 保证可见性:** volatile synchronized锁 final 非特殊说明,本文版权归 余情 所有,转载请注明出处. 本文标题: 几道让你拿offer的面试题 本文网址: http://www.yuqingbolg.cn/read/161 延伸阅读 SpringBoot之HandlerInterceptor拦截器的使用 [2020-07-22 11:40:35] echarts如何实现关键词云图 [2020-07-18 18:22:57] Java实现简单上传图片到阿里云对象存储OSS [2020-07-16 16:51:55] Error:java: OutOfMemoryError: insufficient memory [2021-04-16 10:49:08] 单点登录原理 [2020-12-30 17:21:20] 发表评论 提交评论