注:本系列文章均摘录自《深入理解Java虚拟机:JVM高级特性与最佳实践》,作者周志明,我看的是第一版,现在第二版已经出了,
第十三章 线程安全与锁优化
1."当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协同操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的"。
2.按照线程安全的“安全程度”由强到弱来排序,可以将Java语言中各种操作共享的数据分为以下五类:
(1)不可变。在Java语言里里面(特指JDK1.5以后,即Java内存模型被修正之后的Java语言),不可变(Immutable)的对象一定是线程安全的,无论是对象的方法实现还是方法的调用者,都不需要再进行任何的线程安全保障措施。
Java语言中,如果共享数据是一个基本数据类型,那么只要在定义时使用final关键字修饰它就可以保证它是不可变的。如果共享的数据是一个对象,那就需要保证对象的行为不会对其状态产生任何影响才行。
保证对象行为不影响自己状态的途径有很多中,其中最简单的就是把对象中带有状态的变量多声明为final,这样在构造函数结束之后,它就是不可变的。
(2)绝对线程安全
绝对的线程安全完全满足Brian Goetz给出的线程安全的定义。在Java API中标注自己是线程安全的类,大多数都不是绝对的线程安全。
(3)相对线程安全
相对线程安全就是我们通常意义上所讲的线程安全,它需要保证对这个对象单独的操作是线程安全的,我们在调用的时候不需要做额外的保障措施,但是对于一些特定顺序的连续调用,就可能需要在调用段使用额外的同步手段来保证调用的正确性。
(4)线程兼容
线程兼容是指对象本身并不是线程安全的,但是可以通过在调用端正确地使用同步手段来保证对象在并发环境中安全地使用,我们平常说一个雷不是线程安全的,绝大多数指的都是这种情况。
(5)线程对立
线程对立是指不管调用端是否采用了同步措施,都无法在多线程环境中兵法使用的代码。
线程对立的例子:Thread类的suspend()和resume()方法、System.setIn()、System.setOut()、System.runFinalizersOnExit()等。
3.互斥同步(Mutual Exclusion&Synchronization)是最常见的一种并发正确性保障手段,同步是指在多个线程并发访问共享数据时,保证共享数据在同一个时刻只被一条线程(或是一些,使用信号量的时候)线程使用。而互斥是实现同步的一种手段,临界区(Critical Section)、互斥量(Mutex)和信号量(Semaphore)都是主要的互斥实现方式。因此在这四个字里面,互斥是因,同步是果,互斥是方法,同步是目的。
在Java里面,最基本的互斥同步手段就是synchronized关键字。
除了synchronized之外,我们还可以使用java.util.concurrent(下文称J.U.C)包中的重入锁(ReentrantLock)来实现同步,在基本用法上,ReentrantLock与synchronized很相似,都具备线程可重入性,只是代码写法有区别,一个表现为API层面的互斥锁(lock()和unlock()方法配合try/finally语句块来完成),一个表现为原生语法层面的互斥锁。不过ReentrantLock 比synchronized增加了一些高级功能,主要有以下三项:
(1)等待可中断。
等待可中断是指当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情,可中断特性对处理执行时间长的同步块很有帮助。
(2)可实现公平锁。
公平锁是指多个线程在等待同一个锁的时候,必须按照申请锁的事件顺序来依次获得锁;而非公平锁在不保证这一点,在锁被释放时,任何一个等待的锁的线程都有机会获得锁。synchronized 中的锁是非公平锁,ReentrantLock默认情况下也是非公平锁,但可以通过带布尔值的构造函数要求使用公平锁。
(3)锁可以绑定多个条件
锁绑定多个条件是指一个ReentrantLock对象可以同时绑定多个Condition对象,而在synchronized中,锁对象的wait()和nofity()或nofityAll()方法可以实现一个隐含的条件,如果要和多于一个的条件关联时,就不得不额外地添加一个锁,而ReentrantLock则无须这样做,只需要多次调用newCondition()方法即可。
JDK1.6发布之后,人们就发现synchroni和ReentrantLock在性能上完全持平了。
4.非阻塞同步
互斥同步最重要的问题就是尽心线程阻塞和唤醒所带来的性能问题,因此这种同步也被称为阻塞同步(Blocking Synchronization)。另外,它属于一种悲观的并发策略,总是认为只要不去做正确的同步措施(加锁),那就肯定会出现问题。
基于冲突检测的乐观并发策略,通俗地说就是先进行操作,如果没有其他线程争用共享数据,那操作就成功了;如果共享数据有争用,产生了冲突,那就再进行其它的补偿措施(最常见的补偿措施就是不断地重试,知道试成功为止)。这中乐观的并发策略的许多实现都不需要把线程挂起,因此被称为非阻塞同步(Non-Blocking Synchronization)。
在JDK1.5之后,Java程序中才可以使用CAS操作,该操作由sun.misc.Unsafe类里面的compareAndSwapInt()和compareAndSwapLong()等几个方法包装提供,虚拟机在内部对这些方法做了特殊处理,即时编译出来的结果就是一条平台相关的处理器CAS之类,没有方法调用的过程,或者可以认为是无条件内联进去了。
5.无同步方案
要保证线程安全,并不一定就要进行同步,两者没有因果关系。同步只是保障共享数据争用时的正确性手段,如果一个方法本来就不涉及共享数据,那它自然就无须任何同步措施去保证正确性,因此会有一些代码天生就是现场安全的。
(1)可重入代码(Reentrant Code):这种代码也叫纯代码(Pure Code),可以在代码执行的任何时刻中断它,转而去执行另外一段代码(包括递归调用它本身),而在控制权返回后,原来的程序不会出现任何错误。相对于线程安全来说,可重入性是更基本的特性,它可以保证线程安全,即所有可重入的代码都是线程安全的,但并非所有的线程安全的代码都是可重入的。
(2)线程本地存储(Thread Local Storage):如果一段代码中所需要的数据必须与其他代码共享,那就看看这些共享数据的代码是否能保证在同一个线程中执行?如果能保证,我们就可以把共享数据的可见范围限制在同一个线程之内,这样,无须同步也能保证线程之间不出现数据争用的问题。
6.锁优化
高效并发是JDK1.6的一个重要主题,HotSpot虚拟机开发团队在这个版本上花费了大量的精力去实现各种锁优化技术,如适应性自旋(Adaptive Spinning)、锁消除(Lock Elimination)、锁粗化(Lock Coarsening)、轻量级锁(Lightweight Locking)、偏向锁(Biased Locking)等,这些技术都是为了在线程之间更高效地共享数据,以及解决竞争问题,从而提高程序的执行效率。
(1)为了让线程等待,只须让线程执行一个忙循环(自旋),这项技术就是所谓的自旋锁。
自旋锁在JDK1.4.2中就已经引入,只不过默认是关闭的,可以使用-XX:+UseSpinning参数来开启,在JDK1.6中就已经默认开启了。自旋次数的默认值时10次,用户可以使用参数-XX:PreBlockSpin来更改。
在JDK1.6中引入了自适应的自旋锁。自适应一位置自旋的时间不在固定了,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。
7.锁消除
锁消除是指虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除。锁消除的主要判定依据来源于逃逸分析的数据支持。
8.锁粗化
如果虚拟机探测到有一串零碎的操作都对同一个对象加锁,将会把加锁同步的范围扩展(粗化)到整个操作序列的外部。
9.轻量级锁
轻量级锁是JDK1.6中加入的新型锁机制,它名字中的“轻量级”是相对于使用操作系统互斥量来实现的传统锁而言的,因此传统的锁机制就被称为“重量级”锁。
HotSpot虚拟机的对象头(Object Header)分为两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄(Generational GC Age)等,这部分数据的长度在32位和64位的虚拟机中分别为32个和64个Bits,官方称它为“Mark Word”,它是实现轻量级锁和偏向锁的关键。
另外一部分用于存储指向方法区对象类型数据的指针,如果是数组对象的话,还会有一个额外的部分用于存储数组长度。
在代码进入同步块的时候,如果此同步对象没有被锁定(锁标志位为“01”状态),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储所对象目前的Mark Word的拷贝(官方把这份拷贝加了一个 Displaced前缀,即Displaced Mark Word)。
然后,虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针。如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象的Mark Word的锁标志位(Mark Word的最后两个Bits)将转变为"00",即表示此对象处于轻量级锁定的状态。
如果这个更新操作失败了,虚拟机会首先检查对象的Mark Word是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,可以直接进入同步块继续执行,否则说明这个锁对象已经被其他线程抢占了。如果有两条以上的线程争用同一个锁,那轻量锁就不再有效,要膨胀为重量级锁,锁标志的状态值变为“10”,Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。
10.偏向锁
偏向锁也是JDK1.6中引入的一项锁优化,它的目的是消除数据在无竞争情况下的同步原语,进一步提高程序的运行性能。如果说轻量级锁是在无竞争情况下使用CAS操作去消除同步使用的互斥量,那偏向锁就是在无竞争的情况下把整个同步都消除掉,连CAS操作都不做了。
偏向锁的“偏”,就是偏心的“偏”,偏袒的“偏”。它的意思是这个锁会偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要再进行同步。
偏向锁启用参数:-XX:+UseBiasedLocking,这是JDK1.6的默认值,禁用参数-XX:-UseBiasedLocking。
相关推荐
线程安全与锁优化:当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者再调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果。
为了深入学习和理解JVM的原理,可以参考诸如《深入理解Java虚拟机》这样的经典教材,其中涵盖了JVM从设计到实现的诸多细节。此外,随着JVM技术的发展,不断有新的特性被加入,因此,阅读官方文档、参与社区讨论、...
《深入理解JVM & G1 GC》一书深入剖析了Java虚拟机(JVM)的工作原理,特别是针对垃圾收集器(GC)中的G1(Garbage-First)算法进行了详尽的探讨。JVM是Java程序运行的基础,它负责解析、编译、执行Java代码,并管理...
《实战Java虚拟机——JVM故障诊断与性能优化》内容简介:随着越来越多的第三方语言(Groovy、Scala、JRuby等)在Java虚拟机上运行,Java也俨然成为一个充满活力的生态圈。本书将通过200余示例详细介绍Java虚拟机中的...
根据提供的文件信息,“圣思园张龙 深入理解jvm”,我们可以推断出这份资料主要关注于Java虚拟机(JVM)的深入理解和实践应用。JVM是Java开发环境中非常核心的一个组成部分,它不仅为Java程序提供了运行时环境,还负责...
每个使用Java的开发者都知道Java字节码是在JRE中运行,而JVM则是JRE中的核心组成部分,承担分析和执行Java字节码的工作,而Java程序员通常并不需要深入了解JVM运行情况就可以开发出大型应用和类库。尽管如此,如果你...
通过阅读《实战Java虚拟机——JVM故障诊断与性能优化》,读者不仅可以学习到JVM的基础知识,还能掌握如何在实际工作中诊断问题和优化性能,从而提升Java应用程序的运行效率和稳定性。这本书是Java开发者深入理解JVM...
【Java学习笔记——全面解析】 Java作为一种广泛应用的高级编程语言,是软件开发领域的核心力量。这份"学习笔记——资料"涵盖了Java学习的各个方面,旨在帮助初学者和有经验的开发者巩固基础,提升技能。以下是对这...
《深入理解JVM & G1 GC》主要为学习Java语言的学生、初级程序员提供GC的使用参考建议及经验,着重介绍了G1 GC。中国的软件开发行业已经有几十年了,从目前的行业发展来看,单纯的软件公司很难有发展,目前流 资源太...
深入理解JVM,首先要明白Java技术的组成部分,包括Java编程语言、Java类文件格式、Java虚拟机(JVM)和Java应用程序接口(Java API)。这些组件共同构成了Java平台,使得Java程序能够实现跨平台运行。 Java虚拟机是...
"Java学习笔记——良葛格"是一份专为初学者设计的教程资料,由良葛格精心编写,旨在帮助读者掌握JDK5.0版本的Java基础知识。JDK(Java Development Kit)是Java开发的核心工具集,包含了编译器、调试器和运行环境等...
全面理解JVM虚拟机.pdf
### 深入理解JVM #### 一、Java技术与Java虚拟机 Java不仅仅是一种编程语言,更是一项综合性的技术。它主要包括四个关键组成部分: 1. **Java编程语言**:这是一种面向对象的编程语言,提供了丰富的类库支持,...
《深入理解Java虚拟机JVM高级特性与最佳实践》一书,正是为解决这一需求而生。 本书的作者周志明站在前人的基础上,对JVM进行了全面而深入的解读,不仅覆盖了JVM的基础概念,而且深入探讨了JVM的高级特性,让读者...
《深入JVM内核—原理、诊断与优化》是一份深度探索Java虚拟机(JVM)的视频教程,旨在帮助开发者全面理解JVM的工作机制,掌握性能诊断技巧,并能进行有效的优化。本教程覆盖了从基础到高级的JVM主题,不仅适用于Java...
《深入理解Java虚拟机》是一本深度探讨Java虚拟机(JVM)的权威著作,旨在帮助读者全面了解和掌握JVM的工作原理与优化技术。这本书的内容涵盖了JVM的基础概念、内存管理、类加载机制、执行引擎、垃圾收集、性能调优...
本教程——“深入JVM内核—原理、诊断与优化视频教程”,将重点讲解这些关键点,帮助开发者提升技术水平,更好地解决实际问题。 首先,我们来探讨JVM的内核原理。JVM主要由类加载子系统、运行时数据区、执行引擎、...
本文将深入探讨JVM中的访问控制器,主要基于“java之jvm学习笔记十一(访问控制器)-源码”这一主题,以及相关的源码分析。 首先,我们得了解Java的安全模型。Java安全模型基于一种称为安全管理器(SecurityManager)...
### Java分布式应用学习笔记03:JVM对线程的资源同步和交互机制 在深入探讨Java虚拟机(JVM)如何处理线程间的资源同步与交互机制之前,我们先来明确几个关键概念:线程、多线程、同步、并发以及它们在Java中的实现...
《深入理解JVM & G1 GC》这篇文章和相关压缩包文件主要聚焦于Java虚拟机(JVM)的内存管理,特别是垃圾收集器(GC)的优化,特别是G1(Garbage-First)垃圾收集器的深度解析。下面将详细阐述JVM、GC的基本概念,...