本文承接未完待续的 Java内存模型JMM之六深入理解synchronized(1)
3.7 锁消除
消除同步锁是JVM另外一种锁的优化,这种优化更彻底, JVM通过运行时JIT编译(可以理解为当某段代码即将第一次被执行时进行编译,又称即时编译),对一些在代码上要求添加同步,但是通过数据逃逸技术分析发现不可能存在共享数据竞争,这时JVM会对这些没有必要的同步锁进行消除。所以锁消除可以节省毫无意义的请求锁的时间,那么明知不存在数据竞争为何还进行了同步操作?作为程序员来说,这的确不会写出这样的代码,但是有时候程序并不是我们所想的那样,我们虽然没有显示的使用同步锁,但是我们在使用一些JDK内置的API时,如StringBuffer、Vector、HashTable等,这个时候会存在隐形的同步加锁操作。比如StringBuffer的append()方法,Vector的add()方法。
public void add(String str1, String str2) { //StringBuffer是线程安全,由于sb只会在append方法中使用,不可能被其他线程引用 //因此sb属于不可能共享的资源,JVM会自动消除内部的锁 StringBuffer sb = new StringBuffer(); sb.append(str1) .append(str2); }
以上代码很常见吧,特别是在拼接一些SQL、HQL等操作的时候, 由于该处的sb对象是一个局部变量,并且不会被其他线程引用到,但是StringBuffer的append方法却是个同步方法,此时,JVM就会通过锁消除机制将其锁消除。
3.7 锁粗化
原则上,我们在编写代码的时候,总是推荐奖同步块的作用范围限制的尽量小,只在共享数据的实际作用域中才进行同步,这是为了使得需要同步的操作尽可能的少,如果存在锁竞争也能够使等待锁的线程能够尽快地拿到锁。大部分情况下,这样的原则是没有问题的,但是如果一系列的连续操作都是对同一个锁的反复加锁和解锁,甚至在循环体中进行反复的加解锁操作,这样的情况即使没有线程竞争,频繁的进行互斥同步操作也会导致不必要的性能损耗。
锁粗化就是将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁,如下代码示例:
public void vectorTest(){ Vector<String> vector = new Vector<String>(); for(int i = 0 ; i < 10 ; i++){ vector.add(i + ""); } System.out.println(vector); }以上代码是很常见的在循环体中进行集合元素添加操作,我们知道vector的add方法是一个同步方法,每次add都需要加锁,执行完之后又需要解锁操作, 这时候JVM检测到对同一个对象(vector)连续加锁、解锁操作,就会合并一个更大范围的加锁、解锁操作,即将加锁解锁操作移到for循环之外,以一次性的加解锁操作取代多次的加解锁操作。
3.8 synchronized不能被interrupt中断
线程的中断方法说的是,只能中断正处于阻塞状态或者正准备执行一个阻塞操作的线程,这里说的阻塞其实就是指调用了Join, wait, sleep等方法导致线程进入的WAITING/TIMED_WAITING状态,因为这些方法能够感知到中断操作从而抛出中断异常。而所谓的synchronized不能被interrupt方法中断,指的是当线程执行到synchronized方法或者代码块的时候,由于对象锁被占用导致线程不得不通过调用底层的park方法进入死等状态,这时线程其实也是进入了阻塞状态,但是由于没有显示的调用能够抛出中断异常的方法,从而导致被阻塞的线程不能从阻塞状态恢复过来,使得线程一直处于锁等待的阻塞状态。如下代码示例可以作为一种说明:
public class ThreadTest implements Runnable{ public ThreadTest() { //该线程已持有当前实例锁 new Thread() { public void run() { f(); // Lock acquired by this thread } }.start(); } public synchronized void f() { System.out.println("Trying to call f()"); while(true) // Never releases lock Thread.yield(); } public void run() { //中断判断 while (true) { if (Thread.interrupted()) { System.out.println("中断线程!!"); break; } else { f(); } } } public static void main(String[] args) throws InterruptedException { Thread t = new Thread(new ThreadTest()); t.start(); TimeUnit.SECONDS.sleep(1); //中断线程,无法生效 t.interrupt(); } }
在创建ThreadTest示例时,其构造方法里面立即就执行了synchronized方法f,f方法会导致其一直持有对象锁,当线程t被创建出来也调用f方法的时候,将会一直处于锁等待状态,也是阻塞状态,虽然后来调用了interrupt中断方法,但是线程t并不能被中断,因为没有可以抛出中断异常的方法,线程t只能一直等待直到获取到对象锁。
3.9 synchronized锁及锁优化总结
锁 | 原理 | 优点 | 缺点 | 适用场景 |
偏向锁 | 如果一个线程获得了锁,那么该锁就进入偏向模式,当这个线程再次请求锁时,无需再做任何同步操作 | 省去重复获取/释放锁的开销 | 如果竞争激烈,会带来额外的锁撤销的消耗 | 大多数时候都只有一个相同的线程,并且多次访问同步块的场景,不存在竞争 |
轻量级锁 | 仅仅通过CAS指令和自旋操作达到锁的获取和释放 | 避免了线程从用户态切换到内核态的消耗,线程并不会被阻塞,提高了程序的响应速度 | 如果存在锁竞争,将会通过自旋消耗CPU | 不存在竞争,多线程之间交替执行,并且同步块执行较快的场景 |
重量级锁 | 通过操作系统底层的实现,阻塞竞争的线程和唤醒被阻塞的线程 | 比起其它情况,几乎没有优点 | 线程阻塞/唤醒需要在用户态和内核态之间切换,消耗大量成本,并且由于阻塞,使线程响应变慢 | 竞争激烈,同步代码块逻辑复杂需占用大量时间的场景 |
自旋/自适用自旋 | 通过执行多次无意义的指令,避免不必要的挂起和恢复线程时状态转换造成的性能消耗 | 避免了线程从用户态切换到内核态的消耗 | 白白消耗CPU | 轻量级锁和重量级锁产生竞争时,在切换到内核态之前 |
锁消除 | 通过数据逃逸技术分析对不存在数据共享的同步锁实施锁消除 | 使其消除了不必要的获取/释放锁的消耗 | 还好吧,完全由JVM自行完成,依赖JVM的JIT编译 | JVM的即时编译,针对JDK底层的API |
锁粗化 | 将多次对同一个锁对象的获取/释放操作,扩展为一次获取/释放操作 | 省去重复获取/释放锁的开销 | 完全依赖JVM自身 | 将同步块置于循环操作中的场景 |
相关推荐
深入理解Java内存模型对于编写高效的并发程序至关重要。本文将详细介绍JMM的核心概念、工作原理以及相关的编程实践。 1. **核心概念** - **线程私有区域**: 包括程序计数器、虚拟机栈、本地方法栈,这些区域中的...
JMM的目的之一是为了提供一个跨平台的内存模型,使得Java开发者可以更好地理解Java语言的语言特性和内存相关的内容。JMM的概念包括堆、栈、本机内存、防止内存泄漏等方面。 JSR133是JMM的一部分,它是Java语言规范...
《深入理解 Java 内存模型》这本书由程晓明编著,旨在帮助开发者深入理解和应用 JMM。 1. **内存层次结构**:Java 内存模型将内存分为堆内存、栈内存、方法区(在 Java 8 及以后版本中被元空间替代)和程序计数器等...
这本书"深入理解Java内存模型"显然是为了帮助读者深入探讨这个主题,以便更好地理解和解决并发编程中的问题。 Java内存模型主要涉及以下几个核心概念: 1. **主内存**:所有线程共享的数据存储区域,包括类的静态...
Java内存模型,简称JMM(Java Memory Model),是Java虚拟机规范中定义的一个抽象概念,它描述了在多线程环境下,如何保证各个线程对共享数据的一致性视图。JMM的主要目标是定义程序中各个变量的访问规则,以及在...
深入理解Java内存模型可以帮助开发者避免并发编程中常见的问题,如数据竞争、死锁和活锁等。通过合理地使用同步机制,可以编写出高效且线程安全的代码,这对于大型分布式系统和高并发应用尤为重要。 总之,Java内存...
在《深入理解Java内存模型(经典)》这本书中,作者可能详细探讨了JMM的原理、规则以及在实际编程中的应用,包括案例分析和最佳实践。通过阅读这本书,开发者可以更深入地掌握Java并发编程的核心技术,提高程序的...
本教程《深入理解JAVA内存模型》将带你深入探讨这一主题,尤其关注Java中的同步原语——synchronized、volatile和final。 首先,我们要了解JMM的基础结构。JMM规定了程序中各个线程如何访问和修改共享变量,包括主...
在深入理解Java内存模型之前,我们首先需要了解并发编程中的两个基本问题:线程间的通信和线程间的同步。 在并发编程中,线程间的通信是指线程之间如何交换信息,主要有两种并发模型。共享内存并发模型中,线程之间...
Java内存模型(JMM,Java Memory Model)是Java平台中用于描述如何在多线程环境中管理内存的一套规范。它确保了并发编程时不同线程之间的数据一致性、可见性和原子性,以避免出现数据竞争和其他并发问题。以下是JMM...
Java内存模型,简称JMM(Java Memory Model),是Java虚拟机规范中定义的一个抽象概念,它...通过阅读"深入理解Java内存模型(二)共3页.pdf.zip"中的内容,开发者可以进一步了解JMM的细节,解决并发编程中的复杂问题。
这些文档如"Java内存模型.docx"、"Java内存模型2.docx"、"深入Java核心 Java内存分配原理精讲.docx"、"java内存模型.pdf"将深入探讨这些概念,帮助开发者更深入地理解Java内存模型及其在实际编程中的应用。...
《深入理解Java虚拟机》是一本深度探讨Java虚拟机(JVM)的著作,涵盖了JVM性能调优、内存模型以及虚拟机原理等多个关键领域。本文将基于这些主题,详细阐述其中的重要知识点。 首先,我们要了解Java虚拟机(JVM)...
Java内存模型(Java Memory Model,简称JMM)是Java虚拟机(JVM)规范中定义的一...深入理解JMM的运作机制,合理使用Java提供的同步机制和并发工具类,才能有效解决多线程编程中的各种问题,编写出安全可靠的并发程序。
Java内存模型(JMM)是Java虚拟机(JVM)的一部分,它定义了程序中不同变量如何交互,特别是在多线程环境下。JMM确保了在各种硬件和操作系统平台上,Java程序的行为具有一致性和可预测性。Java内存模型的主要目标是...
总结来说,深入理解Java内存模型能帮助我们更好地掌握Java并发编程的底层原理,编写出高效、无错的并发代码,同时也有利于进行JVM的性能优化。通过阅读"深入理解JAVA内存模型.pdf",可以系统学习和掌握这些关键知识...
总之,深入理解Java内存模型对于编写高效、线程安全的Java程序至关重要。这需要我们熟悉JMM中的核心概念,如可见性、有序性和原子性,并掌握相关的同步工具。同时,理解JVM的内存管理,包括垃圾收集的工作原理,也是...