java.util.current 学习

转自:https://blog.csdn.net/furingsnill/article/details/80986521

JAVA current包学习

一. collction 并发容器相关
    1.BlockingQueue接口
        是一个线程安全的 存取实例的队列
        一个线程将会持续生产新对象并将其插入到队列之中,直到队列达到它所能容纳的临界点。也就是说,它是有限的。
        如果该阻塞队列到达了其临界点,负责生产的线程将会在往里边插入新对象时发生阻塞。它会一直处于阻塞之中,直到负责消费的线程从队列中拿走一个对象。

                抛异常      特定值       阻塞        超时
        插入     add(o)      offer(o)    put(o)      offer(o, timeout, timeunit)
        移除     remove(o)   poll(o)     take(o)     poll(timeout, timeunit)
        检查     element(o)  peek(o)

        抛异常:如果试图的操作无法立即执行,抛一个异常。
        特定值:如果试图的操作无法立即执行,返回一个特定的值(常常是 true / false)。
        阻塞:如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行。
        超时:如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行,但等待时间不会超过给定值。返回一个特定值以告知该操作是否成功(典型的是 true / false)。

        实现:
            ①. ArrayBlockingQueue
                数组 + ReentrantLock + two condition实现(notempty + notfull生产者消费者模型)

            ②. DelayQueue 延迟队列
                DelayQueue是一个无界阻塞队列,只有在延迟期满时才能从中提取元素。该队列的头部是延迟期满后保存时间最长的Delayed 元素;
                使用优先队列(PriorityQueue)实现的BlockingQueue,优先队列的比较基准值是时间;
                DelayQueue = BlockingQueue +PriorityQueue + Delayed。

            ③. LinkedBlockingQueue 链阻塞队列
                单向链表实现,尾部追加node。
                    private final ReentrantLock takeLock = new ReentrantLock();
                    private final Condition notEmpty = takeLock.newCondition();

                    private final ReentrantLock putLock = new ReentrantLock();
                    private final Condition notFull = putLock.newCondition();
                take锁控制take, put锁控制take。
                count通过AtomicInteger的cas控制原子性。

            ④. PriorityBlockingQueue 具有优先级的阻塞队列
                加锁优先队列(上浮下沉算法实现)

            ⑤. SynchronousQueue 同步队列
                SynchronousQueue 是一个特殊的队列,它的内部同时只能够容纳单个元素。
                如果该队列已有一元素的话,试图向队列中插入一个新元素的线程将会阻塞,直到另一个线程将该元素从队列中抽走。
                同样,如果该队列为空,试图向队列中抽取一个元素的线程将会阻塞,直到另一个线程向队列中插入了一条新的元素。

    2. BlockingDeque接口 extends BlockingQueue接口  阻塞双端队列(Blocking Double Ended Queue)
        双端队列是一个你可以从任意一端插入或者抽取元素的队列
                   抛异常                特定值            阻塞             超时
        插入         addFirst(o)            offerFirst(o)  putFirst(o)        offerFirst(o, timeout, timeunit)
        移除         removeFirst(o)     pollFirst(o)   takeFirst(o)   pollFirst(timeout, timeunit)
        检查         getFirst(o)            peekFirst(o)

                    抛异常                特定值            阻塞             超时
        插入         addLast(o)         offerLast(o)   putLast(o)     offerLast(o, timeout, timeunit)
        移除         removeLast(o)      pollLast(o)        takeLast(o)        pollLast(timeout, timeunit)
        检查         getLast(o)         peekLast(o)

        抛异常:如果试图的操作无法立即执行,抛一个异常。
        特定值:如果试图的操作无法立即执行,返回一个特定的值(常常是 true / false)。
        阻塞:如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行。
        超时:如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行,但等待时间不会超过给定值。返回一个特定值以告知该操作是否成功(典型的是 true / false)

        该接口只有一个实现类:
        LinkedBlockingDeque
        非线程安全也有数组实现

    3. ConcurrentMap接口
        符号表的并发实现对应Map接口

        ①ConcurrentHashMap

            final Segment<K,V>[] segments;

            //...

            static final class HashEntry<K,V> {
                final int hash;
                final K key;
                volatile V value;
                volatile HashEntry<K,V> next;
                //...
            }

            static final class Segment<K,V> extends ReentrantLock implements Serializable {
                // ...
                transient volatile HashEntry<K,V>[] table;

                put(key, value){
                    tryLock; //如果没有其他线程在更新该段,那么直接put。否则轮询请求锁,直至获得锁
                    hash -> key.hash();
                    int index = (table.length - 1) & hash; //此处为key在Segment中的索引
                    HashEntry<K,V> first = entryAt(table, index); //通过索引找到元素
                    //first为链表中第一个元素
                    //1.如果key和first的key相同, 替换原来值。
                    //2.如果key和first中key不相同,first = first.next,循环比较;
                    //3.如果遍历完整个链表都没有相同的KEY,则新增链表结点node, node.next = first;
                    //4.插入node结点到segment
                    //    if (c > threshold && tab.length < MAXIMUM_CAPACITY) // threshold为segment中的元素总个数
                            rehash(node);
                            //segmennt hashtable重算, 索引算法:
                            //int newCapacity = oldCapacity << 1;
                            //int sizeMask = newCapacity - 1;
                            //int idx = e.hash & sizeMask;

                    //    else
                    //      setEntryAt(tab, index, node);//node与table关联
                    // jdk1.8若链表长度达到指定值(>=8),使用红黑树数据结构存储数据
                    unlock;
                }
            }

    4. ConcurrentNavigableMap接口
        跳跃表:
        时间复杂度O(logn),基于链表,链表查找O(logn)
        跳跃表有如下特点:
            (1) 每个跳跃表由很多层结构组成;
            (2) 每一层都是一个有序链表,且第一个节点是头节点;
            (3) 最底层的有序链表包含所有节点;
            (4) 每个节点可能有多个指针,这与节点所包含的层数有关;
            (5) 跳跃表的查找、插入、删除的时间复杂度均为O(log N)。
        插入的过程包括如下4个步骤:
             1、首先,需要找到每一层要插入节点的位置,并保存(用于后续调整指针);
             2、确定该节点包含的层数,初始化要插入的节点;
             3、相关的指针的调整;
             4、若跳跃表层数增加,需要调整Header节点。

        ConcurrentSkipListMap源码解读:
        https://blog.csdn.net/vickyway/article/details/49507615

        static final class Node<K,V> {
            final K key;
            volatile Object value;
            volatile Node<K,V> next;
        }

        static class Index<K,V> {
            final Node<K,V> node;
            final Index<K,V> down;
            volatile Index<K,V> right;
        }

        通过findPredecessor():返回该小于该key的最接近结点

    5. 其他并发容器:
       1. CopyOnWriteArrayList
              适用于读操作远远多于写操作,并且数据量较小的情况。
              由于修改容器都会复制数组,从而当数组超大时修改容器效率很低,修改容器的代价是昂贵的,因此建议批量增加addAll、批量删除removeAll。
              public CopyOnWriteArrayList{

                  transient final ReentrantLock lock = new ReentrantLock();
                  private volatile transient Object[] array;
                  System.arraycopy(elements, 0, newElements, 0, index);
                  System.arraycopy(elements, index, newElements, index + 1, numMoved);
              }

       2. CopyOnWriteArraySet,ConcurrentSkipListSet
            CopyOnWriteArraySet : 它内部包含了一个CopyOnWriteArrayList,因此本质上是由CopyOnWriteArrayList实现的。
            ConcurrentSkipListSet:相当于线程安全的TreeSet。它是有序的Set。它由ConcurrentSkipListMap实现。

       3. ConcurrentLinkedQueue, ConcurrentLinkedDeque
            volatile + cas 实现

二. executor 线程池相关
    Executor框架包括:线程池,Executor,Executors,ExecutorService,CompletionService,Future,Callable等。

        1. Executor 接口
            Executor{
                void execute(Runnable command);
            }

        2. Executors 工厂类, 线程管理
            public static ExecutorService newFixedThreadPool(int nThreads)
            创建固定数目线程的线程池。

            public static ExecutorService newCachedThreadPool()
            创建一个可缓存的线程池,调用execute将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线 程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。

            public static ExecutorService newSingleThreadExecutor()
            创建一个单线程化的Executor。

            public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
            创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。

        3. ExecutorService 执行器服务 extends executor
            定义了一些生命周期的方法
            ExecutorService exec = Executors.newSingleThreadExecutor();

            执行Runnable任务
            exec.execute(new RunbleImpl());

            执行Callable任务
            Future<String> future = exec.submit(new TaskWithResult(i));

        4. ThreadPoolExecutor 自定义线程池 线程池执行者
            //创建等待队列
            BlockingQueue<Runnable> bqueue = new ArrayBlockingQueue<Runnable>(20);

            //创建线程池,池中保存的线程数为3,允许的最大线程数为5
            ThreadPoolExecutor pool = new ThreadPoolExecutor(3,5,50,TimeUnit.MILLISECONDS,bqueue);

        5. ForkJoinPool 分叉和合并
            ForkJoinPool extends AbstractExecutorService implements ExecutorService
            它使用了一个无限队列来保存需要执行的任务,而线程的数量则是通过构造函数传入,如果没有向构造函数中传入希望的线程数量,
            那么当前计算机可用的CPU数量会被设置为线程数量作为默认值。

            主要用来使用分治法(Divide-and-Conquer Algorithm)来解决问题
            ForkJoinPool的优势在于,可以充分利用多cpu,多核cpu的优势,把一个任务拆分成多个“小任务”,
            把多个“小任务”放到多个处理器核心上并行执行;当多个“小任务”执行完成之后,再将这些执行结果合并起来即可。

            使用ForkJoinPool能够使用数量有限的线程来完成非常多的具有父子关系的任务,比如使用4个线程来完成超过200万个任务。
            但是,使用ThreadPoolExecutor时,是不可能完成的,因为ThreadPoolExecutor中的Thread无法选择优先执行子任务,需要完成200万个具有父子关系的任务时,
            也需要200万个线程,显然这是不可行的。
            ps:ForkJoinPool在执行过程中,会创建大量的子任务,导致GC进行垃圾回收,这些是需要注意的。

三. locks 显式锁(互斥锁和速写锁)相关
    1. ReentrantLock 重入锁
        简单来说,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放.
        重入锁的性能远远好于synchronized关键字,JDK6.0版本之后synchronized 得到了大量的优化,二者性能也不分伯仲,但是重入锁是可以完全替代synchronized关键字的。
        除此之外,重入锁又自带一系列高逼格UBFF:可中断响应、锁申请等待限时、公平锁。另外可以结合Condition来使用。
        public static ReentrantLock lock = new ReentrantLock();
        lock.lock();
        lock.unlock();

        1、中断响应
            对于synchronized块来说,要么获取到锁执行,要么持续等待。而重入锁的中断响应功能就合理地避免了这样的情况。
            比如,一个正在等待获取锁的线程被“告知”无须继续等待下去,就可以停止工作了。(使用重入锁如何解决死锁)
            lock.lockInterruptibly();  // 以可以响应中断的方式加锁
            if (lock.isHeldByCurrentThread()) lock.unlock(); //抛出异常,final中判断是否持有锁,若持有则释放。

        2、锁申请等待限时
            可以使用 tryLock()或者tryLock(long timeout, TimeUtil unit) 方法进行一次限时的锁等待。
            后者带有参数,表示在指定时长内获取到锁则继续执行,如果等待指定时长后还没有获取到锁则返回false

        3、公平锁
            所谓公平锁,就是按照时间先后顺序,使先等待的线程先得到锁,而且,公平锁不会产生饥饿锁,也就是只要排队等待,最终能等待到获取锁的机会。
            public ReentrantLock(boolean fair) {
                sync = fair ? new FairSync() : new NonfairSync();
            }
        4.源码解读
            ReentrantLcok  implements AbstractQueuedSynchronizer  implements AbstractOwnableSynchronizer
            ReentrantLcok include sync
            sync subclass FairSync, NonfairSync

            1. AbstractQueuedSynchronizer(AQS)理解:
                https://www.cnblogs.com/micrari/p/6937995.html
                http://ifeve.com/introduce-abstractqueuedsynchronizer/

                AQS(java.util.concurrent.locks.AbstractQueuedSynchronizer)是Doug Lea大师创作的用来构建锁或者其他同步组件(信号量、事件等)的基础框架类。
                JDK中许多并发工具类的内部实现都依赖于AQS,如ReentrantLock, Semaphore, CountDownLatch等等

                AQS内部维护一个CLH队列来管理锁。

                CLH锁:https://blog.csdn.net/chenssy/article/details/50245555
                假设有两个线程(线程A、线程B)。开始线程A需要获得锁,那么他会创建一个QNode节点,并将locked设置为true(表示需要获取锁),
                同时获取一个指向前驱的myPred并在前驱节点的的locked上面旋转直到前驱节点是否锁为止(locked为false,这个动作我们一般称之为自旋),
                当然这里会将tail指向自身来表示它是CLH队列的最后一个节点。
                然后线程B添加到CLH队列中,这时tail域应该指向线程B,B的pre在A的QNODE上自旋。


                线程会首先尝试获取锁,如果失败,则将当前线程以及等待状态等信息包成一个Node节点加到同步队列里。
                接着会不断循环尝试获取锁(条件是当前节点为head的直接后继才会尝试),如果失败则会阻塞自己,直至被唤醒;
                而当持有锁的线程释放锁时,会唤醒队列中的后继线程。

                ReentrantLock: 使用了AQS的独占获取和释放,用state变量记录某个线程获取独占锁的次数,获取锁时+1,释放锁时-1,在获取时会校验线程是否可以获取锁。
                Semaphore: 使用了AQS的共享获取和释放,用state变量作为计数器,只有在大于0时允许线程进入。获取锁时-1,释放锁时+1。
                CountDownLatch: 使用了AQS的共享获取和释放,用state变量作为计数器,在初始化时指定。只要state还大于0,获取共享锁会因为失败而阻塞,直到计数器的值为0时,共享锁才允许获取,所有等待线程会被逐一唤醒。

                获取锁的思路很直接:
                while (不满足获取锁的条件) {
                    把当前线程包装成节点插入同步队列
                    if (需要阻塞当前线程)
                        阻塞当前线程直至被唤醒
                }
                将当前线程从同步队列中移除

                释放锁的过程设计修改同步状态,以及唤醒后继等待线程:

                修改同步状态
                if (修改后的状态允许其他线程获取到锁)
                    唤醒后继线程

                通过上面的AQS大体思路分析,我们可以看到,AQS主要做了三件事情

                同步状态的管理
                线程的阻塞和唤醒
                同步队列的维护

                下面三个protected final方法是AQS中用来访问/修改同步状态的方法:
                int getState(): 获取同步状态
                void setState(): 设置同步状态
                boolean compareAndSetState(int expect, int update):基于CAS,原子设置当前状态

                方法                                 描述
                boolean tryAcquire(int arg)            试获取独占锁
                boolean tryRelease(int arg)            试释放独占锁
                int tryAcquireShared(int arg)      试获取共享锁
                boolean tryReleaseShared(int arg)  试释放共享锁
                boolean isHeldExclusively()            当前线程是否获得了独占锁

                //......

            2. CAS理解 Compare And Swap
                首先,CPU 会将内存中将要被更改的数据与期望的值做比较。
                然后,当这两个值相等时,CPU 才会将内存中的数值替换为新的值。否则便不做操作。
                最后,CPU 会将旧的数值返回。这一系列的操作是原子的。
                它们虽然看似复杂,但却是 Java 5 并发机制优于原有锁机制的根本。
                简单来说,CAS 的含义是“我认为原有的值应该是什么,如果是,则将原有的值更新为新值,否则不做修改,并告诉我原来的值是多少”

                 简单的来说,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则返回V。
                 这是一种乐观锁的思路,它相信在它修改之前,没有其它线程去修改它;
                 而Synchronized是一种悲观锁,它认为在它修改之前,一定会有其它线程去修改它,悲观锁效率很低。

                 有ABA问题(即在更新前的值是A,但在操作过程中被其他线程更新为B,又更新为 A),这时当前线程认为是可以执行的,其实是发生了不一致现象,
                 如果这种不一致对程序有影响(真正有这种影响的场景很少,除非是在变量操作过程中以此变量为标识位做一些其他的事,比如初始化配置),
                 则需要使用AtomicStampedReference(除了对更新前的原值进行比较,也需要用更新前的 stamp标志位来进行比较)。

    2. Condition 条件变量
            public interface Condition {
                void await() throws InterruptedException; // 类似于Object.wait()
                void awaitUninterruptibly(); // 与await()相同,但不会再等待过程中响应中断
                long awaitNanos(long nanosTimeout) throws InterruptedException;
                boolean await(long time, TimeUnit unit) throws InterruptedException;
                boolean awaitUntil(Date deadline) throws InterruptedException;
                void signal(); // 类似于Obejct.notify()
                void signalAll();
            }
            ReentrantLock 实现了Lock接口,可以通过该接口提供的newCondition()方法创建Condition对象.
            public static ReentrantLock lock = new ReentrantLock(true);
            public static Condition condition = lock.newCondition();
            生产者消费者问题:
                Condition notfull = lock.newCondition();
                Condition notEmpty = lock.newCondition();

    3. 读写锁 ReadWriteLock 接口
        实现 ReentrantReadWriteLock
        状态的高16位用作读锁,低16位用作写锁


    4. Lock VS Synchronized
            AbstractQueuedSynchronizer通过构造一个基于阻塞的CLH队列容纳所有的阻塞线程,而对该队列的操作均通过Lock-Free(CAS)操作,但对已经获得锁的线程而言,ReentrantLock实现了偏向锁的功能。
            synchronized的底层也是一个基于CAS操作的等待队列,但JVM实现的更精细,把等待队列分为ContentionList和EntryList,目的是为了降低线程的出列速度;
            当然也实现了偏向锁,从数据结构来说二者设计没有本质区别。但synchronized还实现了自旋锁,并针对不同的系统和硬件体系进行了优化,而Lock则完全依靠系统阻塞挂起等待线程。
            当然Lock比synchronized更适合在应用层扩展,可以继承AbstractQueuedSynchronizer定义各种实现,比如实现读写锁(ReadWriteLock),公平或不公平锁;
            同时,Lock对应的Condition也比wait/notify要方便的多、灵活的多。

        synchronized
        优点:实现简单,语义清晰,便于JVM堆栈跟踪,加锁解锁过程由JVM自动控制,提供了多种优化方案,使用更广泛
        缺点:悲观的排他锁,不能进行高级功能

        lock
        优点:可定时的、可轮询的与可中断的锁获取操作,提供了读写锁、公平锁和非公平锁
        缺点:需手动释放锁unlock,不适合JVM进行堆栈跟踪

        相同点
        都是可重入锁

四. tools 同步工具相关,如信号量、闭锁、栅栏等功能

    1. CountDownLatch闭锁 (重写AQS)
        用来协调多个线程之间的同步,或者说起到线程之间的通信
        CountDownLatch能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。使用一个计数器进行实现。计数器初始值为线程的数量。当每一个线程完成自己任务后,计数器的值就会减一。
        当计数器的值为0时,表示所有的线程都已经完成了任务,然后在CountDownLatch上等待的线程就可以恢复执行任务。

        public void await() throws InterruptedException {
                sync.acquireSharedInterruptibly(1);
        }
        protected int tryAcquireShared(int acquires) {
            (getState() == 0) ? 1 : -1;
        }

        public void countDown() {
                sync.releaseShared(1); //释放共享锁
        }

    2. CyclicBarrier 栅栏 (使用重入锁)
            CyclicBarrier强调的是n个线程,大家相互等待,只要有一个没完成,所有人都得等着。
            CountDownLatch强调一个线程等多个线程完成某件事情。
            CyclicBarrier是多个线程互等,等大家都完成。

        CountDownLatch                                     CyclicBarrier
        减计数方式                                          加计数方式
        计算为0时释放所有等待的线程                         计数达到指定值时释放所有等待线程
        计数为0时,无法重置                                 计数达到指定值时,计数置为0重新开始
        调用countDown()方法计数减一,                        调用await()方法计数加1,若加1后的值不等于构造方法的值,则线程阻塞
        调用await()方法只进行阻塞,对计数没任何影响
        不可重复利用                                         可重复利用

    3. Semaphore 信号量 (重写AQS)
        Semaphore可以控同时访问的线程个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可
        Semaphore其实和锁有点类似,它一般用于控制对某组资源的访问权限。


    4. Exchanger 交换机
        Exchanger可以在两个线程之间交换数据,只能是2个线程,他不支持更多的线程之间互换数据。
        String get = exchanger.exchange(send);


五. atomic 原子变量类相关,是构建非阻塞算法的基础

    1. AtomicInteger 原子性整型
        基于CAS的乐观锁实现

        private volatile int value;

        public final boolean compareAndSet(int expect, int update) {
            return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
        }

        public final int incrementAndGet() {
            for (;;) {// 这样优于while(true)
                int current = get();// 获取当前值
                int next = current + 1;// 设置更新值
                if (compareAndSet(current, next))
                    return next;
            }
        }


    2. AtomicReference 原子性引用型
        private volatile V value;

        public final boolean compareAndSet(V expect, V update) {
            return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
        }
--------------------- 
作者:骨灰级_菜鸟 
来源:CSDN 
原文:https://blog.csdn.net/furingsnill/article/details/80986521 
版权声明:本文为博主原创文章,转载请附上博文链接!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值