`
brandNewUser
  • 浏览: 446509 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

java中的线程安全与锁优化

阅读更多
 
Java的线程是映射到操作系统的原生线程之上的,如果要阻塞或唤醒一条线程,都需要操作系统来帮忙完成,这就需要操作系统来帮忙完成,需要从用户态转换到内核态中,状态转换需要耗费很多的处理器时间。如果是非常简单的代码同步块,状态转换消耗的时间可能比用户代码执行的时间还要长。
 
因此可以说,synchronized是Java语言中的一个重量级操作,对于有经验的程序员都会在确实必要的情况下才使用这种操作,虚拟机本身也会进行一些优化,譬如在通知操作系统阻塞线程之前加入一段自旋等待过程,避免频繁地切入到核心态中。
 
关于用户态和内核态之间的区别,可以查看:http://www.cnblogs.com/viviwind/archive/2012/09/22/2698450.html,可以说,挂起线程和恢复线程的操作都需要转入内核态来完成,给系统的并发性能带来了很大压力。
 
除了synchronized之外,我们还可以使用JUC包中的重入锁ReentrantLock来实现同步,它与synchronized类似,都具备一样的线程重入特性,只是代码写法上有点区别。ReentrantLock比synchronized增加了一些高级功能,主要有以下几项:
 
  • 等待可中断:当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情,对处理执行时间非常长的同步块很有帮助;
  • 公平锁:多个线程在等待同一个锁时,必须按照申请锁的顺序来依次获得锁,非公平锁不能保证这一点,锁释放时任何一个等待锁的线程都有机会获得锁。synchronized中的锁是非公平的。
  • 锁绑定条件:一个ReetrantLock对象可以同时绑定多个condition对象,而在synchronized中,锁对象的wait, notify方法可以实现一个隐含的条件,如果要和多于一个条件关联的时候,就不得不额外添加一个锁。而ReentrantLock不需要这么做,只需要多次调用newCondition()即可。
 
互斥同步最主要的问题就是进行线程阻塞和唤醒带来的性能问题,这种同步也被称作阻塞同步,属于一种悲观的并发策略。随着硬件指令集的发展,有了另外一个选择:基于冲突检测的乐观并发策略,不需要将线程挂起,也被称为非阻塞同步。
 
这种乐观并发策略需要操作和冲突监测这两个步骤具备原子性,只能靠硬件来完成这件事情,保证一个从语义上看起来需要多次操作的行为只通过一条处理器指令就能完成,这类指令常用的有:
 
  • 测试并设置(Test-and-Set);
  • 获取并增加(Fetch-and-Increment);
  • 交换(Swap);
  • 比较并交换(Compare-and-Swap, CAS);
  • 加载链接/条件存储(Load-Linked/Store-Conditional, LL/SC);
 
锁优化
 
JVM在monitorenter和monitorexit字节码依赖于底层操作系统mutex lock(互斥锁)来实现的,但是由于使用mutex lock需要需要当前线程挂起并从用户态切换到内核态来进行,切换代价非常昂贵。
 
在大部分的情况下,同步方法是运行在单线程环境,也就是无锁竞争环境中,如果每次都调用mutex lock会严重影响性能,不过jdk1.6中对锁的实现引入了大量的优化:
 
锁粗化(Lock coarsening)
 
原则上来说我们编写代码总是推荐将同步块的作用范围限制得尽量小,为了使得同步操作数量尽可能变小,如果存在锁竞争,等待锁的线程也能尽快地拿到锁。但一系列的加锁和解锁,甚至加锁操作出现在循环中,也会极大地影响性能。因此减少不必要的紧连在一起的unlock,lock操作,将多个连续的锁扩展成一个范围更大的锁。

锁消除
 
通过运行时JIT编译器的逃逸分析来消除一些没有在当前同步块以外被其他线程共享的数据的锁保护。就是判断一段代码中,在堆上的所有数据都不会逃逸出去被其他线程访问到,就可以把它们当做栈上数据对待,认为它们是线程私有的,同步加锁无须进行。
 
轻量级锁

轻量级锁:基于一种假设,即在真实的情况下我们程序中的大部分同步代码一般都处于无锁竞争状态,单线程运行环境,在无锁竞争的情况下完全可以避免调用操作系统层面的重量级互斥锁,取而代之的是在monitorenter和monitorexit中只需要依靠一条CAS原子指令就可以完成锁的获取以及释放。当存在锁竞争的情况下,执行CAS指令失败的线程将调用操作系统互斥锁进行阻塞状态,当锁被释放的时候被唤醒。
偏向锁:为了在无锁竞争的情况下避免在锁获取过程中执行不必要的CAS原子指令,因为CAS操作原子指令虽然相对于重量级锁来说开销比较小,但还是存在可观的本地延迟。

自旋与适应性自旋
 
当线程在获取轻量级锁的过程中执行CAS操作失败时,在进入与monitor相关联的操作系统重量级(mutex semaphore)前会进入忙等待,然后再次尝试,当尝试一定的次数后如果仍然没有成功,则调用与该monitor关联的semaphore(互斥锁)进入阻塞状态,可以使用-XX:+UseSpinning参数来开启。自旋等待本身虽然避免了线程切换的开销,但是需要占用处理器时间的,如果锁被占用的时间很短,自旋等待效果就会很好,否则就是白白浪费处理器资源,不会做任何有用工作反而带来性能上的浪费。自旋次数的默认值=10,可以使用-XX:PreBlockSpin来更改,适应性自旋的时间就不会固定了,而是由前一次在同一个锁上的自旋时间以及锁的拥有者状态来决定。
 
 
轻量级锁
 
对于每个被锁住的对象(java中的所有锁的地方都是加在某个对象上的,无论是具体对象还是class),都会和一个monitor record关联,对象头中的LockWord指向monitor record起始地址,同时monitor record中有一个owner字段存放拥有该锁的线程的唯一标识,表示该锁被这个线程占用。
 
 
Java对象内存布局
 
 
对象在内存中的存储部分分成三个部分:
 
1.对象头,对象头中自身的运行时数据,Mark Word(在32bit和64bit虚拟机长度分别为32bit和64bit),主要包括以下的信息:
 
  • 对象hashCode;
  • 对象GC分代年龄;
  • 锁状态标志(轻量级锁,重量级锁);
  • 线程持有的锁(轻量级锁,重量级锁);
  • 偏向锁相关,偏向锁,自旋锁,轻量级锁以及其他的一些锁优化策略是jdb1.6加入的,这些优化使得synchronized的性能与ReentrantLock的性能持平,在synchronized可以满足要求的情况下,优先使用synchronized,除非是使用一些ReetrantLock独有的功能,比如指定时间等待等。
 
例如在32位的hotspot虚拟机中对象未被锁定的情况下,mark word的32bits中25bits用于存储对象hashCode,4bits用于存储对象分代年龄,2bits用于存储锁标志位,1bit固定为0
 


 
 
此外对象头中还包括类型指针,对象通过指向元数据的指针,JVM通过这个指针来确定这个对象是哪个类的实例;
 
2.实例数据,对象真正存储的数据,有效信息;
3.对齐填充,JVM要求对象大小必须是8的整数倍,如果不是则需要补位。
 
需要注意的是,mark word具有非固定的数据结构,以便在极小的空间内存储尽量多的信息;如果对象是一个数组,对象头必须有一块儿用于记录数组长度的数据,JVM可以通过Java对象的元数据确定对象长度,但是对于数组则不行;基本的数据类型中占用的内存大小:
 


 
 
轻量级锁和偏向锁
 
理解什么是偏向锁之前,必须要先理解什么是轻量级锁(lightweight locking)。引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令,由于一旦出现多线程竞争的情况,就必须撤销偏向锁,所以偏向锁的撤销操作的性能损耗必须小于节省下来的CAS原子指令的性能损耗。在JDK1.6以后默认开始了偏向锁的优化,可以通过启动JVM的时候加入 -XX:-UseBiasedLocking参数来禁用偏向锁,在存在大量锁对象的创建并高度并发的环境下禁用偏向锁能够带来一定的性能优化。
 
轻量级锁的执行过程:在代码进入同步块的时候,如果此同步对象没有被锁定(标志位为01状态),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录的空间,用于存储锁对象目前mark word的拷贝。然后虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针,如果更新成功该线程拥有该对象的锁,将对象mark word锁标志位转变为00,轻量级锁定状态;如果更新失败,虚拟机首先检查该对象的mark word是否指向当前线程的栈帧,如果是就说明当前线程已经拥有该对象的锁,可直接进入同步块继续执行,否则说明这个锁对象已经被其他线程抢占。
 
如果有两条以上的线程征用同一个锁,则轻量级锁不再有效,膨胀为重量级锁,状态值转换为10,后面等待锁的线程也要进入阻塞状态。此中的操作都是使用CAS尝试来进行比较并交换标志位。
 
轻量级锁提升程序同步性能的依据是:对于绝大部分的锁,在整个同步周期内都是不存在竞争的,但如果锁竞争较为激烈,除了互斥量的开销还额外发生了CAS操作,轻量级锁此时会比传统的重量级锁更慢。
 
偏向锁的目的是消除数据在无竞争情况下的同步原语,进一步提高程序的运行性能。如果说轻量级锁是在无竞争的情况下使用CAS操作去除同步使用的互斥量,那偏向锁就是在无竞争情况下把整个同步都消除掉,连CAS操作都不做了。
 
假如当前虚拟机设置了偏向模式,当锁对象第一次被线程获取的时候,虚拟机将会把对象头中的标志位设置为01,即偏向模式,同时使用CAS操作把获取到这个锁的线程ID记录在对象的Mark Word之中,如果CAS操作成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时,虚拟机都可以不再进行任何同步操作。
 
当另外的线程去尝试获取该锁时,偏向模式宣告结束,根据锁对象目前是否处于锁定状态,撤销偏向后恢复到未锁定或轻量级锁定状态,后续的同步操作就如轻量级锁来执行。
 
 
偏向锁可以提高带有同步但无竞争的程序性能,但并不一定总是对程序有力,如果大多数的锁总是被多个不同线程访问,偏向模式就是多余的。
 
 

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
  • 大小: 57.3 KB
  • 大小: 56 KB
  • 大小: 194.5 KB
分享到:
评论

相关推荐

    【Java正来-深入理解JVM】线程安全与优化。xmind思维导图

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

    Java 多线程编程面试集锦20道问题解答Java多线程编程高难度面试题及解析

    您将了解线程安全的实现、死锁的避免策略、线程池的使用方法、线程上下文切换的原因与优化、线程同步与互斥的区别、volatile关键字的作用、synchronized关键字的用法等。同时,我们还探讨了多线程编程中 通过研究和...

    java面试第二部分:多线程与锁

    锁升级、虚拟机锁优化、锁的实现原理、线程安全、可重入锁、线程池的实现等常见的面试问题

    Java多线程和并发知识整理

    3.3 JVM中锁的优化 3.4 Synchronized与Lock 3.5 扩展 四、volatile 详解 4.1 作用 4.2 实现原理 4.3 应用场景 五、final 详解 5.1 基础 5.2 重排序规则 5.3 扩展 六、JUC 6.1 汇总 6.2 Lock框架和Tools类...

    java高并发相关知识点.docx

    线程安全:Java中的线程安全,包括同步方法和同步块等。 死锁:Java中的死锁,包括如何避免死锁和如何解除死锁。 性能优化:Java中的性能优化,包括JVM参数调优、代码优化、使用并发框架等。 并行计算:Java中的并行...

    java最新高薪面试题库.docx

    在Java中如何实现线程安全? 什么是继承?Java中的继承有哪些特点? 什么是多态?Java中的多态有哪些实现方式? 什么是抽象类?Java中的抽象类有哪些特点? 什么是接口?Java中的接口有哪些特点? 什么是泛型?Java...

    JAVA_API1.6文档(中文)

    java.util.concurrent.atomic 类的小工具包,支持在单个变量上解除锁的线程安全编程。 java.util.concurrent.locks 为锁和等待条件提供一个框架的接口和类,它不同于内置同步和监视器。 java.util.jar 提供读写 ...

    java深度历险

    JAVA线程:基本概念、可见性与同步 16 JAVA线程基本概念 16 可见性 17 JAVA中的锁 18 JAVA线程的同步 19 中断线程 20 参考资料 20 JAVA垃圾回收机制与引用类型 22 JAVA垃圾回收机制 22 JAVA引用类型 23 参考资料 27 ...

    java虚拟机知识点整理

    线程安全与锁优化 1 标记-清除算法:首先标记所有需要回收的对象(引用计数或可达性分析算法标记),在标记完成后统一回收所有被标记的对象。 缺点:效率问题,标记和清除两个过程效率都不高,另一个是清除之后会产生...

    java面试题-java-interview-questions-master.zip

    java面试题_java-interview-questions-master.zip2、在 Java 程序中怎么保证多线程的运行安全? 出现线程安全问题的原因一般都是三个原因: 1、 线程切换带来的原子性问题 解决办法:使用多线程之间同步...

    Java并发编程(学习笔记).xmind

    事件处理器与访问共享状态的其他代码都要采取线程安全的方式实现 框架通过在框架线程中调用应用程序代码将并发性引入应用程序,因此对线程安全的需求在整个应用程序中都需要考虑 基础知识 线程安全性 ...

    Java 1.6 API 中文 New

    java.util.concurrent.atomic 类的小工具包,支持在单个变量上解除锁的线程安全编程。 java.util.concurrent.locks 为锁和等待条件提供一个框架的接口和类,它不同于内置同步和监视器。 java.util.jar 提供读写 JAR ...

    JavaAPI1.6中文chm文档 part1

    java.util.concurrent.atomic 类的小工具包,支持在单个变量上解除锁的线程安全编程。 java.util.concurrent.locks 为锁和等待条件提供一个框架的接口和类,它不同于内置同步和监视器。 java.util.jar 提供读写 ...

    Java面试题-并发.docx

    这份文档详细讨论了Java中HashMap的各个方面,包括其内部机制、线程安全性、性能优化以及与其他类的比较等。通过对HashMap的不同问题进行深入分析,读者可以全面了解该数据结构的工作原理和使用注意事项。 首先,...

    Java面试题-哈希.docx

    这份文件详细探讨了Java中HashMap的各个方面,包括其内部机制、线程安全性、性能优化以及与其他类的比较等。通过对HashMap的不同问题进行深入分析,读者可以全面了解该数据结构的工作原理和使用注意事项。 首先,...

    超全的Java岗面试题库合集.zip

    乐观锁与悲观锁 设计模式 数据库 性能优化 ActiveMQ消息中间件 Dubbo JVM Kafka Linux MongoDB MyBatis MySQL Netty Nginx RabbitMQ消息中间件 Redis Spring SpringBoot SpringCloud SpringMVC Tomcat Zookeeper 四...

    深入理解_Java_虚拟机 JVM_高级特性与最佳实践

    / 342 13.1 概述 / 342 13.2 线程安全 / 343 13.2.1 Java语言中的线程安全 / 343 13.2.2 线程安全的实现方法 / 348 13.3 锁优化 / 356 13.3.1 自旋锁与自适应自旋 / 356 13.3.2 锁消除 / 357 13.3.3 锁粗化 ...

    Java虚拟机

    第13章 线程安全与锁优化 13.1 概述 13.2 线程安全 13.2.1 Java语言中的线程安全 13.2.2 线程安全的实现方法 13.3 锁优化 13.3.1 自旋锁与自适应自旋 13.3.2 锁消除 13.3.3 锁粗化 13.3.4 轻量级锁 13.3.5...

    理解原子操作,CAS加锁是线程安全的.docx

    在Java中实现并发用的最多的就是synchronized关键字了,自从jdk1.6对synchronized进行重大优化后,其广为人诟病的性能问题也得到了改善,与ReentrankLock相比性能方面相差无几 性能的改善得益于 偏向锁、轻量级锁 ...

    java面试笔试资料包括JAVA基础核心知识点深度学习Spring面试题等资料合集.zip

    第五题 如何保证集合是线程安全的.pdf 第八题 Java并发类库提供的线程池有哪几种 分别有什么特点.pdf 第六题 synchronized和ReentLock有什么区别.pdf 第四题 ArrayList LinkedList Vector的区别.pdf docker讲得最...

Global site tag (gtag.js) - Google Analytics