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()不⽀持静态变量)

相关推荐
masa0105 分钟前
JavaScript--JavaScript基础
开发语言·javascript
拓端研究室TRL8 分钟前
Python用TOPSIS熵权法重构粮食系统及期刊指标权重多属性决策MCDM研究|附数据代码...
开发语言·python·重构
一只特立独行的猪6111 小时前
Java面试——集合篇
java·开发语言·面试
大得3692 小时前
go注册中心Eureka,注册到线上和线下,都可以访问
开发语言·eureka·golang
讓丄帝愛伱2 小时前
spring boot启动报错:so that it conforms to the canonical names requirements
java·spring boot·后端
weixin_586062022 小时前
Spring Boot 入门指南
java·spring boot·后端
小珑也要变强3 小时前
队列基础概念
c语言·开发语言·数据结构·物联网
Dola_Pan5 小时前
Linux文件IO(二)-文件操作使用详解
java·linux·服务器
wang_book5 小时前
Gitlab学习(007 gitlab项目操作)
java·运维·git·学习·spring·gitlab
AI原吾6 小时前
掌握Python-uinput:打造你的输入设备控制大师
开发语言·python·apython-uinput