java 并发_Java并发防范机制

1.背景

并发程序开发不可避免地要涉及多线程、多线程协作、数据共享和线程安全等问题。在多线程并发场景下,由于采用数据共享的线程通信模型可能导致多个线程之间并发时相互干扰,影响到程序的正常逻辑、无法保证正常的结果。为了保证程序在并发环境的正确性,有必要对多线程并发进行防范,因此就有了并发控制机制。

Java并发控制机制

并发防范机制等价于并发控制机制,同步(有序)机制可以说是并发防范的一个子集。Java并发提供了多个维度的并发防范机制。我们可划分JVM、JDK 2个层面:

  1. JVM层面主要指关键字同步原语(volatile、synchronized、final)。通过字节码指令禁止指令重排序来保证顺序一致性。
  2. JDK层面是JUC并发包,比如基于队列同步器实现的重入锁RetrantLock、读写锁ReentrantReadWriteLock,此外还有Semaphore、CountDownLatch、CyclicBarrier等并发工具(本质还是锁)、原子操作类(比如AtomicInteger)、ThreadLocal线程局部变量(无锁防并发方案)、线程安全的并发容器(ConcurrentHashMap、BlockingQueue等)。

关于线程安全

“线程安全”网上大部分的解释是:如果一个对象可以安全地被多个线程同时使用,那它就是线程安全的。并不能说它不对,但是不够精确,几乎获取不到什么有用信息。

《Java Concurrency In Practice》的作者Brian Goetz为“线程安全”做出了一个比较恰当的定义:当多个线程同时访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那就称这个对象是线程安全的。”

这个定义就很严谨而且有可操作性,它要求线程安全的代码都必须具备一个共同特征:代码本身封装了所有必要的正确性保障手段(如互斥同步等),令调用者无须关心多线程下的调用问题,更无须自己实现任何措施来保证多线程环境下的正确调用。这点听起来简单,但其实并不容易做到。

2.JVM同步机制

volatile

volatile关键字的并发安全性承诺(即声明为volatile的变量可以做到):

  1. 线程对volatile变量的修改,可以及时反应到其他线程(对volatile变量的写入可以及时作用到主内存,其他线程读取volatile变量也是直接从主内存读取)。
  2. volatile变量的读写有序性(JVM通过字节码指令enterexit禁止指令重排序来保证有序性),即两线程并发使volatile变量的写入总是先行发生于对volatile变量的读取。

以上描述的是volatile变量在多个线程间的可见性和有序性(禁止指令重排序),说到底volatile变量需要保证volatile写/读顺序,volatile重排序规则表如下(JSR-133):

cb586c8774609ec3a3d38a76cbf677fe.png
  • 首先,若第二个操作是volatile写,则不允许指令重排序。
  • 其次,若第一个操作是volatile读,同样不允许指令重排序。
  • 最后,当第一个操作是volatile写,第二个操作volatile读,则不允许指令重排序。

那么volatile具体是如何做到的?

为了实现volatile的内存语义,JVM采用基于保守策略的JMM内存屏障插入策略。

  • 在每个volatile写操作前面插入StoreStore屏障、在每个volatile写的后面插入StoreLoad屏障。
  • 在每个volatile读操作后面插入LoadLoad屏障、在每个volatile读的后面插入LoadStore屏障。

基于保守策略可保证在任意平台、任意程序都得到正确的volatile语义。

0b01402846dd59ec9c4cd99ddfc44019.png

通过加入屏障可以保证volatile写-读与锁的释放-获取具有相同的内存效果:锁的释放总是先行发生于获取锁;同理,volatile写总是先行发生于volatile读。

synchronized

synchronized是内部锁(也叫重量级锁,实际上1.6后它做过优化,没那么重量级了),是Java最重要的同步机制之一。

虽然synchronized可以保证对象和代码段的线程安全,但仅通过synchonized还不足以控制拥有复杂逻辑的线程交互,为了实现多线程交互,还需要和object的wait()和notify()两个方法联合使用。

synchronized(obj) {while(<?>) {obj.wait()// 收到通知后继续执行}
}

synchronzied配合wait()、notify()是并发编程的基本技能之一。

synchronized关键字的并发安全性承诺:

  1. 临界区互斥执行。
  2. 锁的释放先行发生于锁的获取的内存语义。

synchronized是如何做到互斥和保证先行发生关系的

Java中每个对象都可以作为锁(对象的锁)。普通同步方法,锁是当前实例对象;静态同步方法,锁是当前类的Class对象;同步方法块,锁是synchronized括号里配置的对象。这些实例对象、Class对象、配置的对象在锁范畴内叫Monitor对象。 JVM基于进入和退出Monitor对象来实现临界区互斥执行和锁的释放先行发生于锁的获取的内存语义。

  • 代码块同步使用monitorenter和monitorexit指令实现。
  • 同步方法是通过检查方法是否标志ACC_SYNCHRONIZED实现。

锁优化

方案1:自旋

首先,分析一下synchronized的性能瓶颈。互斥同步对性能影响最大的是阻塞的实现。线程阻塞和用户态内核态转换带来的性能开销。虚拟机团队注意到在大部分应用,共享数据的锁定状态只会持续很短一段时间,如果在这个很短的共享数据锁定状态去挂起和恢复线程是划不来的,对于多处理器系统,当发现共享资源被锁定后,能否让这个线程稍等一会儿,但不放弃处理器执行时间呢?答案是肯定的,方案可行,前提是共享资源很快会被释放。我们只需要让线程执行一个忙等待(自旋),这就是自旋锁的由来。我们可以通过-XX:+UseSpinning开启自旋锁。

其次,自旋锁不能替代阻塞,自旋锁对处理器有要求(即多处理器),虽然避免了阻塞但会占用CPU执行时间,如果锁定很短效果会很好,但如果锁定很长呢?那是否就白白浪费的处理器执行时间了。因此自旋的等待时间必须有一个限度,如果自旋超过了限定次数仍然没有成功获得锁,就应该使用传统方式挂起线程。在虚拟机默认设置中自旋次数是10次,可通过参数-XX:PreBlockSpin来更改。

最后,不过无论是默认值还是用户指定的自旋次数,对整个Java虚拟机中所有的锁来说都是相同的。在 JDK 6中对自旋锁的优化,引入了自适应的自旋。自适应意味着自旋的时间不再是固定的了,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定的。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也很有可能再次成功,进而允许自旋等待持续相对更长的时间,比如持续100次忙循环。另一方面,如果对于某个锁,自旋很少成功获得过锁,那在以后要获取这个锁时将有可能直接省略掉自旋过程,以避免浪费处理器资源。有了自适应自旋,随着程序运行时间的增长及性能监控信息的不断完善,虚拟机对程序锁的状况预测就会越来越精准,虚拟机就会变得越来越“聪明”了。

a.Java对象头和MarkWord设计

首先,synchronized用的锁是存在Java对象头里的,对象如果是数组类型,则JVM用3个字宽(一个字宽32bit)存储对象头;如果对象是普通类型,则使用2字宽。Java对象头组成如下所示:

43371fbbd90c736fc33bc2c0beb46043.png

下面,我们看下Mark Word的字段组成情况。

首先,在无锁状态下,32bit Mark Word划分如下:

a4aaa0cb0cd87fdd46edf9bc012f0567.png

在运行期,Mark Word存储的数据会随着标志位的变化而变化,如下所示:

ed88125599691f59ec230c871268d573.png

以上是32位虚拟机的Mark Word字段分配。

注:无锁状态的Mark Word当有线程获取Monitor对象时,会拷贝到栈帧的锁记录中。

b.锁的升级过程(锁膨胀)

从以上分析我们知道锁有4种状态:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。这几种状态随着竞争情况而逐渐升级。锁只能升级而不能降级,只所以这样做是为了提高获取锁和释放锁的效率。

偏向锁

Hotspot作者发现,大多数情况下,锁不仅不存在多线程竞争,而且只是由同一线程获取,为了让线程获取锁的代价更低而引入了偏向锁。

  1. 当一个线程访问同步块时,首先会判断锁的状态,如果是01,且允许偏向,则进入第2步,否则进入第4步。
  2. 获取锁,在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时,不需要进行CAS操作来加锁和解锁,只需要简单测试一下对象头Mark Word是否存储了指向当前线程的偏向锁。如果偏向锁没有设置,且此时锁标志位为01,则尝试CAS设置偏向锁。
  3. 偏向锁的撤销使用了一种等待竞争出现才释放锁的机制,当有其它线程竞争锁时,持有偏向锁的线程需要等待全局安全点(没有正在执行的字节码这个点),它会暂停拥有偏向锁的线程,判断线程的活跃状态,如果不活跃,则设置为无锁状态,否则升级。

轻量级锁

轻量级对性能的提升的前提条件是同步块可以很快执行完成,且系统是多核,这样只需要忙等轮询很小一段时间就可以获取锁,避免线程阻塞导致的开销。

  1. 在代码即将进入同步块的时候,如果此同步对象没有被锁定(锁标志位为“01”状态),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝(官方为这份拷贝加了一个Displaced前缀,即Displaced Mark Word)。
  2. 虚拟机将使用CAS操作尝试把对象的Mark Word更新为指向Lock Record的指针。如果这个更新动作成功了,即代表该线程拥有了这个对象的锁,并且对象Mark Word的锁标志位(Mark Word的最后两个比特)将转变为“00”,表示此对象处于轻量级锁定状态。
  3. 如果这个更新操作失败了,那就意味着至少存在一条线程与当前线程竞争获取该对象的锁。虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是,说明当前线程已经拥有了这个对象的锁,那直接进入同步块继续执行就可以了,否则就说明这个锁对象已经被其他线程抢占了,空转轮询一段时间锁要膨胀为重量级锁,锁标志的状态值变为“10”,跳转到7。

重量级锁

  1. 此时Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也必须进入阻塞状态。

锁膨胀到重量级锁后,可能导致线程阻塞,而线程阻塞时需要通过操作系统指令完成的,这种系统调用会导致程序用户态内核态的切换,消耗系统资源。

偏向锁、轻量级锁的状态转化及对象Mark Word的关系如下所示。

e68416a4313b32d5af8ed1fd33e2e6e2.png

锁的整体膨胀过程如下图所示:

ddee16b680f5078ad444b1887b98b4d5.png

偏向锁、轻量级、重量级锁优缺点分析

e2910986bb9a352f3e57ea11a824a2a7.png

final

final的安全承诺:

  1. final对象只在初始化构建时进行赋值,实例化成功后不允许改变其值,从根本上避免了并发写入带来的线程安全问题。
  2. 读一个对象的final域之前,一定会先这个对象的引用,如果引入对象不为null,则final域一定被初始化了,

怎么做到的:

  1. JMM禁止编译器把final域的写重排序到构造函数之外,实现方法是在final域写之后,构造函数return前插入一个StoreStore屏障。
  2. 读对象final域之前插入LoadLoad屏障,保证读对象final域之前,一定会先读对象本身。

3.JUC并发防范机制

ReentrantLock

RetrantLock提供了比synchronized更强大的功能,更好的灵活性。它可以响应中断、支持超时时间设置、支持公平和非公平策略。

lock.tryLock(5, TimeUtil.SECONDS);
lock.lockInterruptibly();

ReadWriteLock

读写锁可以有效减少读写并发时的锁竞争,进而减少线程阻塞提高响应时间。

Condition

Condition用于协调多线程的复杂协作,常与Lock配合使用,通过lock.newCondition()可以生成与Lock绑定的Condition实例。

Semaphore

信号量为多线程协作提供了更加强大的控制方法。信号量是对锁的扩展,无论是内部锁synchronized还是重入锁ReentrantLock,一次仅允许一个线程访问资源,而信号量则可以指定多个线程同时访问资源。

构造方法如下:

public Semaphore(int permits) {}
public Semaphore(int permits, boolean fair) {}

主要方法:

public void acquire() throws InterruptedException {}
public void acquireUninterruptibly() {}
public boolean tryAcquire() {}
public boolean tryAcquire(long timeout, TimeUtil unit) throews InterruptedException {} 
public void release() {}

CountDownLatch

CountDownLatch允许一个或多个线程等待其他线程完成操作。一个线程调用countDown方法happen-before另外一个线程调用await方法。API如下

CountDownLatch latch = new CountDownLath(2);
latch.countDown(); 
latch.await();

CyclicBarrier

循环屏障可以做的事是让一组线程到达一个屏障(也叫同步点)时被阻塞,直到最后一个现线程到达屏障才会打开。CyclicBarrier可用于多线程计算数据,最后合并计算结果的场景。CyclicBarrier API如下:

CyclicBarrier barrier = new CyclicBarrier(4, this);
barrier.await(); 

ThreadLocal

ThreadLocal提供的并发防范机制有别于以上在数据共享常见下通过加锁来达到并发控制,防范线程非安全情况出现(即保证线程安全)。ThreadLocal为每个线程提供变量的独立副本,从而从根本上杜绝了数据共享,线程之间根本就不会相互干扰,也就不会有线程安全问题。

4.线程安全集合

  1. ConcurrentHashMap是线程安全且高效的HashMap。
  2. BlockingQueue常用语生产者消费者场景、是线程安全的Queue。

线程安全集合并不在本次讨论范围。

总结

本文较全面的讨论了Java并发控制机制,在JVM层面通过volatile保证了内存的可见性和volatile写/读的先行发生关系。通过synchronized保证了多线程并发时对临界区的互斥访问以及锁的释放先行发生于锁的获取内存语义,为了提供并发性能,本文重点分析了内部锁的膨胀过程。通过final关键字保证了构造函数的调用先行发生于final域的读取并保证了final域的不可变性。除了JVM层面通过JMM定义的先行发生顺序外,JUC也提供了并发防范工具,包括:RetrantLock、ReentrantReadWriteLock、Condition、Semaphore、CountDownLatch、CyclicBarrier以及ThreadLocal。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/505303.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

水晶底是什么材质_都是红酒杯,水晶的和玻璃的有什么区别?

除了看酒杯的形状外&#xff0c;材质也是我们选酒杯不可忽视的一环。酒杯材质可简单分为玻璃酒杯和水晶酒杯。那么&#xff0c;水晶酒杯和玻璃酒杯到底有何不同&#xff1f;普通玻璃杯二氧化硅是主要成分&#xff0c;价格低廉&#xff0c;容易清洗普通玻璃酒杯材质较厚&#xf…

计算机网络 --- 网络层

主要任务是把分组从源端传到目的端&#xff0c;为分组交换网上的不同主机提供通信服务。网络层的传输单位是数据报。&#xff08;分组是把数据报进行切割形成&#xff09; 网络层主要功能 路由选择与分组转发异构网络互联拥塞控制 如果所有结点都来不及接收分组&#xff0…

mac 串口调试工具_MACamp;串口调试

上一篇文章讲了Mac下进行stm32开发wlzz&#xff1a;Mac下stm32开发(clion)​zhuanlan.zhihu.com不可避免的遇到了串口调试的问题,发现mac下进行串口调试还真的不太容易。下了不少网上的软件,发现都不是特别好用,有很多已经不支持现在的macos系统版本了。于是在CLion下搜索了一下…

golang atomic load 性能_设计模式之Golang单例模式

今天给大家讲下什么是单例模式&#xff0c;以及在Go语言中如何用正确的姿势实现它。其实单例模式是一种在平时开发中经常用到的软件设计模式。在设计模式结构中&#xff0c;其核心是只包含一个被称为单例的特殊类。通过单例模式可以确保系统中一个类只有一个实例&#xff0c;且…

计算机网络 --- 传输层

传输层概述 传输层是只有主机才有的层次&#xff08;路由器没有&#xff09;&#xff0c;为应用层提供通信服务&#xff0c;使用网络层的服务 传输层的功能 传输层提供进程与进程之间的逻辑通信&#xff08;网络层提供主机之间的逻辑通信&#xff09;。复用和分用传输层对收…

手机相机里面的m_荣耀V30 PRO详细评测:Matrix Camera相机矩阵开启5G视频时代

【IT168 评测】随着手机摄像头的配置越来越高&#xff0c;手机上已经能实现不俗的拍照效果&#xff0c;甚至超越了数码相机(DC)成为了人们最常用的拍照工具。进入今年的下半年&#xff0c;随着5G的商用&#xff0c;手机厂商对于手机的摄像头功能优化已经不局限于拍照&#xff0…

计算机网络 --- 传输层UDP协议

UDP只在IP数据报服务之上增加了很少功能&#xff0c;即复用分用和差错检测功能。 UDP的主要特点 UDP是无连接的&#xff0c;减少开销和发送数据之前的时延。UDP使用最大努力交付&#xff0c;即不保证可靠交付。UDP是面向报文的&#xff0c;适合一次性传输少量数据的网络应用。…

怎么调整字段长短_【芝士蛋糕怎么总烤不熟呢?】

烘焙群一位同学发来图片问&#xff0c;不是说看蛋糕有没有烤熟&#xff0c;要用牙签检测吗&#xff1f;可我这个芝士蛋糕&#xff0c;怎么扎都会带出面糊&#xff0c;烤不熟怎么办&#xff1f;看到这个被扎成刺猬的舒芙蕾芝士&#xff0c;原谅Windy 不厚道的笑了。也是Windy 疏…

计算机网络 --- 传输层TCP协议

TCP协议的特点 TCP是面向连接&#xff08;虚连接&#xff09;的传输层协议。每一条TCP连接只能有两个端点&#xff0c;每一条TCP连接只能是点对点的。TCP提供可靠交付的服务&#xff0c;无差错、不丢失、不重复、按序到达。可靠有序&#xff0c;不丢不重。TCP提供全双工通信。…

java 委托_java 能不能自己写一个类叫 java.lang.System/String 正确答案

来自&#xff1a;一汪清水 | 责编&#xff1a;乐乐链接&#xff1a;blog.csdn.net/tang9140/article/details/42738433正文 最近学习了下java类加载相关的知识。然后看到网上有一道面试题是能不能自己写个类叫java.lang.System&#xff1f;网上提供的答案&#xff1a;通常不可…

中奖人js滚动效果_js使用transition效果实现无缝滚动

作者&#xff1a;李大雷出自&#xff1a;SegmentFault 思否原文&#xff1a;segmentfault.com/a/1190000023945464前言无缝轮播一直是面试的热门题目&#xff0c;而大部分答案都是复制第一张到最后。诚然&#xff0c;这种方法是非常标准&#xff0c;那么有没有另类一点的方法呢…

计算机网络 --- 应用层

应用层概述 应用层对应用程序的通信提供服务。 应用层协议定义&#xff1a; 应用进程交换的报文类型&#xff0c;请求还是响应各种报文类型的语法&#xff0c;如报文中的各个字段及其详细描述字段的语义&#xff0c;即包含在字段中的信息的含义进程何时&#xff0c;如何发送…

opencv机器学习线性回归_Python机器学习之?线性回归入门(二)

线性回归原理介绍文章目录机器学习分类线性回归原理线性回归定义线性回归背后矩阵运算机器学习分类机器学习的主要任务便是聚焦于两个问题&#xff1a;分类和回归分类相信大家都不会陌生&#xff0c;生活中会见到很多的应用&#xff0c;比如垃圾邮件识别、信用卡发放等等&#…

php 邮件发送是html 没样式_使用python发送邮件

发送邮件这个功能非常实用&#xff0c;比如你写了一个爬虫&#xff0c;可以通过邮件来接收程序异常或者任务完成的通知&#xff0c;再比如你搭了一个网站&#xff0c;别人只需留下他的邮箱&#xff0c;你就可以自动给他发送邮件。总之我觉得邮件通知非常适合那些处理批量、大量…

easypoi设置黑色边框_迷人的G-SHOCK MTG-B1000XBD,碳纤维与黑色金属的魅力

美国最大的奢侈腕表盛会,第五届纽约WatchTime于2019年10月25日至26日在曼哈顿中城的Gotham Hall举行&#xff0c;共有37个参展品牌展出了最新的腕表款式&#xff0c;卡西欧&#xff08;Casio&#xff09;在G-SHOCK高级系列MT-G中展示了最新作品“ MTG-B1000XBD”和“ MTG-B1000…

c语言 单词变复数_【热点】浅谈 :怎样学好C语言?

是新朋友吗&#xff1f;记得先点蓝字关注我哦&#xff5e;本文共 1900 字&#xff0c;预计阅读时间&#xff1a; 7 分钟。最近有好多朋友和我抱怨说C语言真的太难学了&#xff0c;你有没有什么好的意见&#xff1f;嗯&#xff0c;的确如此&#xff0c;我上个学期学C语言的时候也…

安川机器人报错代码_今日 IPO|对标库卡机器人的先惠技术上市 近八成收入靠上汽...

面对全球金融体系的重构&#xff0c;与中国资本市场的变革。「晚点」希望从小处入手&#xff0c;以每日 IPO 公司的快速报道&#xff0c;记录这个时代的变化。通过「晚点早知道」&#xff0c;我们希望可以让读者只需要花几分钟时间&#xff0c;就可以了解到当天上市公司里&…

htmlplay前端编辑器下载_2019年最好用的代码编辑器推荐

对于经常需要编写代码的程序员来说&#xff0c;拥有一款自己的编辑器是非常重要的事情&#xff0c;一款好用的代码编辑器往往能够让代码的编辑更加流畅&#xff0c;今天我们为大家带来最流行的代码编辑器Sublime TextSublime Text是一款快速轻量可定制的代码编辑器&#xff0c;…

geoserver发布瓦片_Geoserver2.15.1配置自带GeoWebCache 插件发布ArcGIS Server瓦片

之前写过一篇关于 Geoserver2.8.5 版本的部署配置发布 ArcGIS Server 瓦片点击查看&#xff0c;那是下载 Geoserver2.8.5 源码编译&#xff0c;重新打包 jar 来部署配置思路的&#xff0c;版本也比较旧。最近我想升级 Geoserver 版本&#xff0c;所以从官网下载最新版本 Geoser…