Java锁机制2.0
Posted 364.99°
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java锁机制2.0相关的知识,希望对你有一定的参考价值。
在 Java锁机制1.0中已经介绍了 锁 这种抽象概念,Java如何实现互斥锁(synchronized)、互斥锁的工作原理以及互斥锁的优化 及 锁的四种状态及其转化条件。
1.为什么需要CAS
悲观: 操作系统会悲观的认为如果不严格同步线程调用,就一定会造成异常。
悲观锁: 当有多个线程要操作同一对象,如果使用互斥锁,但是它的同步方式是悲观的,所以互斥锁将会锁定资源,只供一个线程调用,而阻塞其他线程——悲观锁。
悲观锁不适用场景1: 当大部分调用都是读操作(线程安全问题都是对全局/静态变量进行写操作造成的),那么就没有必要在每次调用的时候都锁定资源。
悲观锁不适用场景2: 同步代码块执行的耗时远远小于线程切换的耗时。
那么,如何不对共享资源进行锁定,也能对线程调用进行协调呢?
于是,CAS(Compare And Swap) 算法就诞生了。
2.啥是CAS?
通过下列场景进行描述CAS:两个线程争夺一个对象资源的情况。
注意: 上述的比较值和切换值同一时间只能有一个线程进行操作 → CAS必须是原子性的
如何实现CAS的原子性?
当然不能通过锁
各种不同的4架构的CPU都提供了指令级别的CAS原子操作,如X86(cmpxchg
支持CAS)、ARM(LL/SC
),而不需要通过操作系统的同步原语(如mutex
),CPU已经原生支持了CAS,上层调用即可,这样就能不依赖锁来进行线程同步。但这样并不意味着CAS就能代替锁。
这些通过CAS来实现同步的工具,由于不会锁定资源。而且当线程需要修改共享资源时,总是会乐观地认为对象状态值没有被其他线程修改过,而是每次自己都会主动尝试去比较状态值,相较于悲观锁,这种机制被称为 乐观锁 。但这是一种无锁的同步机制。
3.Java如何实现CAS
案例: 使用3条线程,将一个值,从0累加到1000
先演示不用锁,也不用CAS来进行
public class TestDemo1
static int num = 0;
public static void main(String[] args)
for (int i = 0; i < 3; i++)
Thread thread = new Thread(() ->
while (true)
if (num >= 1000)
break;
System.out.println("thread name: " + Thread.currentThread().getName() + ":" + num++);
);
thread.start();
查看控制台输出:
出现了重复打印的结果
接着演示,使用互斥锁(悲观锁)来改进:
public class TestDemo1
static int num = 0;
public static void main(String[] args)
for (int i = 0; i < 3; i++)
Thread thread = new Thread(() ->
while (true)
synchronized (TestDemo1.class) //给对象资源上锁
if (num >= 1000)
break;
System.out.println("thread name: " + Thread.currentThread().getName() + ":" + num++);
);
thread.start();
使用CAS
public class TestDemo1
static AtomicInteger num = new AtomicInteger(0);
public static void main(String[] args)
for (int i = 0; i < 3; i++)
Thread thread = new Thread(() ->
while (true)
if (num.get() >= 1000)
break;
System.out.println("thread name: " + Thread.currentThread().getName() + ":" + num.incrementAndGet());
);
thread.start();
4.AtomicInteger是如何实现CAS同步的?
使用 Unsafe.compareAndSwapInt 进行更新
进入源码:
public class AtomicInteger extends Number implements java.io.Serializable
private static final long serialVersionUID = 6214790243416807050L;
//设置使用 Unsafe.compareAndSwapInt 进行更新
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
找到incrementAndGet()
public final int incrementAndGet()
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
进一步点进getAndAddInt()
可以看到,其调用了compareAndSwapInt()
public final int getAndAddInt(Object var1, long var2, int var4)
int var5;
//这个循环就是自旋操作
do
var5 = this.getIntVolatile(var1, var2);
while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
注意: 此处的自旋并不会一直循环下去,其默认最大循环次数为10
5.了解unsafe类
java不能直接访问操作系统底层,而是通过本地方法来访问。Unsafe类提供了硬件级别的原子操作,主要提供了以下功能:
- 通过Unsafe类可以分配内存,可以释放内存
- 可以定位对象某字段的内存位置,也可以修改对象的字段值,即使它是私有的
- 挂起与恢复(
park
挂起线程、unpark
恢复挂起的线程) - CAS操作(通过
compareAndSwapXXX
方法实现)
CAS操作有3个操作数,内存值M,预期值E,新值U,如果M==E,则将内存值修改为B,否则啥都不做。
以上是关于Java锁机制2.0的主要内容,如果未能解决你的问题,请参考以下文章