AQS 并发灵活人物

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
为什么需要AQS
在这里插入图片描述
理解相似点:比如说,锁只能允许一个线程,而Semaphore,只允许一定量的线程。比如说:他们还可以去看一看目前是不是陷入等待,tryLock() tryQuire()方法,还有在一定时间内尝试获取。
在这里插入图片描述
在这里插入图片描述
代码演示:
在这里插入图片描述
可以看到他有一个Sync内部类。进入这个内部类,可以看到它继承了AQS(这是类的缩写)
在这里插入图片描述
结论:在Semaphore中,有一个Sync内部类,这个内部类是继承了AQS
其实CountDownLatch也是这样的;
在这里插入图片描述
其实,在ReentrantLock()中,其实也是这样的套路
在这里插入图片描述
所以说,AQS是如何使用呢,就是把这个类放到了要实现类的内部,作为内部实现类。
AQS的功能:1.同步状态的原子性处理,线程的阻塞与解除阻塞,队列的管理。
在这里插入图片描述
在这里插入图片描述
AQS是一个用于构建锁类时的工具类,它解决了大量细节问题,比如说:等待线程用先进先出的操作,以及用一些标准来判断这个线程是应该等待还是不应该等待,也会帮我们处理,在多个地方的竞争问题,提高吞吐量,提高性能。总之,有了AQS,构建线程协作类就容易多了。
在这里插入图片描述
我们先来看一下这个类:
在这里插入图片描述
这个类是抽象类,点一下左边这个图标就可以看到他有很多实现。
在这里插入图片描述
其中FairSync :代表的是公平实现; NonfairSync:代表的是非公平实现。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
比如说,compareAndSetState():
在这里插入图片描述
这里是用了unsafe里面的cas操作操作,通过底层指令的原子性,来保证了这个操作的原子性。
在这里插入图片描述

在ReentrantLock中,state代表锁的占有情况,如果为0,代表他还没被占有,如果为1,说明该锁被占有了;如果为2说明这个锁,在该线程里被占有了多次。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

对于,这部分内容,AQS就可以被当做是排队管理器,当多个线程争取同一把锁时,将那些没能拿到锁的线程串在一起。
head是目前拿到锁的线程,而后面这些就被阻塞了。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
比如说ReentrantLock 获取到state变量大于0的时候,就会导致阻塞;
Semaphore中,acquire获取到state大于0时,就可以执行;
CountDownLatch里面,如果await获取到数字大于0 ,就会阻塞;
在这里插入图片描述
Semaphore中,释放方法,就是让state加一 ,在CountDownLatch中,countDown方法就是让数减一,不同的实现类方法不同,需要自己去实现。
在这里插入图片描述
在讲完线程获取方法和释放方法以后,还需要线程实现类去自己实现一些方法。
比如说:
在这里插入图片描述
在CountDownLatch中,就有tryAcquireShared 和 tryReleaseShared方法。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在CountDownLatch中,对AQS进行分析:
在这里插入图片描述
在这里插入图片描述
构造器分析:
在这里插入图片描述
我们创建CountDownLatch时,会传入一个值,就是CountDownLatch要倒数的数量,也是Sync中的state值;
在这里插入图片描述
这个就是返回state;
在这里插入图片描述
进入acquireSharedInterruptibly方法:
在这里插入图片描述
然后在,CountDownLatch中查看这个方法(如果直接点进去是不行的,要在他本类上进行查看)
在这里插入图片描述
可以看到,如果等于0就为1;如果大于0。则为-1;如果大于0,返回-1。然后就执行下面语句,
在这里插入图片描述
就会入队进行等待,就是将线程阻塞起来。
下面查看countDown()方法
在这里插入图片描述
在这里插入图片描述
然后点击tryReleaseShared
在这里插入图片描述
发现里面没有什么内容,这个时候就说明,它的上一个类中,对它会有实现;
在这里插入图片描述
发现在Sync中果然有tryReleaseShared的实现;所以,应该查看这个方法;
在这里插入图片描述
会用一个for循环来做cas自旋;
如果等于0说明被人释放过了,就不需要释放了,就返回false;
如果不等于0 就先计算c-1之后的结果;
再用compareAndSetState 即cas的方法把这个值给更新回去;如果发现没被别的线程更新过,那么就直接更新
如果发现被别人更新过了,它就会继续for循环,这时候,它获取到的c会变得不一样,然后nextc也会不一样;
如果nextc == 0 说明这个闸门就打开了 就返回true;如果返回true,就会把这群等待的线程进行唤醒
在这里插入图片描述
await()方法介绍:(如果state等于0就放行,如果不等于0就堵塞)
在这里插入图片描述
在这里插入图片描述
如果state不为0,则返回结果为-1;
就调用doAcquireSharedInterruptibly()方法;
在这里插入图片描述
这个方法就是堵塞线程的作用,在parkAndCheckInterrupt()中,是堵塞线程的操作,到最后面就是native方法 native方法,native方法就是告知JVM调用,该方法在外部定义,我们将使用C语言或者C++语言来实现;
在这里插入图片描述
AQS在Semaphore的应用
state:代表许可证还剩几个
进入Semaphore查看获取许可证
在这里插入图片描述
再进入,发现还是会调用tryAcquireShared方法。这个方法在Semaphore中进行了重写;
在这里插入图片描述
在这里插入图片描述
如果这个方法返回值小于0,那么他就会让线程进入到等待队列;这个是公平的情况;
先看一下有没有等待队列,如果有等待队列的话,说明肯定轮不到我了,因为这个是公平的情况;所以,直接进入等待。如果没有等待队列,就先获取剩余许可证的数量,然后求出剩余许可证减去需要许可证的值,如果这个值小于零,返回这个值,进入等待,如果这个值不小于0就进行CAS操作,假设它操作成功,然后返回这个值,然后进行获取。获取的操作在这个方法之上。不写在这个方法里;(CAS也是有可能失败了,但是它会重新进入for循环)
在这里插入图片描述
AQS在ReentrantLock的应用
最重要的方法就是加锁和解锁;
首先先看一下解锁方法;
在这里插入图片描述
在release中,它调用了tryRealse()方法;
在这里插入图片描述
在这里插入图片描述
在这个方法里面,它会先去看一下这个线程是不是持有锁的线程,只有持有锁的线程才可以进行解锁,如果不是,就不可以进行解锁,抛出一个异常;
如果是持有锁的线程,它会把获取它持有锁的次数,然后要减去释放的次数;如果持有锁的次数减去释放的次数不等于零,那么就setState©;如果等于零,就对其进行释放;free代表当前线程是否有锁,setExclusiveOwnerThread(null)持有这把锁的持有线程设置为null;
如果返回为true,就说明已经释放了;就执行下面的语句;
在这里插入图片描述
唤醒下面在等待的锁;unparkSuccessor uppark代表的是唤醒;
下面我们来看一下lock()
在这里插入图片描述
发现这个锁是抽象方法,因为根据我们公平与不公平,会有两种锁的策略;
在这里插入图片描述
点击ctrl + alt +鼠标左键,我们以非公平为例来进行查看;
在这里插入图片描述
它首先会进行cas操作;即当前没有任何人持有的时候,拿到这把锁;如果cas不成功,这把锁被别的线程拿走了;
在这里插入图片描述
我们就在该类的非公平下查看一下在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
首先,会获取state()如果等于0说明没人锁,就通过cas来获取锁,通常情况是不等于0的,这时候,它会进行判断,判断,当前获得锁的线程是不是和我自己的线程是同一个锁,如果是同一个锁的话,先判断是不是溢出 overflow ;如果不是溢出的话,就给当前锁的数量进行加一,多重入了一次;在返回true;如果不是当前线程,那么就获取锁失败;这时候就返回执行以下语句,将线程放入到等待队列中;
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
代码如下:

package aqs;

import java.util.concurrent.locks.AbstractQueuedLongSynchronizer;

/**
 * 描述: 自己用AQS实现一个简单的线程协作器
 * 起初门栓是关闭的,很多线程想要调用await()方法,会失败
 * 然后有线程会调用signal()方法,这时候,之前等待的线程就会释放;
 */
public class OneShotLatch {
    private final Sync sync = new Sync();
    // 释放的方法
    public void signal(){
        sync.releaseShared(0);
    }
    // 尝试获取锁
    public void await(){
        sync.acquireShared(0);
    }
    private class Sync extends AbstractQueuedLongSynchronizer{
        // 这个方法是可以自己实现的,如果等于1,说明门就打开了
        @Override
        protected long tryAcquireShared(long arg) {
            return getState() == 1 ? 1 : -1;
        }

        @Override
        protected boolean tryReleaseShared(long arg) {
            setState(1);
            return true;
        }
    }

    public static void main(String[] args) {
        OneShotLatch oneShotLatch = new OneShotLatch();
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread() + "尝试获取latch,获取失败就等待");
                    oneShotLatch.await();
                    System.out.println(Thread.currentThread() + "开闸,继续运行");
                }
            }).start();
        }
        try {
            Thread.sleep(5000);
            oneShotLatch.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread() + "尝试获取latch,获取失败就等待");
                oneShotLatch.await();
                System.out.println(Thread.currentThread() + "开闸,继续运行");
            }
        }).start();
    }
}

先分析一下该类:
在这里插入图片描述
进入执行的方法acquireShared(因为是共享锁,所以调用acqureShared,如果是独享锁,例ReentrantLock就是调用acquire的);发现执行tryAcquireShared方法,如果返回值小于0就入队;
在这里插入图片描述
然后我们对其重写,当状态为1的时候,执行,否则就入队;在这里插入图片描述
然后,现在进行分析释放的方法;
在这里插入图片描述
释放的方法,反正就是调用Sync的方法
在这里插入图片描述
这里面的tryReleaseShared是需要进行重写的;如果返回true Sync就会让等待的线程进行释放;
在这里插入图片描述
我这边重写是当调用的时候,设置为1,然后返回true;
这样,一个尝试获得锁,释放的方法就写好了;运行结论如下:
在这里插入图片描述
符合预期;


hashmap
// 默认标准为16
HashMap<String,String> hashMap = new HashMap<>();

底层源码:
1.7 : 数组 + 链表
1.8 : 数组 + 链表 + (红黑树)

为什么需要使用数组、链表、红黑树呢?
1.使用数组对于指定下标的查找,时间复杂度为O(1),一般加入数据,通过其hashCode(),通过特殊的计算(取模),来确定指定的下标;
2.链表:插入很快,但是,查找很慢;
3.红黑树:查找和删除平均复杂度都为O(logn);

创建hashmap时候,并没有创建出对象,只是初始化参数而已;
1.7 put():过程
第一步:先判断数组有没有初始化;如果,是空的,就进行初始化;
如何初始化数组?
默认的初始容量是16,如果,你传入的初始容量不是2的指数次幂;那么他就会转成2的指数次幂(大于当前你传入的值,然后,是最小的2的指数次幂);
为什么需要2的指数次方? 因为,下面通过hash来判断存储数组下标时,是使用tab[(length - 1) & hash])来判断的,如果,不是2的指数次方,就不能使用位运算来进行了;

if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;

第二步:创建一个数组;执行put();

putVal(hash(key), key, value, false, true);
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        // 1.如果tab为null
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
            // 使用 位运算会快很多
            // 2.如果,当前加入的数组当前位置为null
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            // 3.如果,加入数组的位置不为null,但是key相同;那么就覆盖并返回oldValue
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
             // 4.如果是红黑树   
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
                // 5.如果是链表
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

put() 到那个位置是不清楚的,它会通过hash来判断存储的位置,就通过hash值和15进行与操作,来判断他的存储位置(以15为例);如果,当前数组该位置为null;就新建Node()来进行加入;

if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);

如果,在当前数组中位置,已经有数据了;那么,首先会进行判断,判断是不是key相同;如果key相同,会执行下面这个语句,返回oldValue;

			// 如果hash相同,并且key相同
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;

            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                    // 进行新节点的替换
                afterNodeAccess(e);
                return oldValue;
            }

扩容: java7扩容时,会把原来数组中数据,重新进行计算hash,重新进行存储; 而java8,是不需要rehash的;

    final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                    // luguobao :resize() 链表部分
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            // 1.判断,hash和原来数组的长度进行&;
                            // 2.如果是0
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

hashMap8 如何解决死锁?

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值