volatile&⽆锁&锁的优化策略

volatile

● 正常情况下,如果我们不使⽤volatile,那么每条线程都会有⾃⼰的缓存,当全局变量被修改时,其 他线程可能并不会被通知到。
● volatile并不能真正的保证线程安全。它只能确保⼀个线程修改了数据后,其他线程能够看到这个改动。
● 如下所示,即使ready在主线程中被赋值为true,依然⽆法在⼦线程中获得最新修改的值,从⽽结束while循环:

● 当我们使⽤volatile去申明变量时,就等于告诉了虚拟机,这个变量极有可能会被某些程序或者线程修改。为了确保这个变量被修改后,应⽤程序范围内的所有线程都能够"看到"这个改动,虚拟机就 必须采⽤⼀些特殊的⼿段,保证这个变量的可⻅性等特点。如下所示:

● volatile并不能代替锁,它也⽆法保证⼀些复合操作的原⼦性。⽐如通过volatile是⽆法保证i++的原⼦性操作的。

ThreadGroup

● 在⼀个系统中,如果线程数量很多,⽽且功能分配⽐较明确,就可以将相同功能的线程放置在⼀个 线程组⾥。如下图所示:

● 建议⼤家在创建线程和线程组的时候,给它们取⼀个好听的名字。

Daemon

● 守护线程是⼀种特殊的线程,它会在后台默默地完成⼀些系统性的服务。当⼀个Java应⽤内,只有守护线程时,Java虚拟机就⾃然退出了。我们可以通过调⽤setDaemon(true)的⽅式,设置线程为 守护线程。这⾥需要注意, 设置守护线程必须在线程调⽤start()⽅法之前设置
setDaemon(true) ,否则会报IllegalThreadStateException。

synchronized

● 情况 1 : 不使⽤ synchronized修饰的对象,乱序输出。

● 情况 2 :synchronized 修饰代码块 ------ 对象


● 情况 3 :synchronized 修饰代码块 ------ 类

● 情况 4 :synchronized修饰 ⽅法


● 情况 5 :直接作⽤于 静态⽅法 ------相当于对当前类加锁,进⼊同步代码前要获得当前 类的锁 。

锁的优化策略

● 偏向锁

● 如果⼀个线程获得了锁,那么锁就进⼊偏向模式。当这个线程再次请求锁时,⽆须在做任何同步
操作。这样就节省了⼤量有关锁申请的操作,从⽽提⾼了程序性能。

● 轻量级锁

● 如果偏向锁失败,虚拟机并不会⽴即挂起线程.它还会使⽤⼀种称为轻量级锁的优化⼿段。
● 轻量级锁的操作也很轻便,它只是 简单地将对象头部作为指针,指向持有锁的线程堆栈的内
部,来判断⼀个线程是否持有对象锁 。
● 如果线程获得轻量级锁成功,则可以顺利进⼊临界区。如果轻量级锁加锁失败,则表示其他线程
抢先争夺到了锁,那么当前线程的锁请求就会膨胀为重量级锁。

● ⾃旋锁

● 锁膨胀后,虚拟机为了避免线程真实地在操作系统层⾯挂起,虚拟机还会再做最后的努⼒------⾃
旋锁。
● 系统会进⾏⼀次赌注:它会假设在不久的将来,线程可以得到这把锁。因此,虚拟机会 让当前线
程做⼏个空循环 (这也是⾃旋的含义),在经过若⼲次循环后,如果可以得到锁,那么就顺利进
⼊临界区。如果还不能获得锁,才会真实地将线程在操作系统层⾯挂起。

● 锁消除

● 锁消除是⼀种更彻底的锁优化。Java虚拟机在JIT编译时,通过对运⾏上下⽂的扫描,去除不可能存在共享资源竞争的锁。通过锁消除,可以节省毫⽆意义的请求锁时间。
● ⽐如,我们有可能在⼀个不可能存在并发竞争的场合使⽤Vector,⽽Vector内部使⽤了
synchronized请求锁。例如如下代码:


【注】在上述代码中的Vector,由于变量vector只在createStrings()函数中使⽤,因此,它只是⼀个
单纯的局部变量。局部变量是在线程栈上分配的,属于线程私有的数据,因此不可能被其他线程访
问。所以,如果虚拟机检测到这种情况,就 会将这些⽆⽤的锁操作去除掉 。
● 锁消除涉及的⼀项关键技术为 逃逸分析 。所谓逃逸分析就是观察某⼀个变量是否会逃出某⼀个作
⽤域。

⽆锁

8.1> CAS

● 在Unsafe类⾥,包含着CAS的操作函数。它采⽤⽆锁的乐观策略,由于其⾮阻塞性,它对死锁问题天⽣免疫,并且,线程间的相互影响也远远⽐基于锁的⽅式要⼩得多。更为重要的是,使⽤⽆锁的 ⽅式完全没有锁竞争带来的系统开销,也没有线程间频繁调度带来的开销。因此,它要⽐基于锁的 ⽅式拥有更优越的性能。

● CAS算法的过程
● 它包含四个参数CAS(object,offset,expectdValue,newValue),分别是:

object :待更新的对象
offset :待更新变量的offset偏移量
expectdValue :表示预期值,
newValue :表示新值
● 那么,仅当object+offset定位到的值等于expectdValue值时,才会将其值设为newValue,如果
与expectdValue值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。最后,CAS
返回当前V的真实值。
● 在硬件层⾯,⼤部分的现代处理器都已经⽀持原⼦化的CAS指令。

8.2> AtomicInteger

● 通过使⽤AtomicInteger,我们可以得益于CAS等CPU指令,保证Integer操作的原⼦性。如下所
示:

● 我们来看⼀下AtomicInteger的incrementAndGet()⽅法是怎么实现的(基于JDK 1 . 8 )


【注】由于针对 incrementAndGet()⽅法,是先执⾏increment,然后再执⾏get,所以获取的
是increment之后的值;⽽unsafe.getAndAddInt先返回get到的值,然后内部在执⾏addInt,所
以在整体的⽅法这⾥需要"+ 1 ";
● 此处使⽤了CAS的⽅式,来进⾏原⼦的+ 1 操作

8.3> Unsafe

● Unsafe封装了⼀些类似指针的操作。因为指针是不安全的,如果指针指错了位置,或者计算指针偏移量时出错,结果可能是灾难性的,你很有可能覆盖别⼈放⼊内存中的数据,导致系统崩溃 。
● Unsafe类还提供了⼀些常⽤的⽅法,如下所示:

// 获得给定对象偏移量上的 int 值
public native int getInt ( Object o, long offset);
// 设置给定对象偏移量上的 int 值
public native void putInt ( Object o, long offset, int x);
// 获得字段在对象中的偏移量
public native long objectFieldOffset (Field f);
// 设置给定对象的 int 值,使⽤ volatile 语义
public native void putIntVolatile ( Object o, long offset, int x);
// 获得给定对象的 int 值,使⽤ volatile 语义
public native int getIntVolatile ( Object o, long offset);
// 和 putIntVolatile() ⼀样,但是它要求被操作字段就是 volatile 类型的
public native void putOrderedInt ( Object o, long offset, int x);
● 但是,如果我们要使⽤Unsafe类,需要调⽤getUnsafe()函数,如果这个类的ClassLoader不为
null,就直接抛出异常,拒绝⼯作。因为我们知道,只有系统类加载器才会返回null。因此,这也使
得 我们⾃⼰的应⽤程序⽆法直接使⽤Unsafe类 。它是⼀个JDK内部使⽤的专属类。

8.4> AtomicReference

● AtomicReference和AtomicInteger⾮常类似,只是AtomicReference对应普通的对象引⽤。如下所 示:

8.5> AtomicStampedReference

● 有⼀点需要注意的是,当你获得对象当前数据后,在准备修改为新值前,对象的值被其他线程连续修改了N次,⽽最终对象的值⼜被恢复为旧值。这样,当前线程就⽆法正确判断这个对象究竟是否 被修改过。如下所示:

● 那么,针对上⾯的问题,我们可以通过使⽤AtomicStampedReference这种带有时间戳的对象引⽤来解决,因为它包含了⼀个stamp参数,类似版本或时间戳的概念,如果想要CAS成功,就必须ref 和stamp都满⾜期待值,如下所示:

8.6> AtomicIntegerArray

● 除了基本数据类型之外,JDK还为我们提供了数组这种复合类型结构。当前可⽤的原⼦数组有
AtomicIntegerArray、AtomicLongArray和AtomicReferenceArray。它们本质都是对数组类型进⾏
封装,使⽤Unsafe类通过CAS的⽅式控制数组在多线程下的安全性。下⾯以AtomicIntegerArray为
例进⾏演示:

8.7> AtomicIntegerFieldUpdater

● 它的作⽤是,让普通变量也能享受原⼦操作,并且在不改动或极少改动原有代码的基础上,让普通的变量也能享受CAS操作带来的线程安全性。
● 根据数据类型不同,这个Updater有三种。分别是AtomicIntegerFieldUpdater、
AtomicLongFieldUpdater、AtomicReferenceFieldUpdater,顾名思义,它们分别可以对int、
long和Object进⾏CAS修改。
● allScore是通过AtomicInteger来进⾏递增计算的;score是通过AtomicIntegerFieldUpdater进⾏封
装执⾏CAS操作的;scoreTemp就是普通的字段通过⾃增计算的。循环了 100 * 1000 次,总计数应
该是 100000 。如下所示:

● 使⽤Updater的注意事项:
● Updater只能修改它可⻅范围内的变量。因为Updater使⽤反射得到这个变量,如果变量不可⻅,
就会出错。⽐如 不能将score声明为private 。
● 为了确保变量被正确的读取,它 必须是volatile类型 的。
● 由于CAS操作会通过对象实例中的偏移量直接进⾏赋值,因此,它 不⽀持static字段 (即:
Unsafe.objectFieldOffset()不⽀持静态变量)

相关推荐
ccut 第一混10 分钟前
c# winform 调用 海康威视工业相机(又全又细又简洁)
开发语言·c#·工业相机·海康威视
振鹏Dong1 小时前
微服务架构及常见微服务技术栈
java·后端
丶小鱼丶1 小时前
二叉树算法之【中序遍历】
java·算法
摇滚侠2 小时前
Oracle 关闭 impdp任务
java
编程爱好者熊浪3 小时前
RedisBloom使用
java
苇柠3 小时前
Spring框架基础(1)
java·后端·spring
yics.3 小时前
数据结构——栈和队列
java·数据结构
架构师沉默3 小时前
我用一个 Postgres 实现一整套后端架构!
java·spring boot·程序人生·架构·tdd
xiucai_cs3 小时前
布隆过滤器原理与Spring Boot实战
java·spring boot·后端·布隆过滤器
向阳花自开4 小时前
Spring Boot 常用注解速查表
java·spring boot·后端