Java 中的锁

讨论锁,首先要知道 是为了解决啥问题产生的.

锁,是为了避免线程间共享资源出错而产生的.

在 Java 中,从 jdk1.0 开始,每个 Java 对象都有一个内部锁.

比如如果一个线程访问了某个 Java 对象中的数据,此时该线程获得该对象的锁,其余线程将无法访问该对象的数据.

这样说不严谨,实际上比如,一个线程访问了一个对象由 synchronized 修饰的方法,线程就获得该对象的锁,那么其它的线程想要访问由 synchronized 修饰的方法就因为获取不到锁而阻塞.但是,其他线程此时是可以访问非同步方法的.因为无需获取锁.

锁分为很多种.有的锁觉得资源不会发生变化,所以允许多个线程获取该资源.但是一个资源可能发生变化,那么就不能让多个线程获取某个资源了,因为这样可能产生一个线程修改了某资源的值,而另一个线程仍使用着某资源之前的值,从而产生问题.(或者另一个线程使用的值也被修改了,同样会出错.)

Java 的锁都是是对象锁.有的地方说当通过锁机制访问静态方法或数据时(方法声明为 synchronized),此时获取的是类锁.通常我们认为静态方法或属性是类的属性,而普通方法与属性,是对象的属性.参考 Java 类加载 ,我们知道,类在 JVM 中也是一个对象的形式存在的.所以所谓的”类锁”也是对象锁,只不过是类加载进 JVM 后的类对象的锁.

若一个方法用 synchronized 声明,则对象的锁保护整个方法.

对象的锁用来管理试图调用方法的线程.

Java 线程通信

上面提到 锁,是为了避免线程间共享资源出错而产生的.

线程共享资源的一种应用,就是 Java 的线程通信.即 共享内存模型 .

这里联系操作系统的 线程通信

而 Java 线程通信则是由 JMM(Java 内存模型) 类控制的.

Java 内存模型 (JMM)

JVM 尝试定义 JMM 来屏蔽各个硬件平台和操作系统的内存访问差异,以实现 Java 程序在各个平台下都能达到一致的内存访问效果.

JMM 规定所有的变量都存放在主存(物理内存),每个线程都有自己的工作内存(高速缓存).

线程对变量的操作必须在工作内存,不能直接对主存进行操作. –> 所以需要把工作内存的变量值同步至主存.

每个线程不能访问其他线程的工作内存.

上面在 Java 中的锁 中提到一个线程共享资源可能出错的场景,联系这里,可以知道,如果两个线程共享一个资源,那么一个线程修改了自己工作内存的资源,然后把该资源同步到主存.此时另一个线程使用的自己工作内存的资源还是之前的值,这里就可能出现问题.

实际上,会有一个不断触碰,判断工作内存的资源与主存是否一致的过程,当发现不一致,会把主存的值更新到工作内存.这样,也会出错.

Java 中的类与关键字

下面的内容有重复,但是为了从不同的方面对比,是必要的…

简单区分一下

synchronized,volatile –> 是线程同步的关键字

Lock –> 一般来说,是 Java 的锁实现

线程同步可能会使用到锁

而 java.lang.concurrent.atomic 包下的原子操作类 没有使用锁 而是使用 CAS 的方式来保证原子性.

synchronized volatile

简单对比

Java 使用的并发机制 依赖于 JVM 的实现和 CPU 的指令.

volatile 的实现是在 cpu 层面,使用了 lock 前缀的指令…

synchronized 的实现是在 JVM 层面,使用了 monitorenter,monitorexit…

具体怎么回事,看下面…

PS : 而 ReenTrantLock 是 JDK 实现的

synchronized

实现原理

在 JVM 规范中可以看到 synchronized 在 JVM 里的实现原理.

JVM 基于进入和退出 Monitor 对象来实现 方法同步代码块同步.

但两者的实现细节不一样…

代码块同步 使用 monitorenter 和 monitorexit 指令实现…

方法同步 使用另一种方式,细节在 JVM 规范未说明.但是方法的同步也可以用上述俩指令实现.

synchronized 使用的锁存在 Java 对象头的 Mark Word 里…

早期版本中,synchronized 属于重量级锁,在操作系统层面的实现用到了 Mutex Lock,线程之间的切换需要从用户态转换到核心态,开销较大.

jdk6 做了优化.

Java 对象头和 Monitor 是实现 synchronized 的基础.

Java 对象头

对象在内存中的布局

  • 对象头
  • 实例数据
  • 对齐填充

具体 看 JVM 类文件结构

简单讲,锁的各种状态存放在 Java 对象头 的 Mark Word 块里.

Monitor

一种同步机制?

JVM 中用 C++ 实现

jdk1.6 为 synchronized 做了优化,引入了 偏向锁,轻量级锁…

什么是 偏向锁,轻量级锁?

锁的升级

  1. 偏向锁
  2. 轻量级锁
  3. 重量级锁

[看下面锁的相关概念]

synchronized 的四种状态

  • 无锁,偏向锁,轻量级锁,重量级锁

锁膨胀的方向 : 无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁

volatile

介绍

使用 volatile 在某些情况下比使用锁更方便.

与 JMM(Java 内存模型) 对比

与 Java 的锁机制联系

如果一个字段被声明为 volatile,Java 线程内存模型确保所有线程看到这个变量的值是一致的.

一个域(类的成员变量,类的静态成员变量)声明为 volatile :

  • 保证了不同线程对这个变量进行操作的可见性.

若一个线程修改了某个变量的值,这个新值对其他线程来说是立即可见的.

  • 禁止进行 指令重排序 .
  1. 能保证可见性 –> 读取的都是最新的值
  2. 不能保证原子性
  3. 一定程度保证有序性

原子性使用 java.lang.concurrent.atomic 包下的原子操作类.该包下的类没有使用锁,而是使用 CAS 的方式来保证原子性.

注意 : 原子操作类, CAS

不过啥是原子性,有序性…呢?

这是并发编程的三个概念 见下文 三个概念

实现

Java 代码编译为 Java 字节码,字节码被类加载器加载到 JVM,JVM 执行字节码,最终需要转化为汇编指令在 CPU 上执行.

volatile 修饰的变量进行 写操作 时,是通过 cpu 级别实现的.

查看汇编指令会发现多出 lock 前缀的指令…

实现了

  • 把当前处理器缓存行的数据写回系统内存.
  • 这个写回内存的操作会使其他 cpu 里的缓存无效.

lock 前缀指令实际上相当于一个内存屏障(也称 内存栅栏).提供三个功能

  1. 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
  2. 它会强制将对缓存的修改操作立即写入主存;
  3. 如果是写操作,它会导致其他CPU中对应的缓存行无效。

与操作系统联系

每个处理器通过 嗅探 在总线上传播的数据来检查自己缓存的值是否过期,当处理器发现自己缓存行对应的内存地址被修改,将会将当前处理器的缓存行设置成无效,当处理器对这个数据进行修改操作时,会重新从系统内存中把数据读到处理器缓存里.

原子操作

jdk1.5 开始 Java 增加了 atomic 包下的原子类

这些类使用 CAS 保证原子性

CAS 是什么?会出现什么问题?

处理器如何实现原子操作…??

  • 总线锁定
  • 缓存锁定

[回头再看看…]

jdk1.5 后, Java 可以在程序中使用 CAS,该操作由 sun.misc.Unsafe 类里的 compareAndSwapInt() 等方法包装提供.虚拟机在内部做了特殊处理,即时编译出来的结果就是一条平台相关的处理器 CAS 指令.

由于 Unsafe 类不是面向用户调用的类(而是面向 jdk 开发者),故只能通过反射或者其它的 Java api 来间接使用.

如 java.util.concurrent.atomic 下的原子类,其中的 compareAndSet() 和 getAndIncrement() 等方法都使用了 Unsafe 类的 CAS 操作.

// java.util.concurrent.atomic.AtomicInteger

		/**
     * Atomically sets the value to the given updated value
     * if the current value {@code ==} the expected value.
     *
     * @param expect the expected value
     * @param update the new value
     * @return {@code true} if successful. False return indicates that
     * the actual value was not equal to the expected value.
     */
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

unsafe.compareAndSwapInt() 是个 本地方法.

Synchronized 与 ReenTrantLock

Synchronized是依赖于JVM实现的,而ReenTrantLock是JDK实现的,有什么区别,说白了就类似于操作系统来控制实现和用户自己敲代码实现的区别。前者的实现是比较难见到的,后者有直接的源码可供阅读。

synchronized

介绍

因为 jdk6 对 synchronized 有更新,特性应当与 jdk5 之前对比来看…

[待续…]

使用

  1. 修饰普通方法 –> 获取对象内置锁
  2. 修饰代码块 –> 获取对象内置锁
  3. 修饰静态方法 –> 获取类(类的字节码文件对象)锁

说明一下 :

static 修饰的成员,也就是静态成员,是类的属性.

普通的方法,成员变量,是类的具体实例,也就是对象的属性.

但是在 JVM 中,类和普通的实例,都是对象.

比如反射

Class cls = Class.forName("Sutudent");// 获取一个类对象
Student stu = (Student)cls.newInstance();// 使用类对象的方法获取一个类的实例

类对象,即类的字节码文件对象.

而每个对象,都有一个内置锁.

事实上,获取的都是对象锁.但是为了区分,把实例的对象锁称为对象锁,把类对象的锁称为类锁.

实现

上面有提到过

在 JVM 规范中可以看到 synchronized 在 JVM 的实现原理.

JVM 基于 进入 和 退出 Monitor 对象来实现 方法 和 代码块 的同步,但两者实现细节不一样.

代码块同步使用 monitorenter 和 monitorexit 指令实现…

方法同步使用另一种方式,JVM 规范未说明.但是方法的同步也可以用上述俩指令实现.

….

monitor

对象头保存信息…

类的字节码信息,格式…

在操作系统使用啥来实现的??

Mutex Lock…

….

锁的状态

锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁(但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级)。JDK 1.6中默认是开启偏向锁和轻量级锁的,我们也可以通过-XX:-UseBiasedLocking来禁用偏向锁。

特点

synchronized 基于 隐式获取锁,这是优势,也是面对其它方式的缺点.

隐式获取锁 的意思 : 无需显式获取锁,释放锁

PS : 这特么不是废话吗

也就是不需要的手动写代码获取,释放…

优点 : 便捷

缺点 : 扩展性低 –> 与 Lock 比较

  1. 方法,代码块执行完毕自动释放锁.
  2. 遇到异常,其持有的锁自动释放 –> JVM 操作.
  • 内置锁的可重入性

与 Lock 的可重入锁 ReentrantLock 对比

支持一个线程对资源的重复加锁.

因为锁的持有者是线程,而非调用.

所以 : 一个线程对一个资源加锁了,下次还能继续进入这个锁.

相当于它能开锁,进入,再加锁. –> 这就是重复加锁(可重入)的意思.

Lock

介绍 A

Lock 是一个 接口,jdk1.5 之后引入.

与 Lock 相关的接口,类 都放在 java.util.locks 包下.

但是值得一提的是,jdk1.5 之后还与 Lock 一起引入了 ReadWriteLock 接口.

下面谈到的 ReetrantLock 是实现了 Lock 的类.

而 ReentrantReadWriteLock 是实现了 ReadWriteLock 接口的类.

很多资料把 Lock,ReetrantLock,ReentrantReadWriteLock 一起谈,搞得像后两者都是 Lock 的实现一样.虽然从概念上讲差不多,但是实际的实现并不是那么想当然.

使用

使用,肯定是使用接口的具体实现类.

  1. 可重入锁 ReetrantLock
  2. 读写锁 ReentrantReadWriteLock

可重入锁,读写锁…都是锁机制的一些概念.而 ReetrantLock 是通过 Lock 对可重入锁的 Java 实现,其他同理…

那锁有哪些概念呢??看下面 锁的相关概念

使用方式,比如

Lock lock = new ReetrantLock();
lock.lock();
try{
  
}finally{
  lock.unlock();
}

介绍 B

我们回头来看 java.util.locks 包.

比较

jdk1.5 添加了 Lock 接口,提供了 Synchronized 相似的同步功能,只是使用需要显式获取,释放锁.

所以可以简单理解为 Lock 是为了解决 Synchronized 的效率问题引入的.

而 ReadWriteLock 是读写分离锁,啥是读写分离锁呢?(也称 读写锁)

读写分离锁 指 同一时刻允许多个线程进行读访问,但是如果有一个写访问线程,那么其它的读访问,写访问都被阻塞.

既然单独说 ReadWriteLock 是读写锁,那么 ReentrantLock 就不是读写锁啦.ReentrantLock 是一种 排他锁 .顾名思义,无论是读还是写,都只允许一个线程访问.

读写锁 可以有效的帮助减少锁的竞争,提升系统性能。

接口 Lock
public interface Lock {
	void lock();
	void lockInterruptibly() throws InterruptedException;
	boolean tryLock();
	boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
	void unlock();
	Condition newCondition();
}
实现 ReentrantLock
public class ReentrantLock implements Lock, java.io.Serializable {
  //...
}
接口 ReadWriteLock
public interface ReadWriteLock {
    /**
     * Returns the lock used for reading.
     *
     * @return the lock used for reading
     */
    Lock readLock();

    /**
     * Returns the lock used for writing.
     *
     * @return the lock used for writing
     */
    Lock writeLock();
}
实现 ReentrantReadWriteLock
public class ReentrantReadWriteLock
        implements ReadWriteLock, java.io.Serializable {
  //...
}

ReentrantLock

//具体谈谈…

看源码,实现提到了 AbstractQueuedSynchronizer,即 AQS…

单独了解一下吧.

ReentrantReadWriteLock

具体谈谈…

锁的类型

参考下面锁的相关概念.

Synchronized 是啥子锁

  1. 悲观锁
  2. 非公平锁
  3. 不可中断锁.
  4. 可重入锁 —. 是隐式的,获取了锁,直接再用就是了.

阻塞算法

ReentrantLock

  1. 独享锁
  2. 互斥锁
  3. 默认情况下是非公平锁,但是可以设置为公平锁.
  4. 可重入锁 –> 调用了 lock() 方法获取到锁的线程,可以再次调用 lock() 获取锁而不被阻塞.
  5. 不可中断锁.
  6. Lock 是可中断锁.使用 ockInterruptibly().

ReadWriteLock

  1. 读锁是共享锁,其写锁是独享锁。
  2. 读写锁
  3. 默认情况下是非公平锁,但是可以设置为公平锁.

java.util.concurrent.atomic 包下的原子类

  1. 乐观锁
  2. 建立在CAS之上的
  3. 非阻塞算法

锁的相关概念

参考这里

从 锁的状态,锁的特性,锁的设计 有各式各样的锁…

乐观锁/悲观锁

从并发同步的角度来看,锁 在宏观上分为两种,乐观锁,悲观锁.

synchronized 是悲观锁。

在 JDK1.5 中新增 java.util.concurrent (J.U.C) 就是建立在 CAS 之上的.相对于 synchronized 这种阻塞算法,CAS 是非阻塞算法的一种常见实现.所以 J.U.C 在性能上有了很大的提升.

juc 是乐观锁

再看一下 atomic 类

悲观锁

假设最坏情况,每次都会修改数据,故没有线程访问就上锁.

“操作前先上锁.”

用于

传统关系型数据库,行锁,表锁,读锁,写锁…

Java, Synchronized,ReentrantLock

乐观锁

取数据,不上锁

更新数据,会检查有没有被别人更新.

用于

多读类型应用 –> 提高吞吐量

如 数据库 –> write_condition 机制

Java –> java.util.concurrent.atomic 包下的原子类 –> 用 CAS 实现

实现方式

版本号机制

就是加个版本号,每次修改操作,版本号相应变动.(如自增)

别的线程执行修改时,发现版本号不一致,就修改失败…

CAS 算法

CAS 算法可以单独提出来

即 compare and swap (比较与交换)

是一种 无锁算法 –> 不使用 锁 实现多线程之间的变量同步.

在没有线程被阻塞的情况下实现变量的同步 也称 –> 非阻塞同步(Non-blocking Synchronization)

涉及到三个操作数

  • 需要读写的内存值 V
  • 进行比较的值 A
  • 拟写入的新值 B

我认为 V 的值应该为 A,如果是,那么将 V 的值更新为 B,否则不修改并告诉 V 的值实际为多少.

int compare_and_swap (int* reg, int oldval, int newval) 
{
  ATOMIC();
  int old_reg_val = *reg;
  if (old_reg_val == oldval) 
     *reg = newval;
  END_ATOMIC();
  return old_reg_val;
}

一般情况是是一个 自旋操作 即不断地重试…

缺点

  • ABA 问题

如果一个值原来为 A 变化为 B 又变化为 A

看起来没变过 实际变了

解决办法 : 用版本号

  • 循环时间长开销大

增加CPU的开销

  • 只能保证一个共享变量的原子操作

为什么?

硬件实现的

因为它本身就只是一个锁住总线的原子交换操作

两个 CAS 操作之间并不能保证没有重入现象

只能保证一个共享变量的原子操作。当对多个共享变量操作时,CAS就无法保证操作的原子性,这时就可以用锁,或者把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象的原子性,你可以把多个变量放在一个对象里来进行CAS操作。

自旋锁

广泛应用的底层同步机制

  • 许多情况下,共享资源的锁定状态持续时间较短,切换线程不值得
  • 通过让线程执行忙循环等待锁的释放,不让出 CPU.
  • 不过若锁被其它线程长时间占用,会带来许多性能上的开销.
  • 因此自旋应当设置一个超时判断.

当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。

获取锁的线程一直处于活跃状态,但是并没有执行任何有效的任务,使用这种锁会造成busy-waiting

java.util.concurrent.atomic 包下的原子类就是自旋锁.CAS 就是自旋锁的一种是实现方式.

独享锁/共享锁

独享锁:是指该锁一次只能被一个线程所持有。

共享锁:是指该锁可被多个线程所持有。

Java ReentrantLock 是独享锁.

对于 Lock 的另一个实现类 ReadWriteLock,其读锁是共享锁,其写锁是独享锁.

读锁的共享锁可保证并发读是非常高效的,读写,写读,写写的过程是互斥的.

独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享.

互斥锁/读写锁

上面讲的独享锁/共享锁就是一种广义的说法,互斥锁/读写锁就是具体的实现。

  • 互斥锁在Java中的具体实现就是 ReentrantLock
  • 读写锁在Java中的具体实现就是 ReadWriteLock

读写锁将对一个资源的访问分成了2个锁,一个读锁,一个写锁.

同一时刻允许多个线程进行读访问.

但是如果有一个写访问线程,那么其它的读访问,写访问都被阻塞.

公平锁/非公平锁

公平锁

公平锁即尽量以请求锁的顺序来获取锁.

比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该锁,这种就是公平锁.

非公平锁即无法保证锁的获取是按照请求锁的顺序进行的.这样就可能导致某个或者一些线程永远获取不到锁.

在 Java 中,synchronized 就是非公平锁,它无法保证等待的线程获取锁的顺序.

而对于 ReentrantLock 和 ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁.

区分

  • 公平锁:是指多个线程按照申请锁的顺序来获取锁。
  • 非公平锁:是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象。

公平锁即尽量以请求锁的顺序来获取锁.

比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该锁,这种就是公平锁.

非公平锁即无法保证锁的获取是按照请求锁的顺序进行的.这样就可能导致某个或者一些线程永远获取不到锁.

在 Java 中,synchronized 就是非公平锁,它无法保证等待的线程获取锁的顺序.

而对于 ReentrantLock 和 ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁.

可重入锁

具备可重入性就是可重入锁.

可重入性上面也提到了,即 支持一个线程对资源的重复加锁.

可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁

按上面提到的,synchronized 和 ReentrantLock 都是可重入锁.

不过 synchronized 是隐式的,获取了锁,直接再用就是了.

但 ReentrantLock 是 : 调用了 lock() 方法获取到锁的线程,可以再次调用 lock() 获取锁而不被阻塞.

读写锁

读写锁将对一个资源的访问分成了2个锁,一个读锁,一个写锁.

同一时刻允许多个线程进行读访问.

但是如果有一个写访问线程,那么其它的读访问,写访问都被阻塞.

可中断锁

顾名思义,就是可以中断的锁.

Java 中,synchronized 就是不可中断锁.

Lock 是可中断锁.使用 ockInterruptibly().

偏向锁/轻量级锁/重量级锁

指锁的状态

针对 Synchronized

分段锁

一种锁的设计.

把一块数据分成多个段,每个段用一把锁维护.

jdk 1.8 前 ConcurrentHashMap 所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问.

synchronized 升级后的三种锁

来自 : https://www.cnblogs.com/wade-luffy/p/5969418.html

锁的优缺点对比

优点 缺点 适用场景
偏向锁 加锁和解锁不需要额外的消耗,和执行非同步方法比仅存在纳秒级的差距 如果线程间存在锁竞争,会带来额外的锁撤销的消耗 适用于只有一个线程访问同步块场景
轻量级锁 竞争的线程不会阻塞,提高了程序的响应速度 如果始终得不到锁竞争的线程使用自旋会消耗CPU 追求响应时间,锁占用时间很短
重量级锁 线程竞争不使用自旋,不会消耗CPU 线程阻塞,响应时间缓慢 追求吞吐量,锁占用时间较长

具体实现是通过操作 Java 对象头里的 Mark Word…

下面抄自上面链接,没有深入理解…

偏向锁

Java偏向锁(Biased Locking)是Java6引入的一项多线程优化。

偏向锁,顾名思义,它会偏向于第一个访问锁的线程,如果在运行过程中,同步锁只有一个线程访问,不存在多线程争用的情况,则线程是不需要触发同步的,这种情况下,就会给线程加一个偏向锁。

如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会消除它身上的偏向锁,将锁恢复到标准的轻量级锁。

轻量级锁

轻量级锁是由偏向所升级来的,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁

重量级锁

重量锁在JVM中又叫对象监视器(Monitor),它很像C中的Mutex,除了具备Mutex(0|1)互斥的功能,它还负责实现了Semaphore(信号量)的功能,也就是说它至少包含一个竞争锁的队列,和一个信号阻塞队列(wait队列),前者负责做互斥,后一个用于做线程同步。

并发编程的三个概念

原子性

即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

可见性

可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

有序性

即程序执行的顺序按照代码的先后顺序执行。

这个需要与 指令重排序 联系…

指令重排序

处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。

Java 与 CAS

CAS 是乐观锁的一种实现机制

Compare And Swap的缩写,翻译过来就是比较并替换

Java 哪里用到了 CAS ?

atomic 系列类

Lock 系列

jdk1.6 以上的版本,synchronized 转变为重量级锁之前,也会采用 cas 机制.

JVM 对锁的优化

锁消除

JIT 编译时,对运行上下文进行扫描,去除不可能存在竞争的锁.

比如在没有竞争的情况下调用 StringBuffer 的 append(),JVM 会消除 append() 方法上的 synchronized 的影响.

锁粗化

通过扩大加锁范围,避免反复加锁解锁.

其他