JDK并发编程Actomic和AQS详解

JDK并发编程Actomic和AQS详解

1 Atomic系列优化加锁并发性能

1.0 i++和ActomicInteger之间的差别分析

static Integer i = 0;
static AtomicInteger j = new AtomicInteger(0);

private static void synchronizedAdd(){
    for (int i = 0; i < 10; i++) {
        new Thread() {

            @Override
            public void run() {
                // 10个线程就要依次的慢慢的一个一个的进入锁代码块
                // 然后依次对i变量进行++操作
                // 每次操作完i++就写会到主存
                // 这样并发性很不好,相当于串行化
                synchronized (AtomicIntegerDemo.class){
                    System.out.println(AtomicIntegerDemo.i++);
                }
            }
        }.start();
    }

}

private static void atomicAdd(){
    for (int i = 0; i < 10; i++) {
        new Thread() {

            @Override
            public void run() {
               //先累加1然后获取过来   System.out.println(AtomicIntegerDemo.j.incrementAndGet());
            }
        }.start();
    }
}
这俩个方法实现同样的一个功能,但是,第一种进行加锁的方式,导致线程串行化的一种操作,同一时间只能有一个线程获取到锁,对值进行操作,
第二种方式就不是加锁的效果了,它是用的原子类,他是多个线程并发来运行,可以保证原子性

1.1 AtomicInteger中的CAS无锁化原理

底层是一个无锁化的思想,也是一个乐观锁的思想

判断此时此刻是否是某个值,如果是则修改,如果不是则重新查询一个最新的值,再次执行判断,这个操作叫做CAS(compare and swap) 
Atomic 的层原理就是CAS可以叫做是无锁的,也可以是乐观锁的思想,每次尝试修改的时候,就对比一下有没有人修改过这个值,没有人修改,那么就自己修改,如果有人修改,那么就重新查出最新的值,进行修改,再次重复那个过程

1.2 AtomicInteger源码剖析:仅限JDK内部使用的Unsafe类

private volatile int value;

public AtomicInteger(int initialValue) {
    value = initialValue;
}
这里保证了一个可见性,保证一个线程将值修改后,其他值可以快速的看见它,
atomicInteger.incrementAndGet();
public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
Unsafe类是在JDK底层的一个类,而且话人家限制好了,不允许你实例化它以及使用它里面的方法的,首先人家的构造函数是私有化的,不能自己手动去实例化它,其次,如果用Unsafe.getUnsafe()方法来获取一个实例也是不行的,再那个源码里,他会判断一下,如果当前是属于我们的用户的应用系统,识别到由我们的类加载器以后,就会报错,不让我们来获取实例

只能是JDK源码里面,JDK自己内部来使用,不是对外的
Unsafe,封装了一些不安全的操作,指针相关的一些操作,就是比较底层了,主要是Atomic原子类底层用到了大量的unsafe

1.3 AtomicInteger源码剖析:无线重复循环以及CAS操作

private static final long valueOffset;

static {
    try {
        valueOffset = unsafe.objectFieldOffset
            (AtomicInteger.class.getDeclaredField("value"));
    } catch (Exception ex) { throw new Error(ex); }
}

private volatile int value;
类初始化的时候,来进行执行的,valueOffset,value这个字段在AtomicInteger这个中的偏移量,在底层类是由自己对应的结构的,无论是在磁盘的.class文件里的,还是在jvm内存中

大概可以理解为:value这个字段具体是在AtomicInteger这个类的哪个位置,offset,偏移量,这个是很底层的操作,是通过unsafe来实现的,刚刚在类初始化的时候就会完成这个操作,一旦初始化完 就不会变更了

public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
	//利用当前对象实例和offset拿到底层当前最新的value的值
	var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}
//会用getIntVolatile方法从AtomicInteger实例,根据valueOffset偏移量,去获取到当前的value的值,知道了value这个字段的位置,获取到当前value的值

compareAndSwapInt(),CAS的方法
它会拿你刚刚这个获取到的var5的值,去跟底层目前这个AtomicInteger的对象实例中的value的值进行比较,如果是一样的话,就会执行swap的这个过程,var5+var4(1),如果获取到的值,和AtomicInteger+valueOffset获取到的当前的值不一样的话,此时compareAndSwapInt方法就会返回false,如果返回false的话,那么就会进行下一轮循环,找到当前value的值,然后进行比对,如果返回var5的值,如果不成功,那么就进入下一轮循环,在外层方法中+1返回

1.4 AtomicInteger源码剖析:底层cpu指令是如何实现CAS语义的

最最底层是用了一个native的代码,不是用java写的,用C写的代码,可以通过发送一些cpu的指令,来确保说CAS的那个过程,绝对是原子的,具体是怎么来实现的?以前的cpu会通过一些指令来锁掉某一小块的内存,后面会做一些优化,它可以保证仅仅有一个线程campare->set,这是一系列的步骤,在执行这个步骤的话,是每个线程是原子的,有一个线程在执行CAS一系列的比较和设置的过程中,其他的线程是不能来执行的,

Cpu指令来实现
Cpu会通过一些轻量级的锁小块内存的机制来实现,保证整个并发的性能要好的多

1.5 Atomic原子类体系的CAS语义存在的三大缺点分析

1ABA问题:如果某个值一开始是A,后来变成了B,然后又变成了A,你本来期望的是值如果是第一个A才会设置新值,结果第二个A一比较也ok,也设置了新值,跟期望是不符合的。所以atomic包里有AtomicStampedReference类,就是会比较两个值的引用是否一致,如果一致,才会设置新值

假设一开始变量i = 1,你先获取这个i的值是1,然后累加了1,变成了2

但是在此期间,别的线程将i -> 1 -> 2 -> 3 -> 1

这个期间,这个值是被人改过的,只不过最后将这个值改成了跟你最早看到的值一样的值
结果你后来去compareAndSet的时候,会发现这个i还是1,就将它设置成了2,就设置成功了

说实话,用AtomicInteger,常见的是计数,所以说一般是不断累加的,所以ABA问题比较少见

2、无限循环问题:大家看源码就知道Atomic类设置值的时候会进入一个无限循环,只要不成功,就不停循环再次尝试,这个在高并发修改一个值的时候其实挺常见的,比如你用AtomicInteger在内存里搞一个原子变量,然后高并发下,多线程频繁修改,其实可能会导致这个compareAndSet()里要循环N次才设置成功,所以还是要考虑到的。导致cpu的负载比较高

JDK 1.8引入的LongAdder来解决,是一个重点,分段CAS思路

3、多变量原子问题:一般的AtomicInteger,只能保证一个变量的原子性,但是如果多个变量呢?你可以用AtomicReference,这个是封装自定义对象的,多个变量可以放一个自定义对象里,然后他会检查这个对象的引用是不是一个。

1.6 LongAdder是如何通过分段CAS机制优化多线程自旋问题

Java 8提供的一个对AtomicLong改进后的一个类,LongAdder

大量线程并发更新一个原子类的时候,天然的一个问题就是自旋,会导致并发性能还是有待提升,比synchronized当然好很多了

分段迁移,某一个线程如果对一个Cell更新的时候,发现说出现了很难更新他的值,出现了多次自旋的一个问题,如果他CAS失败了,自动迁移段,他会去尝试更新别的Cell的值,这样的话就可以让一个线程不会盲目的等待一个cell的值

2 AQS

2.0 尝试一下用另一种锁ReentractLock

可重入锁
我们多线程需要加锁,除了用synchronized关键字,然后加wait+notify的方式,其实 LockApi,来加锁和释放锁
public class ReentractLockDemo {

    static volatile int i =  0;
    static ReentrantLock lock = new ReentrantLock();

    public static void main(String args[]) {

        new Thread(){
            public void run() {
                for (int i = 0; i < 10; i++) {
                    lock.lock();
                    ReentractLockDemo.i++;
                    System.out.println(ReentractLockDemo.i);
                    lock.unlock();
                }
            }
        }.start();

        new Thread(){

            public void run() {
                for (int i = 0; i < 10; i++) {
                    lock.lock();
                    ReentractLockDemo.i++;
                    System.out.println(ReentractLockDemo.i);
                    lock.unlock();
                }
            }
        }.start();
        lock.lock();
        lock.unlock();
    }
}
上边的这种写法相当于是我们加synchronized的这种方式,那么有synchronized为什么还要用lock的这种锁呢?
因为,lock锁API,来加锁和释放锁,可以加读写锁,非常常用,也很有用

2.1 谈谈你对AQS的理解

AQS(AbstractQueuesSynchronizer,抽象队列同步器)
ReentractLock/ReadWriteReentractLock,API底层都是基于AQS来实现的,一般我们自己不直接使用,但是是属于java并发包里的底层的API,专门支撑各种java并发类的底层的逻辑实现

2.2 ReentractLock底层原来是基于AQS实现锁的

public ReentrantLock() {
    sync = new NonfairSync();
}
默认的构造函数这里,创建了一个sync,Nonfairsync,看起来是一个非常关键的一个组件,很可能是底层专门用来底层的加锁和释放锁

public void lock() {
    sync.lock();
}
ReenactLock在进行加锁的时候,他其实是直接基于底层的Sync来实现的lock操作,但是如果是这个样子的话,ReentractLock这个类其实就是比较外层的一个薄薄的封装的一个类,Sync就是ReenactLock底层的核心组件

Sync:关键组件

abstract static class Sync extends AbstractQueuedSynchronizer

Sycn是抽象的静态内部类,是AbstractQueuedSynchronizer的子类,这个东西抽象队列同步器,是java并发包各种并发工具(锁/同步器)的底层的基础性的组件,主要是以来它来实现各种锁
public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
}
这个AQS类里面都有些什么呢?
static final class Node {
}
这个node是自定义数据结构,可以组成一个双向链表,也就是所谓的队列
private volatile int state;
核心变量,加锁/释放锁都是基于state来完成的

Sync就是AQS(子类的实现,多线程同步组件的一个意思)
NonfairSync()非公平的同步实现
它是sync的一个子类,覆盖重写的几个方法

所以我们也可以认为ReentractLock-->Synchronized功能是一样的(可重入加锁)

2.3 AQS如何基于无锁化的CAS机制实现高性能加锁

final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

AQS底层加锁,释放锁,都是大量的基于CAS的操作来实现的,底层是基于NonfairSync的lock操作来实现加锁的

if (compareAndSetState(0, 1))
AQS中有一个核心的变量,state,代表了锁的状态,看一下state是否是0,如果是0,代表没人加锁,那么我就会加锁,那么我就把state改成1CAS可以无锁化的保证一个数值的修改
compareAndSetState(0, 1)
这个相当于是尝试在加锁,底层原来是基于unsafe来实现的,JDK内部使用的API,指针操作,基于CPU指令是实现原子性的CASAtomic元子类系列,也是基于Unsafe来实现CAS操作
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
这行代码可以保证说,在一个原子操作中,如果发现值是我们期望的expect值,说明符合要求,没人修改过,此时可以将这个值设置为update,state如果是0的话,就修改为1,代表加锁成功了

如果加锁成功了,那么就会执行到setExclusiveOwnerThread(Thread.currentThread());设置当前线程自己是加了一个独占锁的线程,标识出来,自己是加锁的线程

2.4 如何巧妙的借助AQS中的state变量实现可重入式加锁

假如说此时,线程1再次进入,可重入加锁
如果是一个线程可重入的加锁,会是什么样子的?如何实现的?
可重入加锁那么if (compareAndSetState(0, 1))就会返回一个false,然后执行到以下代码
 acquire(1);
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
这个方法会走到AQS的方法中去,
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}
首先先得到当前的一个线程,然后获取到state变量值的一个过程
进入到这个方法里面,那么代表之前state一定不是0,才会进入到这里,但是为什么又会有判断c == 0 这个分支,其实就是代码的一个健壮性,怕之前那个state不为0,所以加锁失败了,但是进入到这里,人家再次判断一下,如果state是0,那么再次加速哦,就怕中间有人释放掉锁,一般会走到else,再次判断,如果执行方法的线程就等于加锁的线程,代表是一个线程在可重入的加锁,之前它已经加过锁,但是这里再次的加锁,此时就会将state设置为2

2.5 AQS的本质:为啥叫抽象队列同步器?

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
如果不是一个线程重复加锁的话,那么就会走到同步器这块
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)将当前线程入队阻塞等待
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}
EXCLUSIVE,排他性,独占锁,同一时间只能有一个线程获取到锁,此时是排他锁,
首先将当前线程(线程二)封装成了一个Node,mode = EXCLUSIVE,(排他锁)
Node中包含了一些什么东西
volatile int waitStatus;
如果一个线程无法获取到锁的话,会进入到一个阻塞等待的状态,卡住不动,线程挂起,阻塞的状态又细分为很多种不同的状态;
volatile Node prev;
一个节点可以有上一个节点,prev指针,指向了Node的上一个Node
volatile Node next;
一个节点还可以有下一个节点,next指针,指向了Node的下一个Node
volatile Thread thread;
当前的node里面封装了一个线程
Node nextWaiter;
下一个等待线程

获取不到锁,处于等待状态的线程,会封装为一个Node,而且有指针,最后多个处于阻塞等待状态的线程可以封装为node的这样一个双向链表。其实就是队列的一个实现

2.6 加锁失败的时候如何借助AQS异步入队阻塞等待?

如果加锁失败的话,它会把自己封装成一个Node,Node原来是AQS底层非常关键的一个数据结构,双向链表,估计就会将当前的线程封装为Node入队,阻塞等待释放这个锁
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}
private final boolean compareAndSetHead(Node update) {
    return unsafe.compareAndSwapObject(this, headOffset, null, update);
}
headOffset ->AQS类里,head变量所在的位置,CAS操作,判断一下,head变量是否为null,如果是null的话,就将head设置为空Node节点

//这个方法的主要功能就是将线程state改为signal,然后将线程进行park挂起
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
addWaiter其实就是将线程二加入到双向链表中。
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
首先获取到node的上一个节点,其实是一个空的node
这个地方,其实会再次调用tryAcquire(arg)方法尝试加锁,如果加锁成功,其实会将线程2对应的Node从队列中移除
if (shouldParkAfterFailedAcquire(p, node),如果说再次尝试加锁失败了,那么此时会判断一下,是否需要将当前的线程挂起,阻塞等待,如果是需要的话,此时就会用park操作挂起线程

//尝试重新加锁
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
	//pred指向的是一个空node
	//默认情况下,waitStatus应该是0,或者是空
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        /*
         * This node has already set status asking a release
         * to signal it, so it can safely park.
         */
        return true;
    if (ws > 0) {
        /*
         * Predecessor was cancelled. Skip over predecessors and
         * indicate retry.
         */
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
	//在这里将空Node的waitStatus设置为SIGNAL
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

//挂起当前线程
private final boolean parkAndCheckInterrupt() {
	//将一个线程进行挂起,不让你动了,此时必须嘚有另外一个线程来对当前线程执行
	//unpark操作,唤醒挂起的线程
    LockSupport.park(this);
    return Thread.interrupted();
}

2.7 AQS默认的非公平加锁策略的运作原理

非公平锁和公平锁
分布式锁底层原理,公平必须严格排队,非公平就是随意抢占
非公平,哪怕是有很多的线程在队列中排队,但是当某个线程释放锁的一瞬间,很可能会有其他的晚到的线程突然争抢到锁,就导致先来的线程还在排队

2.8 ReentractLock如何设置公平锁策略以及原理

如果说,你希望每个人过来都要按照顺序排队来加锁,那就是公平锁了,每个人先来后到,先来的人先加锁,只要你在初始化的时候传一个参数(true)
ReentrantLock lock = new ReentrantLock(true);
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}
if (c == 0) {
    if (!hasQueuedPredecessors() &&
        compareAndSetState(0, acquires)) {
        setExclusiveOwnerThread(current);
        return true;
    }
}
公平锁的核心就是那一行代码,如果前面没有排队等待的线程我就尝试加锁,如果有的话,我是不能加锁的

公平锁,任何一个线程过来,先判断一下,当前是否有人在排队,而且是不是自己在排队,如果不是的话,那么有别人在排队,那么自己不能加锁,直接入队等待

2.9 tryLock如何实现加锁等待一段时间后放弃

Boolean result = lock.tryLock(10, TimeUnit.SECONDS);
if (result){
    
}else {
    //代表加锁失败
}
等待10s,如果超时那么就加锁失败,在实际开发的时候,我们可能会用到tryLock操作,有的时候不希望一直阻塞在那里尝试加锁

2.10 基于AQS实现可重入锁释放源码剖析

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

释放锁,线程1加了锁,总得释放
如何把锁给释放掉,另外一个是如果锁彻底释放了以后,如何让队列中的对头的那个线程来唤醒尝试获取锁
protected final boolean tryRelease(int releases) {
//c = 2 -1
    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;
}
可重入加锁,加了2次,释放锁1次,再次释放锁
c = 1-1 = 0

2.11 锁释放后如何对AQS队列中唤醒阻塞线程尝试抢占锁

unparkSuccessor(h);
private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}
如果一个线程来释放锁的话,他除了更新state和锁占有线程以外,它其实就是用LockSupport的unpark操作来唤醒了一个处于队头的线程
队头的线程此时被unpark唤醒后要干什么
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}
线程是在上边的那个方法被挂起的,当它被唤醒就是返回了一个线程的interrupted
for (;;) {
    final Node p = node.predecessor();
    if (p == head && tryAcquire(arg)) {
        setHead(node);
        p.next = null; // help GC
        failed = false;
        return interrupted;
    }
    if (shouldParkAfterFailedAcquire(p, node) &&
        parkAndCheckInterrupt())
        interrupted = true;
}
如果一旦被unpark唤醒后,就会在这里苏醒过来,然后重新进入for循环中再次执行
此时线程二会再次尝试获取锁,因为它是队头的线程,

2.12 读锁和写锁分开

synchronizedReentrantLock是一样的,如果真正的开发推荐首先用synchronized,
Lock API的话,就是使用的ReentrantReadWriteLock比较多,读写锁,可以加读锁,也可以加写锁,但是读锁和写锁是互斥的,也就是说,你加了写锁后就不能加写锁,如果加了写锁就不能加读锁
但是如果有人加了读锁之后,别人可以同时加读锁,
如果你有一份数据,有人读,有人写,如果你全部都是用synchronized的话,会导致如果多个人读,也是要串行化的,一个接一个的读
我们希望的效果是,多个人都可以同时来读,如果使用读锁和写锁分开的方式,就可以让多个人来读数据,因为多个人可以同时来加读锁,如果有人在读数据,就不能有人写数据,读锁和写锁是互斥的,如果有人在写数据,别人不能写数据,写锁和写锁也是互斥的
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
lock.readLock().lock();
lock.readLock().unlock();
lock.writeLock().lock();
lock.writeLock().unlock();

2.13 读写锁中的谢锁是如何基于AQS的state变量完成加锁的

protected final boolean tryAcquire(int acquires) {
    Thread current = Thread.currentThread();
//首先获取到一个state默认是0
    int c = getState();
//人家读写锁,非常聪明的利用state的值,二进制值里面的高低16位分别代表了读锁和写锁,AQS就一个,state怎么分成俩个锁
//state二进制值的高16位代表了读锁,低16位代表了写锁
//可以认为下边的w就是c(二进制值)通过位运算,获取到了state的低16位,代表了写锁的状态
    int w = exclusiveCount(c);
//如果c不为0,那么就有人加过锁
    if (c != 0) {
        // (Note: if c != 0 and w == 0 then shared count != 0)
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // Reentrant acquire
        setState(c + acquires);
        return true;
    }
//非公平锁,此时一定会去尝试加锁
//如果是公平锁,此时会判断,如果队列有等待的线程,那么就不加锁
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}

2.14 基于AQS的state二进制高低16位判断实现写锁的可重入加锁

写锁的可重入加锁,state的二进制的高低16位的一个判断
c!= 0 ,w==0 c肯定不是0,但是低16位是0,说明有人加了读锁,没有人加写锁,此时你要加写锁,你还不是加锁的线程,那么就直接返回,因为这里是互斥的
c!= 0 ,w==0,那么有人加过锁,之前加的是写锁,但是当前线程不是之前加锁的那个线程,此时也不让你加锁,同一时间,只能有一个线程加写锁,如果线程1加了写锁,线程二也要加写锁是不行的
c!= 0 ,w==0,那么有人加过锁,之前加的是写锁,而且加写锁的是你自己如果加写锁的是你自己,那么你就是在可重入的加写锁setState(c + acquires);此时将state+=1
if (c != 0) {
    // (Note: if c != 0 and w == 0 then shared count != 0)
    if (w == 0 || current != getExclusiveOwnerThread())
        return false;
    if (w + exclusiveCount(acquires) > MAX_COUNT)
        throw new Error("Maximum lock count exceeded");
    // Reentrant acquire
    setState(c + acquires);
    return true;
}

2.15 写锁加锁失败时如何基于AQS队列完成入队阻塞等待?

线程1加了俩次写锁,此时线程2来加锁会出现什么情况?

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
线程2如果来加写锁,那么会被互斥,会被卡住,阻塞,在队列中等待唤醒

2.16 读写锁互斥:基于AQS的state二进制高低16位完成互斥判断

读锁源码
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}
protected final int tryAcquireShared(int unused) {
    Thread current = Thread.currentThread();
    int c = getState();
//exclusiveCount(c):获取的是state的低16位,代表写锁的状态值,如果不等于0,说明有人加了写锁,此时是 不能加写锁的 ,而且还不是你加的写锁,此时是不能加读锁的,此时返回-1,那么就走到doAcquireShared(arg);
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    int r = sharedCount(c);
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        compareAndSetState(c, c + SHARED_UNIT)) {
        if (r == 0) {
            firstReader = current;
            firstReaderHoldCount = 1;
        } else if (firstReader == current) {
            firstReaderHoldCount++;
        } else {
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != getThreadId(current))
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
            rh.count++;
        }
        return 1;
    }
    return fullTryAcquireShared(current);
}
private void doAcquireShared(int arg) {
//将线程3入队
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

2.17 释放写锁的源码剖析以及对AQS队列唤醒阻塞线程

public void unlock() {
    sync.release(1);
}
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
protected final boolean tryRelease(int releases) {
//首先判断一下是否是自己的那个线程
if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
//然后对state-1
    int nextc = getState() - releases;
//判断state是否是1
    boolean free = exclusiveCount(nextc) == 0;
    if (free)
        setExclusiveOwnerThread(null);
    setState(nextc);
    return free;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
[JAVA工程师必会知识点之并发编程]1、现在几乎100%的公司面试都必须面试并发编程,尤其是互联网公司,对于并发编程的要求更高,并发编程能力已经成为职场敲门砖。2、现在已经是移动互联和大数据时代,对于应用程序的性能、处理能力、处理时效性要求更高了,传统的串行化编程无法充分利用现有的服务器性能。3、并发编程是几乎所有框架的底层基础,掌握好并发编程更有利于我们学习各种框架。想要让自己的程序执行、接口响应、批处理效率更高,必须使用并发编程。4、并发编程高级程序员的标配,是拿高薪的必备条件。 【主讲讲师】尹洪亮Kevin:现任职某互联网公司首席架构师,负责系统架构、项目群管理、产品研发工作。10余年软件行业经验,具有数百个线上项目实战经验。擅长JAVA技术栈、高并发高可用伸缩式微服务架构、DevOps。主导研发的蜂巢微服务架构已经成功支撑数百个微服务稳定运行【推荐你学习这门课的理由:知识体系完整+丰富学习资料】1、 本课程总计122课时,由五大体系组成,目的是让你一次性搞定并发编程。分别是并发编程基础、进阶、精通篇、Disruptor高并发框架、RateLimiter高并发访问限流吗,BAT员工也在学。2、课程附带附带3个项目源码,几百个课程示例,5个高清PDF课件。3、本课程0基础入门,从进程、线程、JVM开始讲起,每一个章节只专注于一个知识点,每个章节均有代码实例。 【课程分为基础篇、进阶篇、高级篇】一、基础篇基础篇从进程与线程、内存、CPU时间片轮训讲起,包含线程的3种创建方法、可视化观察线程、join、sleep、yield、interrupt,Synchronized、重入锁、对象锁、类锁、wait、notify、线程上下文切换、守护线程、阻塞式安全队列等内容。二、进阶篇进阶篇课程涵盖volatied关键字、Actomic类、可见性、原子性、ThreadLocal、Unsafe底层、同步类容器、并发类容器、5种并发队列、COW容器、InheritableThreadLocal源码解析等内容。三、精通篇精通篇课程涵盖JUC下的核心工具类,CountDownLath、CyclicBarrier、Phaser、Semaphore、Exchanger、ReentrantLock、ReentrantReadWriteLock、StampedLock、LockSupport、AQS底层、悲观锁、乐观锁、自旋锁、公平锁、非公平锁、排它锁、共享锁、重入锁、线程池、CachedThreadPool、FixedThreadPool、ScheduledThreadPool、SingleThreadExecutor、自定义线程池、ThreadFactory、线程池切面编程、线程池动态管理等内容,高并发设计模式,Future模式、Master Worker模式、CompletionService、ForkJoin等课程还包含Disruptor高并发无锁框架讲解:Disruptor支持每秒600万订单处理的恐怖能力。深入到底层原理和开发模式,让你又懂又会用。高并发访问限流讲解:涵盖木桶算法、令牌桶算法、Google RateLimiter限流开发、Apache JMeter压力测试实战。 【学完后我将达到什么水平?】1、 吊打一切并发编程相关的笔试题、面试题。2、 重构自己并发编程的体系知识,不再谈并发色变。3、 精准掌握JAVA各种并发工具类、方法、关键字的原理和使用。4、 轻松上手写出更高效、更优雅的并发程序,在工作能够提出更多的解决方案。  【面向人群】1、 总感觉并发编程很难、很复杂、不敢学习的人群。2、 准备跳槽、找工作、拿高薪的程序员。3、 希望提高自己的编程能力,开发出更高效、性能更强劲系统的人群。4、 想要快速、系统化、精准掌握并发编程的人群。【课程知识体系图】

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值