Java 锁、Unsafe、CAS、 AQS

Unsafe类简介

Java 和 C++语言的一个重要区别就是Java中我们无法直接操作一块内存区域,不能像C++那样可以自己申请内存和释放内存。Java中的Unsafe类为我们提供了类似C++手动管理内存的能力。Unsafe类,全限定雷鸣是sun.misc.Unsafe,从名字中我们可以看出这个类对普通程序员来说是危险的,一般的应用开发者不会用到这个类

(1)初始化操作

查看源码:

//构造方法已经被私有化 
private Unsafe() { /* compiled code */ } 
//这个静态的方法可以返回Unsafe对象,我们可以试试 @sun.reflect.CallerSensitive 
public static sun.misc.Unsafe getUnsafe() { /* compiled code */ }

getUnsafe源码,其实就是个单例模式:

    @CallerSensitive
    public static Unsafe getUnsafe() {
        Class var0 = Reflection.getCallerClass();
        if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
            throw new SecurityException("Unsafe");
        } else {
            return theUnsafe;
        }
    }

调用getUnsafe测试获取Unsafe对象:

public class UnsafeUtil {
    public static void main(String[] args) {
        Unsafe unsafe=Unsafe.getUnsafe();
        System.out.println(unsafe);
    }
}

Tip:我这样写没有报错,我是jdk1.8,不过老师演示的时候报错了的,如下:
在这里插入图片描述如果报错的话就通过另一种方式获取Unsafe对象,比如反射
在源码里有个字段,就是getUnsafe类里面返回的那个theUnsafe,我们自己通过反射来获取这个字段也可

这是那个字段在源码里的声明,就是单例模式,构造方法私有,字段设置成final

private static final Unsafe theUnsafe;

通过反射获取unsafe对象:

public class UnsafeUtil {
    public Unsafe getUnsafe(){
        Unsafe unsafe=null;
        Class cls=Unsafe.class;
        try {
            Field theUnsafe = cls.getDeclaredField("theUnsafe");
            //去除私有属性
            theUnsafe.setAccessible(true);
            // public Object get(Object obj)得到一个Object对象
            unsafe=(Unsafe)theUnsafe.get(null);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return unsafe;
    }
}

(2)操作对象属性

//获取静态属性在内存中的偏移量 
public native long staticFieldOffset(java.lang.reflect.Field field); 
//获取非静态属性在内存中的偏移量 
public native long objectFieldOffset(java.lang.reflect.Field field);

获取属性在内存中的偏移量(位置):
User类:

public class User {
    String name;
    static int age;
}
public class UnsafeTest1 {
    public static void main(String[] args) throws NoSuchFieldException {
        UnsafeUtil unsafeUtil=new UnsafeUtil();
        Unsafe unsafe=unsafeUtil.getUnsafe();
        //User里面只有两个属性name,age
        Class userClass=User.class;
        //1、获取name属性的偏移量,非静态的
        long nameoffset=unsafe.objectFieldOffset(userClass.getDeclaredField("name"));
        //2、获取age属性的偏移量,静态的
        long ageoffset=unsafe.staticFieldOffset(userClass.getDeclaredField("age"));
        System.out.println("nameoffset: "+nameoffset+"     ageoffset:"+ageoffset);
    }
}
/*
打印结果:
nameoffset: 12     ageoffset:104
*/

得到了在内存中的位置,就可以直接对内存中的值进行操作了

获取和设置指定位置的属性值

可以通过对象,偏移量对内存中对象的属性进行获取和赋值,这里无视java中的访问修饰符的

//给传入的对象o的偏移量l位置设置值o1 
public native void putObject(java.lang.Object o, long l, java.lang.Object o1); 
//获取对象o的偏移量l的属性值 
public native java.lang.Object getObject(java.lang.Object o, long l); 
//强制给主内存中的变量设置值。 
public native void putObjectVolatile(java.lang.Object o, long l, java.lang.Object o1); 
//强制从主内存获取属性值。 关于volatile我们在后面讲解 
public native java.lang.Object getObjectVolatile(java.lang.Object o, long l);

注:在Unsafe中还有很多类似的方法,比如:putInt,getInt,putFloat,getFloat等等,这里不一一列举了。

//public native void putObject(java.lang.Object o, long l, java.lang.Object o1);
        //java.lang.Object o
        User user=new User();
        //给name属性赋值
        unsafe.putObject(user,nameoffset,"张三");
        //给age属性赋值
        unsafe.putObject(user,ageoffset,18);
//public native java.lang.Object getObject(java.lang.Object o, long l);
        //获取name属性的值
        String name=(String)unsafe.getObject(user,nameoffset);
        //获取age属性的值
        int age=(Integer)unsafe.getObject(user,ageoffset);
        System.out.println("name:"+name+"      age:"+age);

/*
打印结果:
name:张三      age:18
*/

(3)操作数组元素

数组比较特殊,在内存中连续存储
我们可以通过Unsafe获取数组的第一个元素的偏移量以及每个元素之间的偏移量增量。通过这两个数据我们可以直
接获取数组中的每一个数据。

//获取数组第一个元素的偏移量位置 
public native int arrayBaseOffset(java.lang.Class<?> aClass); 
//获取数组元素之间的偏移量增量 
public native int arrayIndexScale(java.lang.Class<?> aClass);

Tip :虽然上面的两个方法理论上可以获取数组元素的偏移量,但Unsafe其实已经为我们提供了常用类型的数组的第一个元素的偏移量和增量

	public static final int INVALID_FIELD_OFFSET = -1;
    public static final int ARRAY_BOOLEAN_BASE_OFFSET;
    public static final int ARRAY_BYTE_BASE_OFFSET;
    public static final int ARRAY_SHORT_BASE_OFFSET;
    public static final int ARRAY_CHAR_BASE_OFFSET;
    public static final int ARRAY_INT_BASE_OFFSET;
    public static final int ARRAY_LONG_BASE_OFFSET;
    public static final int ARRAY_FLOAT_BASE_OFFSET;
    public static final int ARRAY_DOUBLE_BASE_OFFSET;
    public static final int ARRAY_OBJECT_BASE_OFFSET;
    public static final int ARRAY_BOOLEAN_INDEX_SCALE;
    public static final int ARRAY_BYTE_INDEX_SCALE;
    public static final int ARRAY_SHORT_INDEX_SCALE;
    public static final int ARRAY_CHAR_INDEX_SCALE;
    public static final int ARRAY_INT_INDEX_SCALE;
    public static final int ARRAY_LONG_INDEX_SCALE;
    public static final int ARRAY_FLOAT_INDEX_SCALE;
    public static final int ARRAY_DOUBLE_INDEX_SCALE;
    public static final int ARRAY_OBJECT_INDEX_SCALE;
public class UnsafeTest2 {
    public static void main(String[] args) throws NoSuchFieldException {
        UnsafeUtil unsafeUtil = new UnsafeUtil();
        Unsafe unsafe = unsafeUtil.getUnsafe();
        String []names={"张三","李四","王五"};
        //偏移量:ARRAY_OBJECT_BASE_OFFSET
        //增量:ARRAY_OBJECT_INDEX_SCALE

        //获取数组的第一个元素,因为知道偏移量和增量,直接可以获取
        String firstName=(String) unsafe.getObject(names, ARRAY_OBJECT_BASE_OFFSET);

        //获取数组的第x个元素,比如第三个,偏移量+增量*(x-1) 即可
        String xName=(String)unsafe.getObject(names,ARRAY_OBJECT_BASE_OFFSET+2*ARRAY_OBJECT_INDEX_SCALE);

        System.out.println("firstName:"+firstName+"      xName:"+xName);

    }
}

/*
打印结果:
firstName:张三      xName:王五
*/

(4)线程挂起和恢复

//释放被park创建的在一个线程上的阻塞。由于其不安全性,因此必须保证线程是存活的。 
public native void unpark(java.lang.Object o); 
//阻塞当前线程,一直等道unpark方法被调用。 
public native void park(boolean b, long l);

(5)CAS机制

public final native boolean compareAndSwapObject(java.lang.Object o, long l, java.lang.Object o1, java.lang.Object o2); 
public final native boolean compareAndSwapInt(java.lang.Object o, long l, int i, int i1); 
public final native boolean compareAndSwapLong(java.lang.Object o, long l, long l1, long l2); 1

volatile关键字

作用:

  1. 可见性
  2. 防止指令重排序

// store 和 write是有原子性的

1. 可见性

先来看一段代码:

public class VolitateTest1 {
    public static int i = 3;

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            System.out.println("ThreadA 开始");
            for (; ; ) {
                if (i != 3) {
                    break;
                }
            }
            System.out.println("ThreadA 结束");
        }).start();
        TimeUnit.SECONDS.sleep(1);
        i = 4;
        System.out.println("i修改为:" + i);
    }
}

线程ThreadA里面只要i!=3就可以退出循环,我们在主线程将i改成了4,这时候ThreadA应该退出循环了才对,我们来看运行结果:
在这里插入图片描述
可以看到i确实被修改为4了,但是死循环确实也没退出。

接下来解释为什么
先了解一下JMM:

JMM:Java内存模型,是Java虚拟机规范中所定义的一种内存模型,Java内存模型是标准化的,屏蔽掉了底层不同计算机的区别

现代计算机的内存模型:

其实在早期计算机中cpu和内存的速度是差不多的,但在现代计算机中,CPU的指令速度远超内存存取速度,由于计算机的存储设备与处理机的运算速度有几个数量级的差距,所以现代计算机系统不得不加入一层读写速度尽可能接近处理器运行速度的高速缓存(Cache)来作为内存与处理器之间的缓冲。

将运算需要使用到的数据复制到缓存中,让运算能快速运行,当运算结束后再从缓存同步回内存之中,这样处理器就无须等待缓慢的内存读写了

基于高速缓存的存储交互很好的解决了处理器与内存的速度矛盾,但是也为计算机系统带来更高的复杂度,因为它引入了一个新的问题:缓存一致性(CacheCoherence)

在多处理器系统中,每个处理器都有自己的高速缓存,而它们又共享同一主内存

在这里插入图片描述

我们的程序 VolitateTest1 中,子程序在循环的使用变量 i。子线程执行完成之前,是不会从主内存中读取数据的。所以即使主线程修改了变量i的值,子线程依然无法读取最新的值。
在这里插入图片描述


我们在 VolitateTest1 里面将 i 前面加上volatile关键字,再看运行结果
在这里插入图片描述
运行结果:
在这里插入图片描述
可以发现程序正常结束了,子线程可以发现变量 i 被修改了,为什么呢,volatile做了什么?


volatile 可见性的实现:

  • 在生成汇编代码指令时会在 volatile 修饰的共享变量进行写操作的时候会多出Lock前缀指令(1)
  • Lock前缀的指令会引起CPU缓存写会内存
  • 一个CPU的缓存写回到内存会导致其他CPU缓存了该内存地址的数据无效
  • volatile 变量通过缓存一致性协议 (2)保证每个线程获得最新值
  • 缓存一致协议保证每个CPU通过嗅探(3)在总线上传播的数据来检查自己缓存的值是不是修改
  • 当CPU发现自己缓存行对应的内存地址被修改,会将当前CPU的缓存设置成无效状态,重新从内存把数据读到CPU缓存

(1)Lock前缀指令

(2)缓存一致性协议

当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存设置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存改变了的缓存行为是无效的,那么他就会从内存重新读取

在MESI协议中,每个Cache line有4个状态,可用2个bit表示,它们
分别是:
在这里插入图片描述
(3)嗅探

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

嗅探的缺点:总线风暴


2.禁止指令重排(有序性的实现)

重排的类型:
在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排序。

  1. 编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
  2. 指令级并行的重排序。现代处理器采用了指令级并行技术(Instruction-LevelParallelism,ILP)来将多条指令重
    叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
  3. 内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。

volatile 是怎么实现有序性的呢?
volatile 通过内存屏障和 happen-before 规则实现有序性


happen-before规则:

  1. 程序次序规则:在一个线程内,按照程序代码顺序,书写在前面的操作优先于书写在啊后面的操作
  2. 管程锁定规则:对一个锁的解锁操作,先行发生于后续对这个锁的加锁操作。这里的锁是指同一个锁
  3. volatile 变量规则:对一个 volatile 变量的写操作先行发生于后面对这个变量的读操作
  4. 线程启动规则:Thread 对象的 start() 方法先行发生于此线程的每一个动作
  5. 线程 join() 规则:被调用 join() 方法的线程的所有操作先行发生于 join() 的返回
  6. 传递性规则:操作 a 先行发生于操作 b ,操作 b 先行发生于操作 c ,则操作 a 先行发生于操作c
  7. 对象终结规则:一个对象的初始化完成(构造函数的执行)先行发生于它的 finalize() 方法

内存屏障:

  • 为了实现volatile内存语义时,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的CPU重排序。

  • 对于编译器,内存屏障将限制它所能做的重排序优化;对于CPU,内存屏障将会导致缓存的刷新操作

  • volatile变量的写操作,在变量的前面和后面分别插入内存屏障; volatile 变量的读操作是在后面插入两个内存屏障

1)在每个volatile写操作的前面插入一个StoreStore屏障

2)在每个volatile写操作的后面插入一个StoreLoad屏障

3)在每个volatile读操作的后面插入一个LoadLoad屏障

4)在每个volatile读操作的后面插入一个LoadStore屏障


CAS算法

全名:Compare And Swap(比较与交换)
无锁算法:基于硬件原语实现,在不使用锁(没有线程阻塞)的情况下实现多线程之间的变量同步
jdk中实现:java.util.concurrent 包中的原子类(AtomicInteger) 就是通过CAS来实现了乐观锁

算法涉及到的三个参数:
1.需要读写的内存值
2.进行比较的值A
3.要写入的新值B

CAS比较交换的伪代码:

do{
	从内存读取需要读写的值
	让A=从内存新读取出的值
}while(!CAS(现在内存里的值,A,B));

就是会一直比较内存中的值和A(A是自己期望的值,也就是上一次从内存中拷贝出来的值)是否是相等的,如果相等,说明自己在做其他事的这段时间还没有线程来修改过内存里面的这个值,那么我就可以直接修改。但是也有可能我刚让A=从内存新取出来这个值,就有其他线程又把内存中的值修改了,那么CAS(现在内存里的值,A,B)==false,我就又只能再重新从内存读取值,让A=读取的值,再比较。

CAS算法存在的问题

  1. ABA问题:
    CAS需要在操作值的时候检查内存值是否发生变化,没有发生变化才会更新内存值。但是如果内存值原来是A,后来变成了B,然后又变成了A,那么CAS进行检查时会发现值没有发生变化,但是实际上是有变化的。ABA问题的解决思路就是在变量前面添加版本号,每次变量更新的时候都把版本号加一,这样变化过程就从“A-B-A”变成了“1A-2B-3A”
    解决ABA问题:
    在原来的基础上加个版本号,通过 AtomicStampedReference asr = new AtomicStampedReference(1,1); 这个类实现

  2. 循环时间长开销大。CAS操作如果长时间不成功,会导致其一直自旋,给CPU带来非常大的开销

  3. 只能保证一个共享变量的原子操作。对一个共享变量执行操作时,CAS能够保证原子操作,但是对多个共享变量操作时,CAS是无法保证操作的原子性的。


Java主流锁的分析

链接:
写的超好的链接:https://www.cnblogs.com/jyroy/p/11365935.html

在这里插入图片描述


简单概括,详细的解释可以看上面那个链接:

1.悲观锁和乐观锁
  1. 悲观锁
    悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,在获取数据的时候会先加锁,确保数据不会被别的线程修改
    锁实现: 关键字synchronized、接口Lock的实现类
    适用场景: 写操作比较多,先加锁可以保证写操作时数据正确
  2. 乐观锁
    乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据
    锁实现CAS算法,例如AtomicInteger类的原子自增是通过CAS自旋实现的
    适用场景:读操作较多,不加锁的特点能够使读操作性能大幅度提升

2.自旋锁和适应性自旋锁
  1. 自旋锁

    是指当一个线程在获取锁的时候,如果锁已经被其他线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,自旋直到获取锁才会退出循环
    自旋锁存在的意义和使用场景:
    1.阻塞与唤醒线程需要操作系统切换CPU状态,需要消耗一定时间
    2.同步代码块逻辑简单,执行时间很短,所以一直循环的时间可能比切换CPU更换算

  2. 适应性自旋锁
    自适应锁假定不同的线程持有同一个锁对象的时间基本相当,竞争程度趋于稳定,因此,可以根据上一次自旋的时间与结果调整下一次自旋的时间


3.无锁、偏向锁、轻量级锁、重量级锁

这四种锁是指锁的状态,专门针对synchronized的

  1. 无锁
    无锁没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功

    多个线程修改同一个值,必定会有一个线程能修改成功,而其他修改失败的线程会不断重试直到修改成功。上面我们介绍的CAS原理及应用即是无锁的实现。无锁无法全面代替有锁,但无锁在某些场合下的性能是非常高的。

  2. 偏向锁
    偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价。

    在大多数情况下,锁总是由同一线程多次获得,不存在多线程竞争,所以出现了偏向锁。其目标就是在只有一个线程执行同步代码块时能够提高性能。

  3. 轻量级锁
    是指当锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能。

  4. 重量级锁
    多个线程同时竞争资源,只让一个线程运行,其余的线程都阻塞

在这里插入图片描述


4.公平锁和非公平锁
  1. 公平锁
    线程直接进入队列中排队,队列中的第一个线程才能获得锁。
    等待队列中除第一个线程以外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大。
  2. 非公平锁
    非公平锁是多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾等待。但如果此时锁刚好可用,那么这个线程可以无需阻塞直接获取到锁,所以非公平锁有可能出现后申请锁的线程先获取锁的场景。

5.可重入锁和非可重入锁
  1. 可重入锁
    可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class),不会因为之前已经获取过还没释放而阻塞
  2. 非可重入锁
    和可重入锁相反,不可递归调用,递归调用就发生死锁,即若当前线程执行某个方法已经获取了该锁,那么在方法中尝试再次获取锁时,就会获取不到被阻塞

6.独享锁和共享锁
  1. 独享锁
    独享锁也叫排他锁,是指该锁一次只能被一个线程所持有。如果线程T对数据A加上排它锁后,则其他线程不能再对A加任何类型的锁。获得排它锁的线程即能读数据又能修改数据。JDK中的synchronized和JUC中Lock的实现类就是互斥锁。
  2. 共享锁
    共享锁是指该锁可被多个线程所持有。如果线程T对数据A加上共享锁后,则其他线程只能对A再加共享锁,不能加排它锁。获得共享锁的线程只能读数据,不能修改数据。

7.粗粒度锁和细粒度锁
  1. 粗粒度锁
    就是把执行的代码块都锁定
  2. 细粒度锁
    就是锁住尽可能小的代码块,java.util.ConcurrentHashMap 中的分段锁就是一种细粒度锁

实例对象是怎样存储的?
对象的示例存储在堆空间,对象的元数据存储在方法区(元数据区),对象的引用存在栈空间

元数据是描述数据的数据(data about data),主要是描述数据属性(property)的信息,用来⽀支持如指示存储位置、历史数据、资源查找、文件记录等功能。EOS元数据有两种元数据:系统元数据和用户定义的元数据。


Synchronized 分析

使用方式

1.同步实例方法:锁当前实例对象
2.同步类方法:锁是当前对象
3.同步代码块:锁是括号里的对象


实现方式

synchronized 是 JVM 内置锁,通过内部 Monitor (监视器)实现,基于进入与退出 Monitor 对象实现方法与代码块同步,监视器锁的实现依赖底层操作系统的Mutex lock(互斥锁)实现(操作系统P、V操作)

JVM 对象加锁原理

认识对象的内存结构:

  • 对象头:比如 hash 码,对象所属年代,锁状态标志,偏向锁(线程)id,偏向时间,数组长度(数组对象)
  • 对象实际数据:即创建对象时,对象中成员变量,方法等

AQS

Java锁分为两种,隐式锁(sync)、显示锁(retreenLock)

RetreenLock:读锁相当于乐观锁

CLH队列:
CLH队列是Craig, Landin, and Hagersten 三个人发明的一种基于双向链表数据结构的队列

Java中的CLH队列是原CLH队列中的一个变种,线程由原自旋机制改为阻塞机制

每个线程都会被封装成一个Node 节点放到同步队列中,每个Node节点保存了当前线程的同步状态,等待状态,前驱和后继节点等。

Node 节点的信息不仅仅包含同步队列的,条件队列的信息也放在Node节点里

条件队列:
Condition 是一个多线程间协调通信的工具类,使得某些线程一起等待某个条件(Condition),只有该条件具备时,这些线程才会被唤醒,从而重新争夺资源

原文链接:
https://blog.csdn.net/disiwei1012/article/details/78596731

同步队列和条件队列的关系:
我的理解:(线程的5中状态分别对应5个队列,新建、就绪、运行、阻塞、结束)同步队列就是就绪队列,条件队列就是阻塞队列
同步队列节点的来源:
1.同步队列依赖一个双向链表来完成同步状态的管理,当前线程获取同步状态失败后,同步器会将线程构建成一个节点,并将其加入同步队列中
2.通过signal 或 signalAll 将条件队列中的节点转移到同步队列中(条件队列转移到同步队列)
条件队列节点的来源:
1.调用await方法阻塞线程
2.当前线程存在于同步队列的头结点,调用await方法进行阻塞(从同步队列转化到条件队列)

可总结为:
1.同步队列和条件队列可以相互转化
2.一个线程只能存在于两个队列中的一个

Node源码:

    static final class Node {

/*
* 锁的两种模式:(下面acquireQueued函数的参数addWaiter的参数Node.EXCLUSIVE
  就是这两种之一)
* acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
*/
//1. 共享模式:读写锁的实现
        static final Node SHARED = new Node();
//2.独占模式:悲观锁的实现
        static final Node EXCLUSIVE = null;



// 以下四个变量是线程的四个活动状态,也就是 waitStatus 的四个值:
	 //1. 表示线程已经被取消,这个状态的线程会被移除同步队列(循环检测到的时候移除)
		/* 一个节点由于超时或者中断需要在CLH队列中取消等待状态,
		被取消的节点不会再次等待	*/
        static final int CANCELLED =  1;
     //2. 表示我当前这个线程还是个正常的线程,可以让我来竞争锁
        static final int SIGNAL    = -1;
     //3. 涉及到条件队列,为 -2 的时候表示线程在条件队列里面,为0的时候表示在同步队列
     	/*
     	* 条件队列:ArrayBlockingQueue,见下面
     	* 作用就是:条件不成立的时候阻塞线程,条件成立了,唤醒线程,把唤醒的线程
     			  从条件队列移到同步队列
     	* 注意:只有独占锁才会有条件队列
     	*/
        static final int CONDITION = -2;
        static final int PROPAGATE = -3;
// 形容线程的那四个状态,上面四个状态是他的值
        volatile int waitStatus;


// 指向同步队列链表的上一个上一个节点
        volatile Node prev;
// 指向同步队列链表的下一个节点
        volatile Node next;
// 同步队列中当前排队的线程
        volatile Thread thread;

// 条件队列的下一个节点
        Node nextWaiter;
//如果节点处于共享模式下等待直接返回true
        final boolean isShared() {
            return nextWaiter == SHARED;
        }
//返回当前节点的前驱节点,如果为空,直接抛出空指针异常
        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }
//用来建立初始化的head 或 SHARED的标记
        Node() {    // Used to establish initial head or SHARED marker
        }
//指定线程和模式的构造方法
        Node(Thread thread, Node mode) {     // Used by addWaiter
            this.nextWaiter = mode;
            this.thread = thread;
        }
// 指定线程和节点状态的构造方法
        Node(Thread thread, int waitStatus) { // Used by Condition
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }

ArrayBlockingQueue类:

//构造函数
   public ArrayBlockingQueue(int capacity, boolean fair) {
        if (capacity <= 0)
            throw new IllegalArgumentException();
        this.items = new Object[capacity];
        lock = new ReentrantLock(fair);
        // 创建了两个Condition,可以想想生产者消费者
        notEmpty = lock.newCondition(); // 队列为空的时候阻塞
        notFull =  lock.newCondition(); // 队列满了的时候阻塞
    }

// 向队列添加元素
    public void put(E e) throws InterruptedException {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        // 先拿到了锁
        lock.lockInterruptibly();
        try {
         //这句话就是条件,如果队列满了,就阻塞线程
            while (count == items.length) 
                notFull.await();
        } finally {
            lock.unlock();
        }
    }

// 从队列取元素
    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        //先加锁
        lock.lockInterruptibly();
        try {
        // 如果队列为空,就阻塞线程等待
            while (count == 0)
                notEmpty.await();
         // 取元素
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

AQS源码分析的很好的:https://blog.csdn.net/qq_30572275/article/details/80297047

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值