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的主要内容,如果未能解决你的问题,请参考以下文章

锁机制

JAVA乐观锁悲观锁实现

Java多线程:乐观锁悲观锁自旋锁

Java锁机制(Synchronized)[无锁偏向锁轻量级锁重量级锁]

JAVA中的内锁机制是啥

做开发,这几种锁机制你不得不了解一下