JAVA虚拟机(JVM)详细介绍(八)——高效并发
内存模型
内存模型是在特定的操作协议下,对特定的内存或高速缓存进行读写访问的过程抽象。其主要目标是定义程序中各个变量的访问规则。
主内存和工作内存
所有的变量都存储在主内存中,每条线程还有自己的工作内存,其工作内存中是被线程使用到的变量的主内存副本拷贝,线程对变量的读取、赋值等操作都必须在工作内存中进行,而不能直接读取主内存中的变量。
内存间交互操作
从主内存拷贝到工作内存:顺序地执行read和load操作。
工作内存同步到主内存:store和write操作。
volatile的特性
Volatile的作用和synchronized相同,但是和synchronized相比,更轻量。其特性主要有如下两点:
保证此变量对所有线程的可见性
啥意思呢?指当一个线程修改了这个变量的值,新值对于其他线程来说是立即可知的。而普通变量做不到这一点,普通变量的值在线程间传递均需要通过主内存来完成,比如线程A修改了一个普通变量的值,然后向主内存进行回写,另外一条线程B在线程A回写完成了之后再从主内存进行读取操作,新变量值才会对线程B可见。
禁止指令重排序优化
因为指令重排序会干扰程序的并发执行。
多线程
为什么需要多线程?
计算机的运算速度与它的存储和通信子系统速度的差距太大,大量的时间都花费在磁盘I/O、网络通信、数据库访问上了。使用多线程能更好地利用cpu。
有哪些并发应用场景?
充分利用计算机处理器
一个服务端同时对多个客户端提供服务
如何使处理器内部的运算单元被充分利用?
加入一层高速缓存
将运算需要使用到的数据复制到缓存中,让运算能快速进行。当运算结束后再从缓存同步回内存中,这样处理器就无须等待缓慢的内存读写了。不过这个要考虑一个问题:怎么保证缓存的一致性。
对输入代码进行乱序执行优化
线程的实现方式
使用内核线程实现
内核线程就是直接由操作系统内核支持的线程。
使用用户线程实现
用户线程的建立、同步、销毁和调度完全在用户态中完成,不需要内核的帮助,内核也感知不到线程存在的实现。这种实现方式使用较少。
使用用户线程加轻量级进行混合实现
合并到一起
线程调度
线程调度是指系统为线程分配处理器使用权的过程。主要分为两种:协同式和抢占式。
协同式
线程的执行时间由线程本身来控制,线程把自己的工作执行完了,会主动通知系统切换到另外一个线程上。
其优点是实现简单,而且没有线程同步的问题。缺点是如果一个线程编写有问题,一直不告诉系统进行线程切换,那程序就会一直阻塞在那里,容易导致系统崩溃。
抢占式
线程将由系统来分配执行时间,线程切换不由本身来决定。java使用的线程调度方式就是这种。
线程安全
当多个线程访问一个对象时,如果不考虑这个线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其它的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是安全的。
共享数据的分类
不可变
不可变的共享数据是用final修饰的数据,其一定是线程安全的。如果共享数据是一个基本类型变量,那么只要在定义的时候使用final关键字即可。
如果共享数据是一个对象,那就需要对象的行为不会对其状态产生影响,可以将对象中带有状态的变量都声明为final。比如String类就是一个不可变类
绝对线程安全
在Java API中标注自己是线程安全的类,大多数都不是绝对的线程安全。比如Vector是一个线程安全的集合,它的所有的方法都被修饰成同步,但是在多线程的环境中,它依旧不是同步的。
相对线程安全
相对线程安全就是我们通常意义上所说的线程安全,它只能保证对这个对象单独操作是线程安全的。但是对于一些特定顺序的连续调用,就可能需要在调用端使用额外的同步手段来保证调用的正确性。
大部分的线程安全类都属于这种类型。
线程兼容
对象本身不是线性安全的,但可以通过在调用端正确地使用同步手段来保证对象在并发环境中可以安全的使用。大部分的不是线程安全的类,都属于这种类型。
线程对立
无论怎样,都不能在多线程环境中并发使用,如System.setIn()、System.SetOut()。一个对输入进行修改,一个对输出进行修改,两者是不能“交替”进行的。
实现方法
方式一:互斥同步——悲观并发策略
(1)synchronized
其原理是:这个关键字在经过编译后,会在同步块的前后分别形成monitorenter和monitorexit这两个字节码指令。当执行monitorenter指令时,程序会尝试获取对象的锁,如果能获取到,则把锁的计数器+1,相应的,在执行monitorexit时,会将锁计数器-1。当计数器为0时,锁就被释放。
其特点是:对同一条线程来说是可重入的;同步块在已进入的线程执行完之前,会阻塞后面的其他线程进入。
其选用场景是:在确实必要的情况下才使用此,因为其是重量级的。
(2)ReentrantLock
此重入锁是java.util.concurrent(JUC)包下的类。其高级特性有:等待可中断、可实现公平锁、锁可以绑定多个条件。
方式二:非阻塞同步——乐观并发策略
先进行操作,如果没有其它线程争用共享数据,那操作就是成功了;如果共享数据有争用,产生了冲突,那就再采取其它的补偿措施。
方式三:无同步方案
如果一个方法本来就不涉及共享数据,那就没有必要进行同步措施。比如可重复代码和线程本地存储。
(1)可重入代码
如果一个方法,它的返回结果是可预测的,只要输入了相同的数据,就都能返回相同的结果,那它就满足可重入的要求。
(2)线程本地存储
如果一段代码中所需要的数据必须与其它代码共享,而且这些共享数据的代码在同一个线程中执行,如此,我们可以把共享数据的可见范围限制在一个线程中,这样,就不用同步也能保证线程之间不出现数据争用问题。
锁优化
适应性自旋
因为阻塞或者唤醒一个JAVA的线程需要操作系统切换CPU状态来完成,这种状态的转换需要耗费处理器时间。如果同步代码块中的内容过于简单,很可能导致状态转换消耗的时间比用户代码执行的时间还要长。
为了解决这个问题,我们可以让后面请求锁的线程“稍等一下”,执行一个忙循环,进行自旋。此时没有放弃处理器的执行时间。如果自旋超过了限定的次数,仍然没有成功获得锁,那就会使用传统的方式去挂起线程了。
那什么叫做适应性自旋呢?
就是在同一个锁对象上,如果自旋等待刚刚成功获得过锁,那虚拟机就会认为这次自旋获得锁的概率挺大,就会允许其自旋等待持续相对更长的时间。相反,如果自旋很少成功获得过锁,则可能省略掉自旋过程。
锁消除
指虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除。
锁粗化
如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作是出现在循环体中的,那即使没有线程竞争,频繁地进行互斥同步操作也会导致不必要的性能损耗。
如果虚拟机探测到一串零碎的操作都对同一个对象加锁,将会把加锁同步的范围粗化到整个操作序列的外部,这样只需要加锁一次就够了。
轻量级锁
在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能损耗。
适用场景:无实际竞争,多个线程交替使用锁;允许短时间的锁竞争。
偏向锁
偏向锁用于减少无竞争且只有一个线程使用锁的情况下,使用轻量级锁产生的性能消耗。轻量级锁每次申请、释放锁都至少需要一次CAS,但偏向锁只有初始化时需要一次CAS。
适用场景:无实际竞争,且将来只有第一个申请锁的线程会使用锁。
以上是关于JAVA虚拟机中高效并发的详细介绍,更多相关问题请访问PHP中文网:JAVA视频教程
以上是JAVA虚拟机(JVM)详细介绍(八)——高效并发的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

该项目为了方便开发者更快监控多个远程主机jvm,如果你的项目是Spring boot那么很方便集成,jar包引入即可,不是Spring boot也不用气馁,你可以快速自行初始化一个Spirng boot程序引入jar包即可

掌握JVM内存使用情况的要点与注意事项JVM(JavaVirtualMachine)是Java应用程序运行的环境,其中最为重要的就是JVM的内存管理。合理地管理JVM内存不仅可以提高应用程序的性能,还可以避免内存泄漏和内存溢出等问题。本文将介绍JVM内存使用的要点和注意事项,并提供一些具体的代码示例。JVM内存分区JVM内存主要分为以下几个区域:堆(He

通过JVM命令行参数,您可以细粒度地调整JVM行为。其中通用参数包括:设置Java堆大小(-Xms、-Xmx)设置新生代大小(-Xmn)启用并行垃圾收集器(-XX:+UseParallelGC)减少Survivor区内存占用(-XX:-ReduceSurvivorSetInMemory)消除冗余垃圾回收(-XX:-EliminateRedundantGCs)打印垃圾回收信息(-XX:+PrintGC)使用G1垃圾收集器(-XX:-UseG1GC)设置最大垃圾回收暂停时间(-XX:MaxGCPau

Java是一种流行的编程语言,在开发Java应用程序的过程中,可能会遇到JVM内存溢出错误。这种错误通常会导致应用程序崩溃,影响用户体验。本文将探讨JVM内存溢出错误的原因和如何处理和避免这种错误。JVM内存溢出错误是什么?Java虚拟机(JVM)是Java应用程序的运行环境。在JVM中,内存被分为多个区域,其中包括堆、方法区、栈等。堆是用于存储创建的对象的

JVM虚拟机的作用及原理解析简介:JVM(JavaVirtualMachine)虚拟机是Java编程语言的核心组成部分之一,它是Java的最大卖点之一。JVM的作用是将Java源代码编译成字节码,并负责执行这些字节码。本文将介绍JVM的作用及其工作原理,并提供一些代码示例以帮助读者更好地理解。作用:JVM的主要作用是解决了不同平台上Java程序的可移

JVM原理详解:深入探究Java虚拟机的工作原理,需要具体代码示例一、引言随着Java编程语言的迅猛发展和广泛应用,Java虚拟机(JavaVirtualMachine,简称JVM)也成为了软件开发中不可或缺的一部分。JVM作为Java程序的运行环境,能够提供跨平台的特性,使得Java程序能够在不同的操作系统上运行。在本文中,我们将深入探究JVM的工作原

JVM内存参数设置:如何合理调整堆内存大小?在Java应用程序中,JVM是负责管理内存的关键组件。其中,堆内存是用于存储对象实例的地方,堆内存的大小设置对应用程序的性能和稳定性有着重要影响。本文将介绍如何合理调整堆内存大小的方法,并附带具体代码示例。首先,我们需要了解一些关于JVM内存的基础知识。JVM的内存分成了几个区域,包括堆内存、栈内存、方法区等。其中

在编写java程序来检查JVM是32位还是64位之前,我们先讨论一下JVM。JVM是java虚拟机,负责执行字节码。它是Java运行时环境(JRE)的一部分。我们都知道java是平台无关的,但是JVM是平台相关的。我们需要为每个操作系统提供单独的JVM。如果我们有任何java源代码的字节码,由于JVM,我们可以轻松地在任何平台上运行它。java文件执行的整个过程如下-首先,我们保存扩展名为.java的java源代码,编译器将其转换为扩展名为.class的字节码。这发生在编译时。现在,在运行时,J
