Java并发-volatile,synchronized关键字,happends-before原则,Lock锁和CAS操作

Java并发编程番外篇(二)happens-before关系 在Java 并发编程(三)同步中,提到了内存一致性错误,而避免内存一致性错误的关键就是了解happens-before关系。那么什么是happens-before关系呢?如何判断两个操作是否存在happends-before关系呢?本文将来介绍这两个方面。1. happens-before关系Wikipedia, Happened-before是这样定义的: In computer scienc 阅读详情

volatile

volatile是轻量级的synchronized,它保证了共享变量的可见性(当一个线程修改一个共享变量时,另一个线程可以读到这个修改的值),但不保证原子性。如果volatile变量修饰符使用的恰当,比synchronize的使用和执行成本更低。

synchronized

synchronized是重量级锁,他不仅保证了可见性,还保证了原子性。

利用synchronized实现同步的具体表现为3种形式:
1.对于同步方法,锁是当前实例对象。
2.对于静态同步方法,锁是当前类的Class对象。
3.对于同步方法块,锁是Synchronized括号里配置的对象。

Sychronized支持重入锁, Sychronized A里面可以调用Sychronized B方法,重入锁另外一种情形,子类锁调用父类锁。
被sychronized锁定的代码块越少同步效率就越高.

使用Synchronized进行同步,首先需要获取对象的监视器monitor,只有获取监视器后才可以继续执行,否则只能等待.这个操作是互斥的,只有一个线程可以获取.

锁的重入性:即在同一锁程中,线程不需要再次获取同一把锁。Synchronized先天具有重入性。每个对象拥有一个计数器,当线程获取该对象锁后,计数器就会加一,释放锁后就会将计数器减一。

public class SynchronizedDemo {
    public static void main(String[] args) {
        //执行同步代码块之前要先执行monitorenter指令 下面第4行
        synchronized (SynchronizedDemo.class) {
        }//退出的时候执行monitorexit指令 下面第6行

        //而该方法为静态方法,该方法的对象仍锁着该类对象,就需要执行monitorenter指令,
        //因为上面已经获取了该对象的监视器,所以不必再次获取
        method();
    }

    private static void method() {
    }
    /**
     *  public static void main(java.lang.String[]);
     *     descriptor: ([Ljava/lang/String;)V
     *     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     *     Code:
     *       stack=2, locals=3, args_size=1
     *          0: ldc           #2     // class threadTest/SynchronizedDemo
     *          2: dup
     *          3: astore_1
     *          4: monitorenter      
     *          5: aload_1
     *          6: monitorexit
     *          7: goto          15
     *         10: astore_2
     *         11: aload_1
     *         12: monitorexit
     *         13: aload_2
     *         14: athrow
     *         15: invokestatic  #3     // Method method:()V
     *         18: return
     */
}

访问过程:

—Monitor.Enter—>Monitor(监视器)—Monitor.Enter成功—>对象(Objject)—Monitor.Exit–>

如果线程A先Enter,获取了Object的监视器,后面来的线程都会进入同步状态(BLOCKED),进入同步队列,当Object的监视器占有者释放后,同步队列中的线程就可以有机会获得监视器.

Synchronized关键字会让没有得到锁资源的线程进入BLOCKED状态,而后在争夺到锁资源后恢复为RUNNABLE状态,这个过程中涉及到操作系统用户模式和内核模式的转换,代价比较高

其他:
当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。
程序运行中如果碰到异常,那么锁默认会被释放.所以在高并发处理的过程中,有异常要考虑清楚
wait/notify/notifyall 必须作用于同一个对象才能达到目的,调用wait会进入等待并且释放锁,调用notify则不会。
调用wait前必须进行Synchronized操作,要么会抛异常 IllegalMonitorStateException(非法监控状态异常)
在并发量较高的情况下,适用Synchronized.

Concurrent.locks.Lock

JUC(Java.utils.Concurrent)包下的类
方法:

void lock() 获取该锁
void unlock() 释放该锁

Lock的实现类Concurrent.locks.ReentrantLock

构造器
ReentrantLock() 构建一个可以被用来保护临界区的重入锁
ReentrantLock(boolean fair) 构建一个带有公平策略的锁

具体加锁步骤

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
//1.加锁
lock.lock();
//业务逻辑
     //2.条件不满足等待
     condition.await();
     //3.通知其他
     condition.signalAll();
//4.释放锁
lock.unlock();

Synchronized和Lock的区别
1.Synchronized是java关键字.Lock是java类
2.Synchronized无法判断锁的状态.Lock可以判断是否获取了锁
3.Synchronized会自动释放锁.Lock必须手动释放,如果不释放会造成 死锁!
4.Synchronized 线程1获取,线程2会一直阻塞.Lock锁不会一直等待下去
5.Synchronized可重入锁,不可以中断的,非公平.Lock可重入锁,可以中断,默认非公平(可设为公平)
6.Synchronized适合少量代码同步.Lock适合大量同步代码!

happends-before原则

cup的运行速度特别快,但读取主存对于cpu来说就有点慢,在读取主存的过程中cpu一直处于空闲状态(没有数据可以运行),对资源造成了极大的浪费.慢慢的cpu演变成了多级cache结构,cpu在读cache的速度比读主存快了n倍。

当线程在执行时,会保存临界资源的副本到私有work memory中,这个memory在cache中,修改这个临界资源会更新work memory但并不一定立刻刷到主存中

而且编译器为了提高指令执行效率,是可以对指令重排序的,重排序后指令的执行顺序不一样,有可能线程2读取某个变量时,线程1还未进行写入操作。

i = 1; //线程A执行
j = i ; //线程B执行
假定线程A的操作(i = 1)happens-before线程B的操作(j = i)。 那么可以确定线程B执行后j = 1 一定成立。 如果他们不存在happens-before原则,那么j = 1 不一定成立

即使代码是先执行j=1,然后执行j=i,也不一定j=1,主要看是否符合happens-before.

happens-before

如果操作1 happens-before 操作2,那么第操作1的执行结果将对操作2可见,而且操作1的执行顺序排在第操作2之前。
两个操作之间存在happens-before关系,并不意味着一定要按照happens-before原则制定的顺序来执行。
如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序并不非法。

如何判断是否为 happens-before?

程序次序规则: 在一个单独的线程中,按照程序代码的执行流顺序,(时间上)先执行的操作happen—before(时间上)后执行的操作(同一个线程中前面的所有写操作对后面的操作可见)

管理锁定规则: 一个unlock操作happen—before后面(时间上的先后顺序)对同一个锁的lock操作。
(如果线程1解锁了monitor a,接着线程2锁定了a,那么,线程1解锁a之前的写操作都对线程2可见(线程1和线程2可以是同一个线程))

volatile变量规则:对一个volatile变量的写操作happen—before后面(时间上)对该变量的读操作。
(如果线程1写入了volatile变量v(临界资源),接着线程2读取了v,那么,线程1写入v及之前的写操作都对线程2可见(线程1和线程2可以是同一个线程))

线程启动规则: Thread.start()方法happen—before调用用start的线程前的每一个操作。
(假定线程A在执行过程中,通过执行ThreadB.start()来启动线程B,那么线程A对共享变量的修改在接下来线程B开始执行前对线程B可见。
注意:线程B启动之后,线程A在对变量修改线程B未必可见。)

线程终止规则: 线程的所有操作都happen—before对此线程的终止检测,可以通过Thread.join()方法结束、Thread.isAlive()的返回值等手段检测到线程已经终止执行。
(线程t1写入的所有变量,在任意其它线程t2调用t1.join(),或者t1.isAlive() 成功返回后,都对t2可见。)

线程中断规则: 对线程interrupt()的调用 happen—before 发生于被中断线程的代码检测到中断时事件的发生。
(线程t1写入的所有变量,调用Thread.interrupt(),被打断的线程t2,可以看到t1的全部操作)

对象终结规则: 一个对象的初始化完成(构造函数执行结束)happen—before它的finalize()方法的开始。
(对象调用finalize()方法时,对象初始化完成的任意操作,同步到全部主存同步到全部cache。)

传递性: 如果操作A happen—before操作B,操作B happen—before操作C,那么可以得出A happen—before操作C。
(A h-b B , B h-b C 那么可以得到 A h-b C)

在程序运行过程中,所有的变更会先在寄存器或本地cache中完成,然后才会被拷贝到主存以跨越内存栅栏(本地或工作内存到主存之间的拷贝动作),此种跨越序列或顺序称为happens-before。
注:happens-before本质是顺序,重点是跨越内存栅栏 通常情况下,写操作必须要happens-before读操作,即写线程需要在所有读线程跨越内存栅栏之前完成自己的跨越动作,其所做的变更才能对其他线程可见

CAS(Compare And Swap)

Java对并发控制的最常见方法就是锁,可以实现线程安全,锁虽然有效,但采用的是一种悲观的策略。它假设每一次对临界区资源的访问都会发生冲突,当有一个线程访问资源,其他线程就必须等待,所以锁是会阻塞线程执行的。

CAS:比较和交换(对资源的访问没有冲突,所有的线程执行不需要等待)
参数: V  A  B 
V : 表示要读写的主存位置
A : 表示旧的预期值
B : 表示新值

CAS指令执行时,当且仅当V的值等于预期值A时,才会将V的值改为B,如果V的值与A不同,说明其他线程作了更新,那么当前线程什么都不做,最后CAS返回的是V的真实值.
在多线程的情况下,多个线程用CAS操作一个变量时,只有一个会成功并更新新值,其余线程都会失败,但不会被挂起,而是循环重试.
1.主存中存储着值为10的变量
2.线程1想操作该值,将其+1. 此时 预期值A=10 新值B为11
3.在线程1提交数据之前,线程2先行一步进行+1操作,将V变为了11 ( v = 11 )
4.线程1提交,将A=10与V=11进行比较结果不相等 操作失败
5.线程1重新获取主存V的值 11, 并重新计算想要修改的值.此时预期值A=11 , 新值B=12,这个重新尝试的过程被称为自旋.
6.线程1提交,将A=11和V=11进行比较,结果相等,操作成功
7.将V的值更新为V=12

CAS的缺点:
1.CPU开销大
在并发量高的情况下,若线程反复尝试更新某一个变量,却一直失败,循环会给CPU带来很大压力
2.不能保证代码块的原子性
CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性 的更新,就不得不使用Synchronized了。
因为它本身就只是一个锁住总线的原子交换操作啊。两个CAS操作之间并不能保证没有重入现象。

3.ABA问题
线程1准备用CAS修改变量值A,在此之前,其它线程将变量的值由A替换为B,又由B替换为A,然后线程1执行CAS时发 现变量的值仍然为A,所以CAS成功。但实际上这时的现场已经和最初不同了.

解决ABA最简单的方案就是给值加一个修改版本号,每次值变化,都会修改它版本号,CAS操作时都对比此版本号。

Altium Designer20-柔性PCB绘制技巧 Altium Designer2020-柔性PCB绘制技巧 注:教程所用PCB随意找的,并没有完全绘制完,大家学习PCB折弯方法即可。 1.打开PCB,点击视图->板子规划模式1,如下图所示 2.点击设计->定义分割线,用两条分割线绘出要折弯的区域 3.点击设计->定义弯曲线(弯曲线呈现黄色) 4.用快捷键d,k进入layer manage stack(层叠管理器),在f... 阅读详情

相关推荐

一文读懂GPU通信互联技术:GPUDirect、NVLink与RDMA

GPUDirect是NVIDIA开发的一项技术,旨在实现GPU与其他设备(如网络接口卡NIC和存储设备)之间的直接通信和数据传输,而无需CPU的参与。传统上,数据在GPU和另一个设备之间传输时,必须通过CPU,这导致潜在的性能瓶颈和延迟增加。GPUDirect技术则通过绕过CPU,直接访问和传输数据,显著提高系统性能。NVLink是NVIDIA开发的一种高速、高带宽的互连技术,用于连接多个GPU之间或GPU与其他设备(如CPU、内存等)之间的通信。

异构算力老群群(在读985计算机博士生)的技术博客 7777

happens-before的法则

                                       happens-before的法则      当一个变量被多个线程读取,且至少被一个xian线程写入式,如果duxi读写操作并未依照happens-before排序,就会产生数据竞争。 happens-before的法则包括:   程序次序法则:线程中的每个动作A都happends-before于该线程中的每一个...

stay hungry ! stay foolish! 730

Ollama-OCR实战:5分钟搞定PDF文档转Markdown(附qwen2.5vl模型配置)

本文详细介绍了如何利用Ollama本地部署多模态大模型qwen2.5vl,结合OCR技术,实现PDF文档到Markdown的端到端智能转换。通过解析传统OCR的局限与AI方案的优势,并提供从环境搭建、提示词工程到处理复杂表格的完整实战指南,帮助用户高效构建自动化文档处理流水线,彻底重塑文档处理工作流。

yog99的博客 606

【JUC】十八、happens-before先行发生原则

如果线程A的操作(x=5)先行发生于线程B的操作(y=x),或者说这两个事件存在先行发生原则,那y=5一定成立,反之则不一定,因为x=5的改变可能还没从A线程的工作内存刷回主内存,线程就暂时挂起了。现在有线程A和线程B,线程A(在时间上先)调用了setValue方法,然后线程B调用同一对象的getValue方法,那线程B的返回值是?:对一个volatile变量的写操作先行发生于后面对这个变量的读操作,前面的写对后面的读是可见的,这里的”后面“同样是指时间上的先后。一定是先发烧了,温度计才能检测到体温变了。

llg___的博客 1660

Linux系统下Materials Studio 2020安装避坑指南:从依赖包到许可证配置全流程

本文提供了一份在Linux系统上安装Materials Studio 2020的深度避坑指南。详细解析了从系统依赖包检查、安装流程、许可证配置到Gateway服务部署的全过程,并分享了与Slurm作业调度系统集成的实战经验,旨在帮助用户高效、稳定地完成部署。

weixin_29313547的博客 387

关于LIO-SAM编译时出现错误 /usr/bin/ld: 找不到 -lBoost::timer

关于LIO-SAM编译时出现错误: 下载LIO-SAM代码 进入LIO-SAM文件夹,通过以下代码编译: mkdir build cd build cmake .. make 报错: /usr/bin/ld: 找不到 -lBoost::timer collect2: error: ld returned 1 exit status CMakeFiles/lio_sam_imuPreintegration.dir/build.make:759: recipe for target 'devel/lib/li

wwtx的博客 3648

happens-before 规则

1.happens-before 定义 happens-before的概念最初由Leslie Lamport在其一篇影响深远的论文(《Time,Clocks and the Ordering of Events in a Distributed System》)中提出,有兴趣的可以google一下。JSR-133使用happens-before的概念来指定两个操作之间的执行顺序。由于这两个操...

当时若不登高望、谁信东流海洋深 1万+

带有验证码的爆破(包含Burp suite工具爆破)

关于含有验证码爆破的一些总结!

weixin_43960066的博客 8698

什么是happens-before原则?

前言 本文隶属于专栏《100个问题搞定Java虚拟机》,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢! 本专栏目录结构和参考文献请见100个问题搞定Java虚拟机 正文 happens-before 关系是用来描述两个操作的内存可见性的。如果操作 X happens-before 操作 Y,那么 X 的结果对于 Y 可见。 Java 内存模型底层是通过内存屏障(memory barrier)来禁止重排序的。 线程内的 happens-before 在同一个线程中,字节码的先

Shockang的博客 1万+

Open3D点云算法与点云深度学习案例汇总(长期更新)

Open3D-python算法专栏文章汇总,长期更新算法原理与实战案例。

纵马踏花向自由 7473

《UDS协议从入门到精通》系列——到底什么是DTC?

到底什么是DTC?看这里,深度图解DTC格式,详述DTC状态位、快照信息和扩展信息!!

车载系统攻城狮 1万+

详细讲解一下JMM中的happends-before

是 Java 内存模型(JMM, Java Memory Model)中的一个核心概念,用于定义多线程环境中操作之间的可见性和顺序性。

大户影视的博客 961

PX4从放弃到精通(三):使用qt编译PX4

1 下载px4源码 2在Firmware所在目录下执行 mkdir Firmware-build cd Firmware-build cmake …/Firmware -G “CodeBlocks - Unix Makefiles” -DCONFIG=nuttx_px4_fmu-v5_default 第三步最后的px4_fmu-v5_default根据自己的情况修改 执行完后如图 打开QT 点击打开工程 选择源码的CmakeLists.txt文件 选择imported Kit 打开项目那一栏,构建目

超维空间科技的博客 1万+

Java并发编程的艺术(底层原理与内存模型总结)

底层原理 (1)volatile:volatile修饰的共享变量进行写操作时后多出lock指令代码,lock前缀指令在多核处理器下会发生两件事: 将处理器缓存行的数据写回到系统内存; 这个写操作会使在其他CPU里缓存了该内存地址的数据无效。 (2)synchronized实现原理: 三种形式:普通方法:锁为当前实例对象;静态同步方法:锁为当前类的Class对象;对于同步方法块:锁为Sync...

ting 237

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

happends-before原则

程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作; 锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作; volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作; 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C; 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作; 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中.

小树じ的博客 479

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

happens-before原则

如果ActionA和ActionB属于同一个线程,那么就说明ActionA happens-before ActionB。   如果ActionA是unlock操作,而ActionB是lock操作,那么ActionA happens-before ActionB。      如果A是对volatile变量的写操作,ActionB是对同一个变量的读操作,那么ActionA happ...

心中有侠 838

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

happen-before 原则

内存屏障:使内存数据对CPU核可见的技术被称为内存屏障或内存栅栏。 happen-before 原则:两个操作之间具有happens-before 关系,就必须要求前一个操作的结果对后一个操作可见。 常见的happens-before规则: 程序顺序规则:一个线程中的每个操作,happens- before 于该线程中的任意后续操作。 监视器锁规则:对一个监视器锁的解锁,happe

做一个会思考的技术人 4624

happen-before原则

happen-before是JMM最核心的概念,所以在了解happen-before原则之前,首先需要了解java的内存模型。 JMM内存模型      java内存模型是共享内存的并发模型,线程之间主要通过读-写共享变量来完成隐式通信。java中的共享变量是存储在内存中的,多个线程由其工作内存,其工作方式是将共享内存中的变量拿出来放在工作内存,操作完成后,...

怪咖小青年的博客 3万+

通俗易懂讲解happens-before原则

在接下来的叙述里我首先会说明happens-before规则是干什么用的,然后用一个简单的小程序说明happens-before规则 一、happens-before规则 我们编写的程序都要经过优化后(编译器和处理器会对我们的程序进行优化以提高运行效率)才会被运行,优化分为很多种,其中有一种优化叫做重排序,重排序需要遵守happens-before规则,不能说你想怎么排就怎么排,如果那样岂不是

Mr孔先森 1万+

synchronized和volatile和happens-before

1. volatile 可见性 java编程语音允许线程访问共享变量,为了确保共享变量的准确性和一致,其他线程能够立即看得到修改的值。 执行原理: volatile保证了变量的可见性。被volatile修饰的变量。标志着线程的本地内存无效。当一个线程修改了变量之后会立刻更新到主内存中,其他线程读取共享变量时,会直接从主内存中读取。 原子性 执行一个操作或多个操作,要么全部执行并且执行过程不会被任何...

我爱吃果冻的博客 665

读书笔记:从happens-before原则说起

看Java内存模型(JMM, Java Memory Model)时,总有一个困惑。关于线程、主存(main memory)、工作内存(working memory),我都能找到实际映射的硬件:线程可能对应着一个内核线程,主存对应着内存,而工作内存则涵盖了写缓冲区、缓存(cache)、寄存器等一系列为了提高数据存取效率的暂存区域。但是,一提到happens-before原则,就让人有点“丈二和尚摸不

zdxiq000的专栏 5373

happens-before规则解析

happens-before规则是java memory model(JMM)最核心的概念。所以,理解happens-before规则对于理解JMM是至关重要的。java重排序之所以会出现happens-before规则,是因为java中存在重排序的情况。重排序:是指编译器和处理器为了优化程序性能而对指令序列进行重排序的一种手段。JMM对编译器和处理器的要求是,只要你保证了程序在单线程和正确同步的多

abyjun的博客 1588

为了实现 happends-before ordering原则, java及jdk提供的工具

为了实现   happends-before ordering原则, java及jdk提供的工具: a, synchronized关键字 b, volatile关键字 c, final变量 d, java.util.concurrent.locks包(since jdk 1.5) e, java.util.concurrent.atmoic包(since jdk 1.5) ...

水滴石穿 186

java多线程之happens-before

1、背景问题 在讲happens-before之前,先引入一个例子: 假定我们有已经被初始化的变量: int counter = 0; 这个 counter 变量被两个线程所共有,也就是说线程A和线程B都可以获取或者更改counter的值。 这里我们假设线程A要增加counter的值: counter++; 然后,线程B打印counter的值 System.out.println(c

Yuwen's Hero 2402

Java并发编程的相关知识(2)--volatile和synchronized和final关键字,happens-before

java并发编程底层原理 多线程编程中synchronized和valatile都扮演重要的角色,volatile是轻量级的synchronized。volatilevolatile的使用优化synchronized 多线程编程中synchronized和valatile都扮演重要的角色,volatile是轻量级的synchronized。 volatile volatile保证了共享变量的“可见性”。 可见性当一个线程修改一个共享变量时,另外一个线程能读这个修改的值。 缓存一致性在缓存中的共享变量保持数据

qq_40996062的博客 189

什么是happens-before

什么是happens-before? 从JDK5开始,提出了happens-before的概念,通过这个概念来阐述操作之间的内存可见性。如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在happens-before关系。这里提到的两个操作既可以是在一个线程之内,也可以是在不同线程之间。 happens-before原则定义如下: 如果一个操作happen...

会飞的冰箱的博客 5485
上一篇: JAVA实现赫夫曼树
下一篇: 线程池的相关知识点(三大方法,七大参数,四种拒绝策略)
Java-fly
博客等级 码龄9年 13粉丝 31原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值