商城网站建设预算要多少钱,广东网络建设有限公司,一个企业做网站推广的优势,第二课强登陆网站新型智库建设的意见目录一、CAS与原子类1.1 CAS1.2 乐观锁与悲观锁1.3 原子操作类二、 synchronized优化2.1 轻量级锁2.2 轻量级锁-无竞争2.3 轻量级锁-锁膨胀2.4 重量级锁-自旋2.5 偏向锁2.6 synchronized-其他优化一、CAS与原子类
1.1 CAS
CAS#xff08;一种不断尝试#xff09;即Compare …
目录一、CAS与原子类1.1 CAS1.2 乐观锁与悲观锁1.3 原子操作类二、 synchronized优化2.1 轻量级锁2.2 轻量级锁-无竞争2.3 轻量级锁-锁膨胀2.4 重量级锁-自旋2.5 偏向锁2.6 synchronized-其他优化一、CAS与原子类
1.1 CAS
CAS一种不断尝试即Compare and Swap它体现的一种乐观锁的思想比如多个线程要对一个共享的整形变量执行1操作 CompareAndSwap尝试把结果赋值给前面的共享变量赋值的同时将旧值与共享变量当前的值作比较【怕写入结果时有其他线程已经将共享变量的值修改】
获取共享变量时为了保证该变量的可见性需要使用volatile修饰。结合CAS和volatile可以实现无锁并发使用于竞争不激烈、多核CPU的场景下。
● 因为没有使用synchronized所以线程不会陷入阻塞CAS需要不断重试进而利用CPU时间这是效率提升的因素之一 ● 但如果因为竞争激烈可以想到重连必然频繁发生反而效率会受影响
CAS底层依赖于一个Unsafe类来直接调用操作系统底层的CAS指令下面是直接使用Unsafe对象进行线程安全保护的例子
1.2 乐观锁与悲观锁
● CAS是基于乐观锁的思想最乐观的估计不怕别的线程来修改共享变量就算修改了也没关系花费点时间再重试而已
● synchronized是基于悲观锁的思想最悲观的估计时刻得防着其他线程来修改共享变量只要线程上了锁就别想修改完全解开了锁其他线程才有机会
1.3 原子操作类
jucjava.util.concurrentJava并发工具包中提供了原子操作类可以提供线程安全的操作例如AtomicInteger原子整数类保护整数操作自增、自减的一些线程安全操作、AtomicBoolean等它们底层就是采用CAS技术volatile来实现的。
import java.util.concurrent.atomic.AtomicInteger;
public class Test {// 创建原子整数对象给一个初始值0private static AtomicInteger inew AtomicInteger(0);public static void main(String[] args) throws InterruptedException {Thread t1new Thread(()-{for (int j 0; j 5000 ; j) {i.getAndIncrement(); // 获取并且自增 i// i.incrementAndGet(); // 自增并且获取 i}});Thread t2 new Thread(()- {for (int j 0; j 5000; j) {i.getAndDecrement(); // 获取并且自减 i--}});t1.start();t2.start();// 让主线程等待,一直等待其他线程不再活动为止 //t1.join();t2.join();System.out.println(i);}
}结果并不会出现整数、负数的情况利用无锁并发的方式来保证原子整数类中整数信息的线程安全
二、 synchronized优化
Java HotSPot虚拟机中每个对象都有对象头包括class指针和Mark Word。Mark Word平时储存这个对象的哈希值、分代年龄当加锁时这些信息就根据情况被替换为标记位、线程锁记录指针、重量级锁指针、线程ID等内容
2.1 轻量级锁
如果一个对象虽然有多线程访问但多线程访问的时间是错开的(也就是没有竞争)那么可以使用轻量级锁来优化。这就好比:
学生(线程A)用课本占座轻量级锁好比用课本占座座位好比CPU的使用权上了半节课出门了(CPU时间到)回来一看, 发现课本没变,说明没有竞争,继续上他的课。
如果这期间有其它学生(线程B) 来了会告知(线程A)有并发访问线程A随即升级为重量级锁,进入重量级锁的流程。【锁膨胀轻量级锁升级为重量级锁】
而重量级锁就不是用课本占座那么简单了可以想象线程A走之前,把座位用一个铁栅栏围起来
假设有两个方法同步块利用同一个对象加锁 static Object objnew Object();public static void method1(){synchronized (obj){// 同步块 Amethod2();}}private static void method2() {synchronized (obj){// 同步块B }}2.2 轻量级锁-无竞争
每个线程的栈帧中都会包含一个锁记录的结构内部可以存储锁定对象的Mark Word(8个字节)
栈帧中锁记录的结构的作用对一个对象加锁后需将原来旧的信息暂存到栈帧的锁记录结构中将来解锁时再将暂存的Mark Word旧的信息恢复回去
对以上代码的加锁、解锁流程
2.3 轻量级锁-锁膨胀
如果在尝试加轻量级锁的过程中CAS操作无法成功这是一种情况就是有其他线程为此对象加上了轻量级锁有竞争这时需要进行锁膨胀将轻量级锁升级为重量级锁。 static Object obj new Object();public static void method1 () {synchronized (obj) {// 同步块}}2.4 重量级锁-自旋
重量级锁竞争的时候还可以使用自选来进行优化如果当前线程自旋成功即这个时候持锁线程已经退出了同步块释放了锁这时当前线程就可以避免阻塞。
在Java 6之后自旋锁是自适应的比如对象刚刚的一次自旋操作成功过那么认为这次自旋成功的可能性会高就多自旋几次反之就少自旋甚至不自旋总之比较智能。
● 自旋会占用CPU时间单核CPU自旋就是浪费单核CPU无闲置CPU毫无意义多核CPU自旋才会发挥优势
● 好比等红灯时汽车是不是熄火不熄火相当于自旋等待时间短了划算熄火相当于阻塞等待时间长了不划算
● Java 7之后不能再控制是否开启自旋功能
①: 自旋重试成功的情况【当线程2也想加锁(获取monitor)时发现不能加锁时并不会马上陷入阻塞】
②: 自旋重试失败的情况【线程2不可能无限制的自旋重试若线程1执行的时间较长在重试过程中同步代码块还未执行完重试多次后会放弃重试然后自己陷入阻塞】
2.5 偏向锁
假设有两个方法同步块利用同一个对象加锁
锁重入线程1对对象加锁由于其调用方法2方法2还是本线程对同一对象加锁但每次进行锁重入时还是会用CAS再做一次修改Mark Word为线程1的锁记录地址这样的操作
如何优化以上存在的问题JDK 6中引入偏向锁的概念做进一步优化
轻量级锁在没有竞争时(就自己这个线程) , 每次重入仍然需要执行CAS操作。Java 6中引入了偏向锁来做进一步优化:只有第一次使用CAS将线程ID设置到对象的Mark Word头之后发现这个线程ID是自己的就表示没有竞争不用重新CAS.
■ 撤销偏向需要将持锁线程升级为轻量级锁,这个过程中所有线程需要暂停(STW)
■ 访问对象的hashCode也会撤销偏向锁无锁状态下对象头中存储的为对象的hashCode加上偏向锁后对象头中存的为线程IDhashCode被放到加锁线程中
■ 如果对象虽然被多个线程访问,但没有竞争,这时偏向了线程TI的对象仍有机会重新偏向T2, (FL)中J重置对象的Thread ID
■ 撤销偏向和重偏向都是批量进行的以类为单位
■ 如果撤销偏向到达某个阈值整个类的所有对象都会变为不可偏向的
■ 可以主动使用-XX:-UseBiasedL ocking禁用偏向锁
可以参考此篇论文: https://www.oracle .com technetwork/java biasedlocking -oopsla2006-wp- 149958.pdf
2.6 synchronized-其他优化
① 减少上锁时间 同步代码块中尽量短
② 减少锁的粒度 将一个锁拆分为多个锁提高并发度
例如 ■ ConcurrentHashMap
■ LongAdder进行计数的原子操作类分为base和cells 两部分。没有并发争用的时候或者是cells数组正在初始化的时候会使用CAS来累加值到base,有并发争用会初始化cells数组数组有多少个cell,就允许有多少线程并行修改最后将数组中每个cell累加再加上base就是最终的值
■ LinkedBlockingQueue 入队和出队使用不同的锁相对于I inkedBlockingArray只有一个锁效率要高
③ 锁粗化 多次循环进入同步块不如同步块内多次循环 另外JVM可能会做如下优化把多次append的加锁操作粗化为一次因为都是对同一个对象加锁没必要重入多次
// StringBuffer类是线程安全的【里面的append()方法会有synchronized来进行同步保护】 new StringBuffer().append(a).append(b).append(c); ④ 锁消除 当对象没有机会被外界用到时就会将对象上的锁消除掉
JVM会进行代码的逃逸分析例如某个加锁对象是方法内局部变量不会被其他线程访问到这时候就会被即时编译器忽略掉所有同步操作。
⑤ 读写分离
CopyOnWriteArrayList(读)读取原始数组的内容 CopyOnWriteSet(写)复制一份在一个新数组上进行 因此读操作不同同步只需要对写操作进行同步即可
参考 https://wiki.openjdk. java.net/display/HotSpot/Synchronization
http://luojinping.com/2015/07/09/javai)iít1c/
https://www.infoq.cn/article/java-se- 16-synchronized
https://www.jianshu.com/p/9932047a89be
https://www.cnblogs.com/sheeva/p/6366782.html
https://stackoverflow.com/questions/463 12817/does-java-ever-rebias-an-individual-lock