您的位置:首页 > 其它

volatile、synchronized、锁的升级、原子操作、总线锁、缓存锁、CAS

2018-01-10 17:33 1016 查看
参考:《Java并发编程的艺术》

并发编程的挑战

1、为了让程序更快,起更多线程。但是线程的上下文切换,会有额外的开销,影响运行速度。
2、死锁问题。
3、资源限制(带宽、硬盘、CPU)。
所以多使用JDK并发包提供的工具。

Java并发机制底层实现原理

Java中的大部分容器和框架都依赖于volatile和原子操作的实现原理。

volatile

volatile(易变的,不稳定的)

是轻量级的synchronized,在多处理器开发中,保证共享变量的可见性。它不会引起上下文的切换。
被其修饰的变量,在写操作时,生成的汇编代码,会多一条Lock前缀的指令。为了提高处理速度,处理器不直接和内存进行通信,而是先把内存数据读到内部缓存。操作完不知何时写会内存。Lock前缀指令作用(也是对“缓存一致性协议”的实现):
        1、将当前处理器缓存行的数据写回到系统内存。
        2、这个写操作会使得其它CPU里,缓存了这个内存地址的数据无效。

volatile内存语义

        1、当写变量时,会把本地内存中的值刷新到主内存中。
        2、当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程会从主内存中读取共享变量。volatile与锁具有相同的内存语义。

volatile特征

        1、线程可见性。一个行程修改了volatile修饰的变量,其他线程可以立刻看到修改的值。
        2、原子性。单个volatile读写具有原子性,但是对于多个volatile操作或者类似于volatile++这种复合操作不具有原子性。

        3、禁止指令重排序。
(重排序知识参考:三 Java内存模型(JMM)

concurrent包的实现

    1、声明共享变量为volatile
    2、使用CAS的原子条件更新来实现线程间的同步
    3、配合volatile、CAS读写内存语义实现内存间通信
这些就是concurrent包实现的基石。

synchronized

重量级锁。JDK1.6之后,synchronized和ReentrantLock性能基本持平。1.6中为了减少获得与释放锁的额外消耗,引入了偏向锁、轻量级锁、锁升级、锁的存储结构。synchronized实现同步的基础:java中每一个对象都可以作为锁。
有普通同步方法(锁是实例对象)、静态同步方法(Class对象)、同步块。是基于进入和退出Monitor实现的。
同步方法是隐式的,即无需通过字节码指令来控制,调用方法时,会检查ACC_SYNCHRONIZED标志是否被设置,如果是,执行线程会要求先成功持有Monitor。同步代码块,是通过monitorenter和monitorexit实现的。每个monitorenter指令必须执行monitorexit指令,所以编译器会自动产生一条athrow指令来做异常处理。
《synchronized锁代码演示》



锁的升级

偏向锁 --->  轻量级锁  ---> 重量级锁
1、偏向锁:等到竞争出现才会释放锁。
2、轻量级锁:CAS加锁解锁。
        加锁的过程:代码进入同步块的时候,如果同步对象没有被锁定,那么JVM会在当前线程的栈帧中建立一个名为锁记录的空间(Lock Record),用来存锁对象Mark Word的拷贝(这个拷贝叫Displaced Mark Word),然后用CAS操作把对象的Mark Word,更新为指向Lock Record。
        如果有其它线程来竞争锁,那么锁会膨胀成重量级锁。锁只会升级。此时其他线程获取锁时,都会被阻塞,持有锁的线程释放后,唤醒这些线程。
        锁消除:JVM编译时,会去除不可能存在共享资源竞争的锁。

原子操作的实现原理

原子操作:不可被中断的一个或一系列操作。

CPU处理器如何实现原子操作

首先处理器会自动保证基本的内存操作的原子性。保证从内存中读取或写入一个字节是原子性的。意思是当一个处理器从系统内存读取一个字节时,其他处理器不能访问这个字节的内存地址。但是复杂的内存操作,处理器不能自动保证其原子性,比如跨总线宽度、跨多个缓存行、跨页表。但是提供缓存加锁和总线加锁。

总线锁

数据通过总线在处理器和内存之间传递。每次数据传递是通过一系列的步骤步骤来完成,叫做总线事务,在一个事务期间,总线会禁止其他处理器和IO设备执行内存读写。总线仲裁(Bus Arbitration),会确保所有处理器都能公平访问内存。
当一个处理器在总线上输出 LOCK#信号,其他处理器的请求将被阻塞。那么该处理器就可以独占共享内存。

缓存锁

锁操作回写内存时,修改内部内存地址,缓存一致性会使得其他CPU的缓存失效。

Java中如何实现原子操作

通过锁和循环CAS实现。

CAS

JVM的CAS操作利用的是处理器提供的CMPXCHG指令实现的。

CAS三大问题
1、ABA问题。
解决思路,使用版本号。AtomicStampedReference的compareAndSet比较引用、标志。
2、循环时间长开销大。
如果JVM支持CPU的pause。第一可以延迟流水线执行指令,减少CPU消耗。第二避免在退出循环的时候,因为内存顺序冲突而引起CPU流水线被清空,提高CPU执行效率。
3、只能保证一个共享变量的原子操作。
AtomicReference类来保证对象之间的原子性,把多个变量放在一个对象里来进行CAS操作。

synchronized和CAS比较

synchronized是互斥锁,会有线程阻塞和唤醒带来额外的消耗,称作阻塞同步,也可称作悲观锁。CAS是非阻塞同步,称作乐观锁。先进行操作,操作完成再判断是否成功,是否有并发问题,有则进行失败补偿,没有就算操作成功。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: