[转帖]【JVM】线程安全与锁优化

jvm,线程,安全,优化 · 浏览次数 : 0

小编点评

**1. 定义** * 当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果。 **2. 类别** * (1)不可变不可变的对象一定是线程安全的,只要一个不可变对象被正确地构建出来(没有发生this引用逃逸情况),那其外部的可见状态永远也不会改变 ,永远也不会看到它在多个线程之中处于不一致的状态。如Integer类,内部状态变量value定义为final来保障状态不变。 * (2)绝对线程安全 要达到不管运行的环境如何,调用者都不需要任何额外的同步措施。在Java API中标注自己是线程安全的类,大多数都不是绝对线程安全的。 * (3)相对线程安全 通常意义上的线程安全,需要保证对这个对象的单独操作是线程安全的,在调用的时候不需要做额外的保障措施。 * (4)内置于JVM中获取锁的步骤 **3.锁优化方法** * **减少锁的持有时间** * 方法锁改为锁对象 * **减少锁粒度** * 将大对象(这个对象可能会被很多线程访问),拆成小对象,大大增加并行度,降低锁竞争 * **锁分离** *读写锁 * **锁粗化** * 使用完公共资源后,立即释放锁 * **锁清除** * 指在虚拟机即时编译器运行时,对一些代码上要求同步,但被检测到不可能存在共享数据竞争的锁进行消除

正文

线程安全

1.定义

当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果

2.分类

(1)不可变

不可变的对象一定是线程安全的,只要一个不可变对象被正确地构建出来(没有发生this引用逃逸情况),那其外部的可见状态永远也不会改变 ,永远也不会看到它在多个线程之中处于不一致的状态。如Integer类,内部状态变量value定义为final来保障状态不变。

例1: JDK 中 Integer 类的构造函数

   private final int value;  

/**

  • Constructs a newly allocated {@code Integer} object that
  • represents the specified {@code int} value.

* @param value the value to be represented by the

  •              {@code Integer} object. 
    

*/
public Integer(int value) {
this.value = value;
}

    (2)绝对线程安全

    要达到不管运行的环境如何,调用者都不需要任何额外的同步措施。在Java API中标注自己是线程安全的类,大多数都不是绝对线程安全的。

    例2:加入同步以保证 Vector 访问的线程安全性

    Thread removeThread = new Thread(new Runnable() {  
        @Override  
        public void run() {  
            synchronized(vector) {  
                for (int i = 0; i < vector.size(); i++) {  
                    vector.remove(i);  
                }  
            }  
        }  
    });  
    

    Thread printThread = new Thread(new Runnable() {
    @Override
    public void run() {
    synchronized(vector) {
    for (int i = 0; i < vector.size(); i++) {
    System.out.println(vector.get(i));
    }
    }
    }
    });

      (3)相对线程安全

      通常意义上的线程安全,需要保证对这个对象的单独操作是线程安全的,在调用的时候不需要做额外的保障措施,但对于一些特定顺序的连续调用,就可能需要在调用端使用额外的同步手段来保证调用的正确性。如Vector、HashTable、Collections的synchronizedCollection()方法包装的集合。

      (4)线程兼容

      指对象本身不是线程安全的,但是可以通过调用端正确地使用同步手段来保证对象在并发环境中可以安全地使用,Java API大部分类属于线程兼容,如ArrayList、HashMap。

      (5)线程对立

      无论调用端是否采取了同步措施,都无法在多线程环境下并发使用的代码。有害的,应该尽量避免,如Thread类的suspend()和resume()方法,如果两个线程同时持有一个线程对象,一个尝试去中断线程,一个尝试去恢复线程,并且并发进行,无论调用时是否进行了同步,目标线程都存在死锁风险,所以这两个方法已经被声明废弃。

      锁优化

      1.定义

      在阻塞式的情况下,如何让性能不要变得太差

      2.锁的分类

      锁分为:轻量级锁、自旋锁、偏向锁、

      • 对象头:描述对象的hash、锁信息、垃圾回收标记。指向锁记录的指针、指向monitor的指针、GC标记、偏向锁线程ID

      (1)偏向锁:锁会偏向于当前已经占有锁的线程

      • 将对象头Mark的标记设置为偏向,并将线程ID写入对象头Mark

      • 只要没有竞争,获取偏向锁的线程,在将来进入同步块时,不需要同步

      • 当其他线程请求相同的锁时,偏向模式结束

      • -XX:+UseBiasedLocking 默认启用

      • 在竞争激烈的场合,偏向锁会增加系统的负担

      (2)轻量级锁

      • 在代码进入同步块的时候,如果此同步对象没有被锁定,虚拟机首先将在当前线程的栈帧中建立一个名为锁记录的空间,用于存储锁对象目前的Mark Word的拷贝,称为Displaced Mark Word,

      • 然后使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针,如果更新动作成功,那么线程就拥有了该对象的锁,并且对象Mark Word的锁标志位转变成“00”,表示处于轻量级锁定状态。

      • 如果更新动作失败,先检查对象的Mark Word是否指向当前线程的栈帧,如果是,就可以直接进入同步块继续执行,否则说明这个锁对象被其他线程抢占了,如果有两条以上线程争用同一个锁,那轻量级锁就不再有效,要膨胀为重量级锁,锁标志的状态值转变为“10”。

      • 解锁过程也是通过CAS操作进行,如果对象的Mark Word仍然指向线程的锁记录,就用CAS操作把对象当前的Mark Word和线程中复制的Displaced Mark Word替换回来,

      • 如果替换成功,整个同步过程就完成了,如果替换失败,说明有其他线程尝试过获取锁,那就要在释放锁的同时,唤醒被挂起的线程。

      • 轻量级锁使用CAS操作避免使用互斥量的开销,如果存在锁竞争,除了互斥量的开销,还额外发生了CAS操作。

      (3)自旋锁

      • 若线程可以很快获得锁,不用在OS挂起锁,让线程自旋。

      • 共享数据的锁定状态可能只持续很短的一段时间,为了这段时间去挂起和恢复线程不值得。如果物理机器有一个以上的处理器,能让两个或以上的线程同时并行执行,可以让后面请求锁的线程“等待一段时间”,但不放弃处理器的执行时间,看看持有锁的线程是否很快释放锁,为了让线程等待,让线程执行一个忙循环(自旋),这项技术就是自旋锁。

      • JDK 1.6引入自适应自旋,自旋时间不固定,由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行,那么虚拟机会认为这次自旋也很可能再次成功,进而允许自旋等待持续相对更长的时间,另外,如果对于某个锁,自旋很少成功,那么在以后获取这个锁时将可能省略掉自旋过程,以避免浪费处理器资源。

      (4)内置于JVM中获取锁的步骤

      ① 偏向锁可用会尝试偏向锁

      ② 轻量级锁可用会尝试轻量级锁

      ③ 若①②失败,尝试自旋锁

      ④ 若①②③都失败,尝试普通锁

      图解:

      这里写图片描述

      2.锁优化的方法

      (1)减少锁的持有时间(方法锁改为锁对象)

      例1

      优化前:在进入方法前就要得到锁,其他线程就要在外面等待

      public synchronized void syncMethod(){  
              othercode1();  
              mutextMethod();  
              othercode2(); 
          }

        优化后:减少其他线程等待的时间

        public void syncMethod(){  
                othercode1();  
                synchronized(this)
                {
                    mutextMethod();  
                }
                othercode2(); 
            }

          (2)减少锁粒度:将大对象(这个对象可能会被很多线程访问),拆成小对象,大大增加并行度,降低锁竞争

          例:concurrentHashMap的分段锁

          (3)锁分离:读写锁

          例:LinkedBlockingQueue

          这里写图片描述

          从头部取出,从尾部放数据

          (4)锁粗化:使用完公共资源后,立即释放锁

          如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁出现在循环体中,那即使没有线程竞争,频繁地进行互斥同步操作也会导致不必要的性能损耗。如果虚拟机检测到有一串零碎的操作都对同一个对象加锁,将会把加锁同步的范围扩展到整个操作序列的外部。

          例2:

          优化前:

          public void demoMethod(){  
                  synchronized(lock){   
                      //do sth.  
                  }  
                  //做其他不需要的同步的工作,但能很快执行完毕  
                  synchronized(lock){   
                      //do sth.  
                  } 
              }

            优化后:

            public void demoMethod(){  
                    //整合成一次锁请求 
                    synchronized(lock){   
                        //do sth.   
                        //做其他不需要的同步的工作,但能很快执行完毕  
                    }
                }

              (5)锁清除:

              指在虚拟机即时编译器运行时,对一些代码上要求同步,但被检测到不可能存在共享数据竞争的锁进行消除,判定依据来源于逃逸分析的数据支持,如果一段代码,堆上的所有数据都不会逃逸出去被其他线程访问到,那就可以把它们当做栈上数据对待,认为是线程私有的,同步加锁无需进行。

              (6)无锁:无锁的实现方式(CAS)



              本人才疏学浅,若有错,请指出
              谢谢!

              与[转帖]【JVM】线程安全与锁优化相似的内容:

              [转帖]【JVM】线程安全与锁优化

              线程安全 1.定义 当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果 2.分类 (1)不可变 不可变的对象一定是线程安全的,只要一个不可变对象被正确地构建出来(没有发生thi

              [转帖]JVM(3)之垃圾回收(GC垃圾收集器+垃圾回收算法+安全点+记忆集与卡表+并发可达性分析......)

              《深入理解java虚拟机》+宋红康老师+阳哥大厂面试题2总结整理 一、堆的结构组成 堆位于运行时数据区中是线程共享的。一个进程对应一个jvm实例。一个jvm实例对应一个运行时数据区。一个运行时数据区有一个堆空间。 java堆区在jvm启动的时候就被创建了,其空间大小也就被确定了(堆是jvm管理的最大

              [转帖]谈 JVM 参数 GC 线程数 ParallelGCThreads 合理性设置

              https://my.oschina.net/u/4090830/blog/7926038 1. ParallelGCThreads 参数含义 在讲这个参数之前,先谈谈 JVM 垃圾回收 (GC) 算法的两个优化标的:吞吐量和停顿时长。JVM 会使用特定的 GC 收集线程,当 GC 开始的时候,GC

              [转帖]JVM系列之:你知道Java有多少种内存溢出吗

              本文为《深入学习 JVM 系列》第二十五篇文章 Java内存区域 关于这部分内容大多来源于《深入理解Java虚拟机》一书。 Java 运行时数据区域(JDK8)如下图所示: 关于上述提到的线程共享和线程隔离区域,下图做详细讲解: 程序计数器 程序计数器是一块较小的内存空间,可以看作是当前线程所执行的

              [转帖]【JVM】Java内存区域与OOM

              引入 Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的“高墙”,墙外面的人想进去,墙里面的人却想出来。 Java虚拟机运行时数据区 如图所示 1.程序计数器(线程私有) 作用 记录当前线程所执行到的字节码的行号。字节码解释器工作的时候就是通过改变这个计数器的值来选取下一条需要执行的字节

              [转帖]【JVM】Java内存区域与OOM

              引入 Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的“高墙”,墙外面的人想进去,墙里面的人却想出来。 Java虚拟机运行时数据区 如图所示 1.程序计数器(线程私有) 作用 记录当前线程所执行到的字节码的行号。字节码解释器工作的时候就是通过改变这个计数器的值来选取下一条需要执行的字节

              【转帖】JVM 内存模型与垃圾回收

              文章目录 1. JVM内存模型1.1. 程序计数器 (线程私有)1.2. Java 虚拟机栈 (线程私有)1.3. 本地方法栈 (线程私有)1.4. Java 堆 (线程共享)1.5. 方法区 (线程共享)1.6. 运行时常量池 (线程共享)1.7. 直接内存 (堆外内存) 2. 垃圾查找算法2.1

              [转帖]JVM CPU过高排查之路

              https://www.jianshu.com/p/97860bbeb45c 双十一了,头一天晚上10点左右收到阿里云cpu超过90%短信报警。 第二天上班了,开始处理,步骤如下: 1、top找出cpu高的java进程号9592 2、top -Hp 9592查看cpu占用time最高的线程编号281

              [转帖]JVM参数:-XX:ReservedCodeCacheSize

              通过笨神的分享整理笔记: 这个参数主要设置codecache的大小,比如我们jit编译的代码都是放在codecache里的,所以codecache如果满了的话,那带来的问题就是无法再jit编译了,而且还会去优化。因此大家可能碰到这样的问题:cpu一直高,然后发现是编译线程一直高(系统运行到一定时期)

              【转帖】15.JVM栈帧的内部结构

              目录 1.栈中存储的是什么?2.栈的运行原理 1.栈中存储的是什么? 1.每个线程都有自己的栈,栈中存储的是栈帧。 2.在这个线程上正在执行的每个方法都各自对应一个栈帧。方法与栈帧是一对一的关系。 3.栈帧是一个内存区块,是一个数据集,维系着方法执行过程中的各种数据信息。 2.栈的运行原理 1.JV