当前位置: 首页 > news >正文

22-09-01 西安 JUC(04)java内存模型JMM、volatile关键字、原子性类、CAS比较并交换、AQS锁原理

计算机运行架构图

1.读取代码到内存条  【代码是存储到硬盘的

2.CPU操作内存条的数据  【cpu和内存交互。

但是内存条比起CPU计算速度还是慢

3.引出cpu缓存,这层缓存空间比较小(相比于主内存RAM)

由于cpu的运行程序速度远大于主存储的速度,所以会在主存RAM和CPU之间加多级高速缓存,缓存的速度接近cpu的运行速度,这样会大大提高计算机的运行速度。

任务栏右键--任务管理器--性能 


java内存模型(JMM)

JMM即为JAVA 内存模型(java memory model)。

因为在不同的硬件生产商和不同的操作系统下,内存的访问逻辑有一定的差异,结果就是当你的代码在某个系统环境下运行良好,并且线程安全,但是换了个系统就出现各种问题。

Java内存模型,就是为了屏蔽系统和硬件的差异,让一套代码在不同平台下能到达相同的访问结果。JMM从java 5开始的JSR-133发布后,已经成熟和完善起来。

1、JMM图

整个JMM图 都是在内存条里,跟cpu没关系。

先简单说一下,下面会有详细的总结

1.所有的线程不能直接操作主内存中的共享变量,共享变量指多个线程都能使用的变量

2.每个线程拷贝一份主内存的共享变量到本地内存,本地内存只有线程自己可以访问。每个线程操作完之后就可以释放内存了


3.JMM由他来控制线程读、写主内存中的数据,主要体现在加锁


2、JMM内存划分与规定

JMM规定了内存主要划分为主内存工作内存两种。

主内存:保存了所有的变量。

共享变量:如果一个变量被多个线程使用,那么这个变量会在每个线程的工作内存中保有一个副本,这种变量就是共享变量。

工作内存:每个线程都有自己的工作内存,线程独享,保存了线程用到的变量副本(主内存共享变量的一份拷贝)。工作内存负责与线程交互,也负责与主内存交互。

关于JMM内存划分注意2点:

1.此处的主内存和工作内存跟JVM内存划分(堆、栈、方法区)是在不同的维度上进行的

2.从更底层的来说,主内存对应的是硬件的物理内存,工作内存对应的是寄存器和高速缓存

JMM对共享内存的操作做出了如下两条规定:

  • 线程对共享内存的所有操作都必须在自己的工作内存中进行,不能直接从主内存中读写;

  • 不同线程无法直接访问其他线程工作内存中的变量,因此共享变量的值传递需要通过主内存完成。  

3、内存模型的三大特性

要保证多线程安全,要保证3大特性

  • 原子性:即不可分割性。比如 a=0;(a非long和double类型) 这个操作是不可分割的,那么我们说这个操作是原子操作。再比如:a++; 这个操作实际是a = a + 1;是可分割的,所以他不是一个原子操作。非原子操作都会存在线程安全问题,需要使用同步技术(sychronized)或者锁(Lock)来让它变成一个原子操作。一个操作是原子操作,那么我们称它具有原子性。java的concurrent包下提供了一些原子类,我们可以通过阅读API来了解这些原子类的用法。比如:AtomicInteger、AtomicLong、AtomicReference等。

  • 可见性:每个线程都有自己的工作内存,所以当某个线程修改完某个变量之后,在其他的线程中,未必能观察到该变量已经被修改。在 Java 中 volatile、synchronized 和 final 实现可见性。volatile只能让被他修饰内容具有可见性,但不能保证它具有原子性。

  • 有序性:java的有序性跟线程相关。一个线程内部所有操作都是有序的,如果是多个线程所有操作都是无序的。因为JMM的工作内存和主内存之间存在延迟,而且java会对一些指令进行重新排序。volatile和synchronized可以保证程序的有序性,很多程序员只理解这两个关键字的执行互斥,而没有很好的理解到volatile和synchronized也能保证指令不进行重排序。


volatile关键字

Java语言规范第三版中对volatile的定义如下:Java语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排它锁单独获取这个变量。

Java语言提供了volatile,在某些情况下比锁要更加方便。如果一个字段被声明为volatile,Java线程内存模型确保所有线程看到这个变量的值是一致的。

1、验证volatile可见性

static  int a = 0;
public static void main(String[] args) throws InterruptedException {
    System.out.println(Thread.currentThread().getName() + " 获取到的修改前的a : " + a);
    //子线程读取a的数据
    new Thread(() -> {
        System.out.println(Thread.currentThread().getName() + " 获取到的修改前的a : " + a);
        while (a == 0) {
            //就是什么都不写,单纯的在a=0的时候,死循环而已
        }
        //子线程中死循环判断 如果a的值改掉了 输出修改后的结果
        System.out.println(Thread.currentThread().getName() + " 获取到的修改后的a : " + a);
    }, "AA").start();
    Thread.sleep(500);
    a = 1;
    System.out.println(Thread.currentThread().getName() + " 获取到的修改后的a : " + a);
}

控制台打印:符合预期,因为 默认普通的变量不具有可见性。 main线程将共享数据a改了,但是子线程AA获取不到修改后的a,所以一直走while循环,程序停不下来

--------------

保证多线程并发变量具有可见性
1.可以加锁:效率低。加锁sychronized 3种问题都能解决。。
2.也可以使用volatile关键字

----------------

使用volatile关键字

改动上面代码,只是加了一个volatile关键字

 控制台效果:符合预期。volatile他修饰的内容具有可见性


2、验证volatile有序性

有序性:多线程并发时指令执行的顺序可能和我们编写的代码的逻辑顺序不一致

通过一个反例来证明,不加volatile会出现代码的有序性问题。

期望的结果: 线程1先执行  a = 1  ,x =0 , b = 1,y=1
期望的结果: 线程2先执行  b = 1  ,y =0 , a = 1,x=1
期望的结果: 线程1执行一半线程2执行了:  a = 1,b=1,x=1,y=1
也就是能接受的结果有3种   x=0&y=1 , y=0&x=1 , x=1&y=1  

如果出现了  x=0&y=0  代表代码的逻辑没有按照编写的指令顺序执行 出现了指令重排

static int a,b,x,y;
    public static void main(String[] args) throws Exception {
        int i = 0;//表示遍历执行的次数
        while(true){
            i++;
            a = b = x = y = 0;
            //线程1: 设置a = 1,x=b;
            Thread t1 = new Thread(() -> {
                a = 1;
                x = b;
            });
            //线程2:设置b = 1;y=a;
            Thread t2 = new Thread(() -> {
                b = 1;
                y = a;
            });
            
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            System.out.println(String.format("第 %d 轮遍历,x = %s, y = %s", i,x,y) );
            //如果出现了  x=0&y=0  代表代码的逻辑没有按照编写的指令顺序执行 出现了指令重排
            while(x==0 && y==0){
                break;
            }
        }
    }

控制台效果:当然我是没出来效果,连x=1,y=1的效果都没出来,更别说x=0,y=0的情况了

保证有序性:
1. volatile关键字声明的变量可以保证代码的有序性,不会出现指令重排

2. synchronized也可以保证程序的有序性


3、验证volatile不具备原子性

原子性: 一个操作不会被分隔

会出现原子性问题的程序:

class AtomicDemo01 {
    private int num = 0;

    public int incr() {
        return ++num;
    }
}

public class VolatileDemo {

    public static void main(String[] args){
        AtomicDemo01 a = new AtomicDemo01();
        for (int i = 0; i < 100000; i++) {
            new Thread(() -> {
                //原子性:
                //一个操作不会被分隔
                System.out.println(a.incr());
            }).start();
        }
    }
}

控制台打印:

100000个线程执行++num操作,如果++num操作具备原子性,最后的值应该是100000。说明++num不具备原子性

特别注意:就算是给num加了volatile,num++和++num也不具备原子性

private volatile int num = 0;

下面是加了volatile后的运行效果

4、怎么保证原子性

1.加锁(保证代码块原子性):synchornized/Lock

给incr方法加锁,保证了整个incr方法内部的代码是具有原子性的

public synchronized int incr() {
    return ++num;
}

控制台运行效果:终于看到了100000,很帅

2.原子性类(保证一个操作具有原子性):juc Atomic包下提供的类

子线程的意义:主线程委托cpu做某些异步操作

class AtomicDemo01 {
    //初始化原子类对象
    private AtomicInteger ai = new AtomicInteger(0);

    public int incr(){
        return ai.incrementAndGet();
    }
}

public class VolatileDemo {

    public static void main(String[] args){
        AtomicDemo01 a = new AtomicDemo01();
        for (int i = 0; i < 100000; i++) {
            new Thread(() -> {
                //原子性:
                //一个操作不会被分隔
                System.out.println(a.incr());
            }).start();
        }
    }
}

控制台运行效果:


5、volatile原理

Java语言提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程。当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序

在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比sychronized关键字更轻量级的同步机制

当一个变量定义为 volatile 之后,将具备两种特性

  • 保证此变量对所有的线程的可见性

  • 禁止指令重排序优化。有volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU访问内存时,并不需要内存屏障。

  • 不保证变量的原子性

volatile 性能:volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。

扩展:【真的很脑阔疼】

Happen-Before被翻译成先行发生原则,意思就是当A操作先行发生于B操作,则在发生B操作的时候,操作A产生的影响能被B观察到,“影响”包括修改了内存中的共享变量的值、发送了消息、调用了方法等。

volatile变量规则(volatile Variable Rule):对同一个volatile的变量,先行发生的写操作,肯定早于后续发生的读操作


CAS 比较并交换

CAS:Compare and Swap。比较并交换的意思。

CAS操作有3个基本参数:内存地址A,旧值B,新值C。它的作用是将指定内存地址A的内容与所给的旧值B相比,如果相等,则将其内容替换为指令中提供的新值C;如果不等,则更新失败。

CAS是解决多线程并发安全问题的一种乐观锁算法。因为它在对共享变量更新之前,会先比较当前值是否与更新前的值一致,如果一致则更新,如果不一致则循环执行(称为自旋锁),直到当前值与更新前的值一致为止,才执行更新。

在JUC下有个atomic包,有很多原子操作的包装类:它们都是基于CAS解决并发安全问题的(类似MybatisPlus中的version版本号)

 1、测试compareAndSet()方法

原子性类的方法:

public static void main(String[] args){
    //初始化原子性类对象:并设置初始化的值为100
    AtomicInteger ai = new AtomicInteger(100);
    // 参数1: 原本的值, 参数2:希望设置的值
    // 相当于将原来的值当做版本号使用
    System.out.println(ai.compareAndSet(1, 2));
    System.out.println("ai="+ai.get());
    System.out.println(ai.compareAndSet(100, 200));
    System.out.println("ai="+ai.get());
 }

 控制台打印:符合预期效果

修改数据时,会检查期望值和原子性对象value属性值是否一致,如果一致使用希望更新的值修改value的值。否则不进行修改。

//AtomicInteger类的方法
public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

//Unsafe类的方法,是个native方法
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

Unsafe类是CAS的核心类,提供硬件级别的原子操作(目前所有CPU基本都支持硬件级别的CAS操作

valueOffset:对象的属性地址偏移量

//以下为AtomicInteger源码部分
static {
    try {
        valueOffset = unsafe.objectFieldOffset
            (AtomicInteger.class.getDeclaredField("value"));
    } catch (Exception ex) { throw new Error(ex); }
}

private volatile int value;

public final int get() {
    return value;
}

2、原子性类存在的3个问题

原子性类的问题:(AtomicInteger为例)
1、不能保证代码块的原子性

Unsafe类只提供了少量的几个原子性的操作方法 只有调用他们其中一个才可以保证此行代码有原子性

2、并发更新时: 效率低

获取到原子性对象的value值相同的N多个线程,更新时版本号冲突!


等待其他线程操作完成后进行更新,但是自己调用原子方法时由于版本号被改了,所以更新失败,高并发的写,会浪费大量的CPU计算

3、ABA问题: 数据修改后又被改回来  无法判断数据之前是否被修改过

多个线程并发:2个线程都希望将数据从初始值改为指定值(100,2)

// 演示ABA问题
public static void main(String[] args){
    //初始化原子性类对象:并设置初始化的值为100
    AtomicInteger ai = new AtomicInteger(100);
    // 参数1: 原本的值, 参数2:希望设置的值
    //并发问题
    //同时来了2个线程,线程1和线程2都是想把100改为2,按道理说它俩只能有一个能修改成功
    new Thread(()->{
        System.out.println(Thread.currentThread().getName()+":"+ai.compareAndSet(100, 2));
    },"线程1").start();
    new Thread(()->{
        System.out.println(Thread.currentThread().getName()+":"+ai.compareAndSet(100, 2));
    },"线程2").start();
 }

控制台打印:

反正是一个true,一个false...,此时是没有问题的

但是ABA问题他就来了

并发的线程中有一个又修改了值(2,100)。此时就是线程1,2,3,了。

// 演示ABA问题
public static void main(String[] args){
    //初始化原子性类对象:并设置初始化的值为100
    AtomicInteger ai = new AtomicInteger(100);
    // 参数1: 原本的值, 参数2:希望设置的值
    //并发问题
    //同时来了2个线程,线程1和线程2都是想把100改为2,按道理说它俩只能有一个能修改成功
    new Thread(()->{
        System.out.println(Thread.currentThread().getName()+":"+ai.compareAndSet(100, 2));
    },"线程1").start();
    new Thread(()->{
        try {
            //线程1和和线程2一起来的,睡眠是为了保证线程3执行优先于线程2
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+":"+ai.compareAndSet(100, 2));
    },"线程2").start();
    new Thread(()->{
        System.out.println(Thread.currentThread().getName()+":"+ai.compareAndSet(2, 100));
    },"线程3").start();
 }

运行结果就不对劲了,但是true。。。线程1和线程2都修改成功了,造成了并发的线程1和线程2居然都能把值从100修改成2。这就是ABA问题 


3、版本号原子引用--解决ABA问题

AtomicStampedReference:版本号原子引用

AtomicStampedReference在构建的时候需要一个类似于版本号的int类型变量stamped,每一次针对共享数据的变化都会导致该 stamped 的变化

public AtomicStampedReference(V initialRef, int initialStamp) {
    pair = Pair.of(initialRef, initialStamp);
}

stamped 需要应用程序自身去负责,AtomicStampedReference并不提供,一般使用时间戳作为版本号)

解决ABA问题

// 解决ABA问题
public static void main(String[] args){
    //参数1:初始化的值    参数2:初始化的值的版本号
    AtomicStampedReference<Integer> asr = new AtomicStampedReference<Integer>(100,1);
    //并发问题
    //同时来了2个线程,线程1和线程2都是想把100改为2,按道理说它俩只能有一个能修改成功
    new Thread(()->{
        //参数1:原数据引用  参数2:要更新数据的引用  参数3:原数据的版本  参数4:更新后的版本号
        boolean b = asr.compareAndSet(100, 2, 1, 2);
        System.out.println(Thread.currentThread().getName()+":"+b);
    },"线程1").start();
    new Thread(()->{
        try {
            //线程1和和线程2一起来的,睡眠是为了保证线程3执行优先于线程2
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //参数1:原数据引用  参数2:要更新数据的引用  参数3:原数据的版本  参数4:更新后的版本号
        boolean b = asr.compareAndSet(100, 2, 1, 2);
        System.out.println(Thread.currentThread().getName()+":"+b);
        System.out.println(asr.getStamp());//输出版本号
        System.out.println(asr.getReference());//输出引用
    },"线程2").start();
    new Thread(()->{
        //参数1:原数据引用  参数2:要更新数据的引用  参数3:原数据的版本  参数4:更新后的版本号
        boolean b = asr.compareAndSet(2, 100, 2, 3);
        System.out.println(Thread.currentThread().getName()+":"+b);
    },"线程3").start();
 }

控制台打印效果:

此时,线程1和线程2就只有一个打印true了。3是版本号,100是原子引用


4、自定义封装一个具有原子操作的类

//自定义原子性类
class AtomicDemo03{

    private volatile int num = 1;
    static Unsafe unsafe;
    static long valueOffset;
    static{
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);//设置私有的可以访问
            unsafe = (Unsafe) field.get(null);
            valueOffset = unsafe.objectFieldOffset  //获取AtomicDemo03类中的num属性的偏移量
                    (AtomicDemo03.class.getDeclaredField("num"));
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
    public int incr(){
        return unsafe.getAndAddInt(this,valueOffset,1);
    }


    public final boolean compareAndSet (int expect, int update)throws Exception {

        //参数1:要修改属性值的对象
        //参数2:要修改的属性在对象中的偏移量(基于偏移量+拥有该属性的对象的地址可以获取该属性的内存地址)
        //参数3:希望值
        //参数4:要修改的值
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
    public int getNum() {
        return num;
    }
}

使用一下它的原型性

public class VolatileDemo {
        public static void main(String[] args){
            AtomicDemo03 ad = new AtomicDemo03();
            for (int i = 0; i < 100000; i++) {
                new Thread(()->{
                    System.out.println(ad.incr());
                }).start();
            }
         }
}

控制台打印:


AQS 抽象队列同步器

AbstractQueuedSynchronizer抽象队列同步器简称AQS,它是实现同步器的基础组件(框架),

juc下面Lock的实现以及一些并发工具类(Semaphore、CountDownLatch、CyclicBarrier等)就是通过AQS来实现的。具体用法是通过继承AQS实现其模板方法,然后将子类作为同步组件的内部类。

1、AQS框架结构

AQS维护了一个volatile语义(支持多线程下的可见性)的共享资源变量state和一个FIFO(first-in-first-out)线程等待队列(多线程竞争state资源被阻塞时,会进入此队列)。

//AbstractQueuedSynchronizer类源码
private volatile int state;

2、基于AQS实现锁的原理

AQS将大部分的同步逻辑均已经实现好,继承的自定义同步器只需要实现state的获取(acquire)和释放(release)的逻辑代码就可以,主要包括下面方法:

  • tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。

  • tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。

  • tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。

  • tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。

  • isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。

也就是说:

通过AQS可以实现独占锁(只有一个线程可以获取到锁,如:ReentrantLock),也可以实现共享锁(多个线程都可以获取到锁Semaphore/CountDownLatch等)

3、自定义锁 Mutex

这个类纯属复制 AQS中的某一段注释。。

//基于AQS的自定义Lock锁:和ReentrantLock类似
class Mutex implements Lock, java.io.Serializable {

    // Our internal helper class
    private static class Sync extends AbstractQueuedSynchronizer {
        // Reports whether in locked state
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        // Acquires the lock if state is zero
        public boolean tryAcquire(int acquires) {
            assert acquires == 1; // Otherwise unused
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        // Releases the lock by setting state to zero
        protected boolean tryRelease(int releases) {
            assert releases == 1; // Otherwise unused
            if (getState() == 0) throw new IllegalMonitorStateException();
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        // Provides a Condition
        Condition newCondition() {
            return new ConditionObject();
        }

        // Deserializes properly
        private void readObject(ObjectInputStream s)
                throws IOException, ClassNotFoundException {
            s.defaultReadObject();
            setState(0); // reset to unlocked state
        }
    }

    // The sync object does all the hard work. We just forward to it.
    private final Sync sync = new Sync();

    public void lock() {
        sync.acquire(1);
    }

    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    public void unlock() {
        sync.release(1);
    }

    public Condition newCondition() {
        return sync.newCondition();
    }

    public boolean isLocked() {
        return sync.isHeldExclusively();
    }

    public boolean hasQueuedThreads() {
        return sync.hasQueuedThreads();
    }

    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }
}

测试我们自定义的这把锁

public class VolatileDemo {
        public static void main(String[] args){
            AtomicDemo04 ad = new AtomicDemo04();
            //通过上锁(自定义的锁)实现++num的原子性
            for (int i = 0; i < 100000; i++) {
                new Thread(()->{
                    System.out.println(ad.incr());
                }).start();
            }
         }
}

/*
基于AQS 同步器自定义锁
*/
class AtomicDemo04{
    private int num = 0;
    Mutex mutex = new Mutex();
    public int incr(){
        mutex.lock();
        try {
            return ++num;
        } finally {
            mutex.unlock();
        }
    }
}

控制台打印如下:


4、ReentrantLock底层原理

创建一个锁,根据传入的构造器参数判断创建的是不是公平锁,不传默认创建不公平锁

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

public ReentrantLock() {
    sync = new NonfairSync();
}

 在ReentrantLock类中包含了3个AQS的实现类:

  1. 抽象类Sync

  2. 非公平锁实现类NonfaireSync

  3. 公平锁实现类FairSync

加锁: 同步器使用cas让当前线程更新同步器的state值从0到1,如果更新成功获取到锁,如果更新失败将当前线程创建为Node对象添加到等待锁的队列中

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}
//同步器
abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = -5179523762034025860L;

    /**
         * Performs {@link Lock#lock}. The main reason for subclassing
         * is to allow fast path for nonfair version.
         */
    abstract void lock();

    /**
         * Performs non-fair tryLock.  tryAcquire is implemented in
         * subclasses, but both need nonfair try for trylock method.
         */
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }

    protected final boolean tryRelease(int releases) {
        int c = getState() - releases;
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
        if (c == 0) {
            free = true;
            setExclusiveOwnerThread(null);
        }
        setState(c);
        return free;
    }

    protected final boolean isHeldExclusively() {
        // While we must in general read state before owner,
        // we don't need to do so to check if current thread is owner
        return getExclusiveOwnerThread() == Thread.currentThread();
    }

    final ConditionObject newCondition() {
        return new ConditionObject();
    }

    // Methods relayed from outer class

    final Thread getOwner() {
        return getState() == 0 ? null : getExclusiveOwnerThread();
    }

    final int getHoldCount() {
        return isHeldExclusively() ? getState() : 0;
    }

    final boolean isLocked() {
        return getState() != 0;
    }

    /**
         * Reconstitutes the instance from a stream (that is, deserializes it).
         */
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();
        setState(0); // reset to unlocked state
    }
}

/**
不公平同步器
     * Sync object for non-fair locks
     */
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
    //sync.lock();
    final void lock() {
        if (compareAndSetState(0, 1))// 锁的状态为空闲 ,从0改为1成功代表获取到锁 返回true
            setExclusiveOwnerThread(Thread.currentThread());//设置当前线程拥有锁
        else //锁状态更新失败  state的值不为0
            acquire(1);
    }
    public final void acquire(int arg) {
        if (!tryAcquire(arg) && //获取锁失败,将当前线程对象添加到获取锁队列中
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
    final boolean nonfairTryAcquire(int acquires) {
        //获取 正在获取锁的线程对象
        final Thread current = Thread.currentThread();
        //获取state值:
        int c = getState();
        if (c == 0) {//刚使用锁的线程释放了锁 
            //更新state的值为1
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        //锁被占用:判断使用锁的线程是不是当前的线程对象
        else if (current == getExclusiveOwnerThread()) {
            //当前线程正在使用锁
            int nextc = c + acquires;//将state的值在之前的基础上+1   表示重入
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc);//设置state的值为重入的次数
            return true;
        }
        //锁被占用 但是不是当前线程占用的  获取锁失败
        return false;
    }
    //释放锁:=============
    public final boolean release(int arg) {
        if (tryRelease(arg)) {//如果为true代表锁释放成功
            Node h = head;
            //释放锁之后获取队列中的线程获取锁
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        //锁释放失败
        return false;
    }
    protected final boolean tryRelease(int releases) {
        int c = getState() - releases;//获取state的值-1
        //判断如果不是当前线程拥有锁 抛出异常
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        
        
        boolean free = false;
        if (c == 0) {//c=0代表锁释放成功
            free = true;
            setExclusiveOwnerThread(null);
        }
        //c无论是否为0  下面的代码都会执行
        //c为0 代表锁完全释放,free的值修改为true  return free为true代表完全释放锁
        //c不为0 代表锁重入了  未释放成功 将c的值设置给state  返回false//如果为true代表锁释放成功
        setState(c);
        return free;
    }
}
//公平的同步器
static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;

    final void lock() {
        acquire(1);
    }

    /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
    protected final boolean tryAcquire(int acquires) {
        //获取当前线程对象
        final Thread current = Thread.currentThread();
        int c = getState();//获取锁的状态
        if (c == 0) {//锁空闲
            //hasQueuedPredecessors判断队列中是否有线程等待获取锁 如果有让他们先获取锁
            if (!hasQueuedPredecessors() &&
                //如果没有线程等待  自己去获取锁
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        //当前线程已经拥有锁了  state!=0  重入
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        //获取锁失败
        return false;
    }
}

相关文章:

  • 加湿器芯片方案:不要让空调带走你的水分
  • Machine learning week 10(Andrew Ng)
  • spring-cloud-alibaba-Nacos2.0.3:注册中心和配置中心框架学习
  • android studio教程,Android Studio一个完整的APP实例
  • jumpserver堡垒机界面设置及界面功能
  • LeetCode---SQL刷题6
  • React知识总结✨
  • 一周时间深扒事务 总结代码演示篇 拿捏事务
  • 剑指offer79-87二进制枚举、回溯
  • 《Coding Monkey的自我修养》之MyBatis批量插入数据的三种方法
  • Windows应急响应信息采集工具
  • 舵机调试上位机
  • 瑞吉外卖 —— 3、员工管理
  • 走到上市前夕,叮当健康如何勾画“医药检险”蓝图?
  • 批量条件赋值、文本字段计算常用表达式
  • 【EOS】Cleos基础
  • 2018以太坊智能合约编程语言solidity的最佳IDEs
  • canvas绘制圆角头像
  • Javascript弹出层-初探
  • nfs客户端进程变D,延伸linux的lock
  • SQLServer之索引简介
  • Transformer-XL: Unleashing the Potential of Attention Models
  • windows下如何用phpstorm同步测试服务器
  • 阿里研究院入选中国企业智库系统影响力榜
  • 从0到1:PostCSS 插件开发最佳实践
  • 短视频宝贝=慢?阿里巴巴工程师这样秒开短视频
  • 基于webpack 的 vue 多页架构
  • 前端相关框架总和
  • 小程序滚动组件,左边导航栏与右边内容联动效果实现
  • media数据库操作,可以进行增删改查,实现回收站,隐私照片功能 SharedPreferences存储地址:
  • 数据库巡检项
  • ​LeetCode解法汇总1410. HTML 实体解析器
  • # 日期待t_最值得等的SUV奥迪Q9:空间比MPV还大,或搭4.0T,香
  • #define用法
  • #控制台大学课堂点名问题_课堂随机点名
  • (4)STL算法之比较
  • (zt)基于Facebook和Flash平台的应用架构解析
  • (保姆级教程)Mysql中索引、触发器、存储过程、存储函数的概念、作用,以及如何使用索引、存储过程,代码操作演示
  • (附源码)springboot电竞专题网站 毕业设计 641314
  • (转)fock函数详解
  • (转)创业家杂志:UCWEB天使第一步
  • (转)视频码率,帧率和分辨率的联系与区别
  • .apk文件,IIS不支持下载解决
  • .class文件转换.java_从一个class文件深入理解Java字节码结构
  • .NET Core中的去虚
  • .net专家(高海东的专栏)
  • @reference注解_Dubbo配置参考手册之dubbo:reference
  • @Service注解让spring找到你的Service bean
  • @synthesize和@dynamic分别有什么作用?
  • [ Linux Audio 篇 ] 音频开发入门基础知识
  • [2021]Zookeeper getAcl命令未授权访问漏洞概述与解决
  • [bzoj 3534][Sdoi2014] 重建
  • [Codeforces] combinatorics (R1600) Part.2
  • [DM复习]Apriori算法-国会投票记录关联规则挖掘(上)
  • [emacs] CUA的矩形块操作很给力啊