您的位置:首页 > 教程文章 > 编程开发

线程安全与锁优化Java并发编程实战

:0 :2021-01-29 15:47:11

线程安全与锁优化Java并发编程实战

1. 互斥同步
同步是指多个线程在并发访问共享数据时,保证共享数据在同一时刻只被同一条线程使用。
互斥是实现同步的一种手段
在java里面,最基本的互斥同步手段就是synchronized关键字,这是一种块结构的同步语法
synchronized
synchronized关键字在Javac编译后,会在同步块前后分别形成monitorenter和monitorexit这两个字节码指令,这两个字节码指令都需要一个确定reference类型的参数来指明要锁定和解锁的对象。
根据《java虚拟机规范》的要求:在执行monitorenter指令时,首先要去尝试获取对象的锁。如果这个对象没有被锁定或当前线程已经持有了那个对象的锁,就把锁的计数器的值加1,而在执行monitorexit指令的时候就把锁的计数器减一。一旦计数器的值为0,锁随即就被释放了。一旦计数器的值为0,锁随即就被释放了。(说明是可重入锁)
锁升级过程
在 JDK 6 中虚拟机团队对synchronized进行了重要改进,优化了其性能引入了 偏向锁、轻量级锁、适应性自旋、锁消除、锁粗化等实现
总体来说锁升级如下:无锁——>偏向锁——>轻量级锁——>重量级锁
偏向锁
顾名思义,它的意思是这个锁会偏向于第一个获得它的线程,如果该锁没有被其他线程获取,则持有偏向锁的线程将永远不需要再进行同步。它的目的是为了消除无竞争条件下的同步。
偏向锁在JDK 6及以后的JVM里是默认启用的。可以通过JVM参数关闭偏向锁:-XX:-UseBiasedLocking=false,关闭之后程序默认会进入轻量级锁状态。
当线程访问同步块并获取锁时处理流程如下:
1. 检查 mark word 的线程 id 。
2. 如果为空则设置 CAS 替换当前线程 id。如果替换成功则获取锁成功,如果失败则撤销偏向锁。
3. 如果不为空则检查 线程 id为是否为本线程。如果是则获取锁成功,如果失败则撤销偏向锁。
轻量级锁
多个线程竞争偏向锁导致偏向锁升级为轻量级锁
加锁流程如下:
1. JVM 在当前线程的栈帧中创建 Lock Reocrd,并将对象头中的 Mark Word 复制到 Lock Reocrd 中。(Displaced Mark Word)
2. 线程尝试使用 CAS 将对象头中的 Mark Word 替换为指向 Lock Reocrd 的指针。如果成功则获得锁,如果失败则先检查对象的 Mark Word 是否指向当前线程的栈帧如果是则说明已经获取锁,否则说明其它线程竞争锁则膨胀为重量级锁。
解锁:
1. 使用 CAS 操作将 Mark Word 还原
2. 如果第 1 步执行成功则释放完成
3. 如果第 1 步执行失败则膨胀为重量级锁。
重量级锁
若当前只有一个等待线程,则该线程通过自旋进行等待。但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁升级为重量级锁。
升级为重量级锁时,锁标志的状态值变为“10”,此时Mark Word中存储的是指向重量级锁的指针,此时等待锁的线程都会进入阻塞状态。
自旋锁
自旋是一种获取锁的机制并不是一个锁状态。在膨胀为重量级锁的过程中或重入时会多次尝试自旋获取锁以避免线程唤醒的开销,但是它会占用 CPU 的时间因此如果同步代码块执行时间很短自旋等待的效果就很好,反之则浪费了 CPU 资源。默认情况下自旋次数是 10 次用户可以使用参数 -XX : PreBlockSpin 来更改。
在同步互斥中,无论共享数据是否存在竞争,它都会进行加锁,必要会引起额外的开销。随着硬件指令集的发展,就有了另外一种选择
非阻塞同步
基于冲突检测的乐观兵法策略,通俗的讲,不管风险,先进行操作。如果没有其他线程争用共享数据,操作成功;如果共享的数据确实被争用,再进行其他补偿操作,最常见的补偿操作便是不断的重试,知道没有出现竞争的共享数据。
比较并交换(Compare-and-Swap,即CAS)
CAS是上述“硬件指令集的”中的一条指令(通过硬件支持多个操作步骤的原子性)
java.util.concurrent包中的原子类就是通过CAS来实现了乐观锁。
CAS算法涉及到三个操作数:
需要读写的内存值 V。
进行比较的值 A。
要写入的新值 B。
当且仅当 V 的值等于 A 时,CAS通过原子方式用新值B来更新V的值(“比较+更新”整体是一个原子操作),否则不会执行任何操作。一般情况下,“更新”是一个不断重试的操作。

图像处理工具包ImagXpress用户指南,使用触摸功能
讲解Excel中16种图表类型的含义