J.U.C并发包(1)

AbstractQueuedSynchronizer

AbstractQueuedSynchronizer是JUC并发包中锁的底层支持,AbstractQueuedSynchronizer是抽象同步队列,简称AQS,是实现同步器的基础组件,并发包中锁的实现底层就是使用AQS实现,

  • 从类图的关系可以看到AQS是一个FIFO的双向队列,内部通过节点head 和 tail 记录队首和队尾元素,队列元素类型为Node。其中Node中Thread变量用来存放进入AQS队列里面的线程
  • Node 节点内部SHARED用来标记该线程是获取共享资源时候被阻塞挂起来后放入AQS队列,
  • EXCLUSIVE标记线程是获取独占资源时候被挂起后放入AQS队列;
  • waitStatus记录当前线程等待状态,分别为CANCELLED(线程被取消了),SIGNAL(线程需要被唤醒),CONDITION(线程在条件队列里面等待),PROPAGATE(释放共享资源时候需要通知其他节点);
  • AQS中维持了一个单一的状态信息state,可以通过getState,setState,compareAndSetState 函数修改其值;对于ReentrantLock 的实现来说,state 可以用来表示当前线程获取锁的可重入次数;
  • pre记录当前节点的前驱节点,next记录当前节点后继节点
  • 调用acquire(int arg)方法获取独占资源,调用release(int arg)方法释放资源;

具体思路:

  • 当多个线程同时调用 lock.lock() 获取锁的时候,同时只有一个线程获取到了该锁,其他线程会被转换为 Node 节点插入到 lock 锁对应的 AQS 阻塞队列里面,并做自旋 CAS 尝试获取锁,前提是head的直接后继;
  • 如果获取到锁的线程又调用了对应的条件变量的 await() 方法,则该线程会释放获取到的锁,并被转换为 Node 节点插入到条件变量对应的条件队列里面;
  • 这时候因为调用 lock.lock() 方法被阻塞到 AQS 队列里面的一个线程会获取到被释放的锁,如果该线程也调用了条件变量的 await()方法则该线程也会被放入条件变量的条件队列;
  • 当另外一个线程调用了条件变量的 signal() 或者 signalAll() 方法时候,会把条件队列里面的一个或者全部 Node 节点移动到 AQS 的阻塞队列里面,等待时机获取锁。

CountDownLatch

他是一个同步辅助类,可以实现类似阻塞当前线程的功能,使用了给定的计数器进行初始化,该计数器操作是原子操作,同一时刻只能有一个线程操作该计数器。

如上图中,TA线程由于await()方法被阻塞,除非前面的线程调用countDown()方法,当计数器为0,TA就可以继续往下执行。计数器不可重置。

        private final static int threadCount = 200;

    public static void main(String[] args) throws Exception {

        ExecutorService exec = Executors.newCachedThreadPool();

        final CountDownLatch countDownLatch = new CountDownLatch(threadCount);

        for (int i = 0; i < threadCount; i++) {
            final int threadNum = i;
            exec.execute(() -> {
                try {
                    test(threadNum);
                } catch (Exception e) {
                    log.error("exception", e);
                } finally {
                    countDownLatch.countDown();
                }
            });
        }
        countDownLatch.await();
        log.info("finish");
        exec.shutdown();
    }     private static void test(int threadNum) throws Exception {
        Thread.sleep(100);
        log.info("{}", threadNum);
        Thread.sleep(100);
    }

Semaphore

他可以控制某个资源可以被多少个线程同时访问,使用Semaphore管理必须要先获取一个许可,执行完毕后释放一个许可,后面的线程才能继续访问,代码演示:

@Slf4j
public class SemaphoreExample1 {     private final static int threadCount = 20;     public static void main(String[] args) throws Exception {         ExecutorService exec = Executors.newCachedThreadPool();         final Semaphore semaphore = new Semaphore(3);         for (int i = 0; i < threadCount; i++) {
            final int threadNum = i;
            exec.execute(() -> {
                try {
                    semaphore.acquire(); // 获取一个许可
                    test(threadNum);
                    semaphore.release(); // 释放一个许可
                } catch (Exception e) {
                    log.error("exception", e);
                }
            });
        }
        exec.shutdown();
    }     private static void test(int threadNum) throws Exception {
        log.info("{}", threadNum);
        Thread.sleep(1000);
    }
}

CyclicBarrier

他是一个同步辅助类,允许一组线程相互等待,直到到达某个公共的屏障点,commonBarrierPoint,CyclicBarrier也具有一个计数器,当计数器达到设置的值,被await()方法阻塞的值会被唤醒,继续执行后续的操作,计数器可以被重置,适合并发情况下需要合并计算的场景。

演示代码如下:

@Slf4j
public class CyclicBarrierExample1 {
    private static Logger log = LoggerFactory.getLogger(CyclicBarrierExample1.class);     private static CyclicBarrier barrier = new CyclicBarrier(5);     public static void main(String[] args) throws Exception {         ExecutorService executor = Executors.newCachedThreadPool();         for (int i = 0; i < 10; i++) {
            final int threadNum = i;
            Thread.sleep(1000);
            executor.execute(() -> {
                try {
                    race(threadNum);
                } catch (Exception e) {
                    log.error("exception", e);
                }
            });
        }
        executor.shutdown();
    }     private static void race(int threadNum) throws Exception {
        Thread.sleep(1000);
        log.info("{} is ready", threadNum);
        barrier.await();
        log.info("{} continue", threadNum);
    }
}

console:

ReentrantLock

 

ReentrantLock(可重入锁)和Synchronize锁的区别

  • 可重入性:二者都是重入锁
  • 锁的实现:ReentrantLock通过jdk实现,synchronize是通过jvm实现,但注意ReentrantLock需要释放锁,而synchronize不需要释放锁,由jvm管理,也就是说synchronize不会产生死锁,而ReentrantLock可能产生死锁。
  • 性能的区别:synchronize未做优化前,ReentrantLock优于synchronize,但synchronize在经过偏向锁,轻量级锁优化后性能就差不多了
  • 功能区别:代码简洁synchronize优于ReentrantLock,锁的细粒度和灵活度ReentrantLock更好。

ReentrantLock独有功能

  • 可指定公平锁还是非公平锁:公平锁(先等待的就先获取锁);
  • 提供了condition类,可以分组唤醒需要唤醒的线程;
  • 提供锁的打断机制,lock.lockInterruptibly()。

ReentrantLock是一种自选锁,内部循环使用CAS操作实现加锁

ReentrantReadWriteLock

ReentrantReadWriteLock是读写锁,维护了一对锁,一个读锁,一个写锁,通过实现ReadWriteLock接口实现了readLock()方法和writeLock()方法。适用于多线程情况下的读写操作,但是要注意如果读操作过于频繁可能会导致写锁饥饿。

StampLock

StampLock有三种控制锁的方式:写,读和乐观读,StampLock会生成票据。

  • 乐观读:乐观的认为写入和读取同时发生的概率很少,因此不悲观的使用读取锁定,程序可以查看读取数据时候遭到写入执行的变更之后,大幅提升程序的性能。
  • 乐观锁和悲观锁实例如下:
public class LockExample4 {

    class Point {
        private double x, y;
        private final StampedLock sl = new StampedLock();         void move(double deltaX, double deltaY) { // an exclusively locked method
            long stamp = sl.writeLock();
            try {
                x += deltaX;
                y += deltaY;
            } finally {
                sl.unlockWrite(stamp);
            }
        }         //下面看看乐观读锁案例
        double distanceFromOrigin() { // A read-only method
            long stamp = sl.tryOptimisticRead(); //获得一个乐观读锁
            double currentX = x, currentY = y;  //将两个字段读入本地局部变量
            if (!sl.validate(stamp)) { //检查发出乐观读锁后同时是否有其他写锁发生?
                stamp = sl.readLock();  //如果没有,我们再次获得一个读悲观锁
                try {
                    currentX = x; // 将两个字段读入本地局部变量
                    currentY = y; // 将两个字段读入本地局部变量
                } finally {
                    sl.unlockRead(stamp);
                }
            }
            return Math.sqrt(currentX * currentX + currentY * currentY);
        }         //下面是悲观读锁案例
        void moveIfAtOrigin(double newX, double newY) { // upgrade
            // Could instead start with optimistic, not read mode
            long stamp = sl.readLock();
            try {
                while (x == 0.0 && y == 0.0) { //循环,检查当前状态是否符合
                    long ws = sl.tryConvertToWriteLock(stamp); //将读锁转为写锁
                    if (ws != 0L) { //这是确认转为写锁是否成功
                        stamp = ws; //如果成功 替换票据
                        x = newX; //进行状态改变
                        y = newY;  //进行状态改变
                        break;
                    } else { //如果不能成功转换为写锁
                        sl.unlockRead(stamp);  //我们显式释放读锁
                        stamp = sl.writeLock();  //显式直接进行写锁 然后再通过循环再试
                    }
                }
            } finally {
                sl.unlock(stamp); //释放读锁或写锁
            }
        }
    }
}

J.U.C并发包(1)的更多相关文章

  1. Java并发包源码学习系列:AbstractQueuedSynchronizer

    目录 本篇学习目标 AQS概述 AbstractOwnableSynchronizer 同步队列与Node节点 同步状态state 重要方法分析 独占式获取与释放同步状态 共享式获取与释放同步状态 A ...

  2. HashMap源码详解与对比

    前几天工作忙得焦头烂额时,同事问了一下关于Map的特性,刹那间懵了一下,紧接着就想起来了一些关于Map的一些知识,因为只要涉及到Collection集合类时,就会谈及Map类,因此理解好Map相关的知 ...

  3. SpringBoot启动流程分析(四):IoC容器的初始化过程

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  4. Java并发编程:用AQS写一把可重入锁

    Java并发编程:自己动手写一把可重入锁详述了如何用synchronized同步的方式来实现一把可重入锁,今天我们来效仿ReentrantLock类用AQS来改写一下这把锁.要想使用AQS为我们服务, ...

  5. AbstractQueuedSynchronizer 源码解读(转载)

    转载文章,拜读了一下原文感觉很不错,转载一下,侵删 链接地址:http://objcoding.com/2019/05/05/aqs-exclusive-lock/ Java并发之AQS源码分析(一) ...

  6. HashMap 源码赏析 JDK8

    一.简介 HashMap源码看过无数遍了,但是总是忘,好记性不如烂笔头. 本文HashMap源码基于JDK8. 文章将全面介绍HashMap的源码及HashMap存在的诸多问题. 开局一张图,先来看看 ...

  7. ConcurrentHashMap源码解析 JDK8

    一.简介 上篇文章详细介绍了HashMap的源码及原理,本文趁热打铁继续分析ConcurrentHashMap的原理. 首先在看本文之前,希望对HashMap有一个详细的了解.不然看直接看Concur ...

  8. Java:并发笔记-06

    Java:并发笔记-06 说明:这是看了 bilibili 上 黑马程序员 的课程 java并发编程 后做的笔记 5. 共享模型之无锁 本章内容 CAS 与 volatile 原子整数 原子引用 原子 ...

  9. j.u.c: Java并发包的5大块

    //TODO Executors: ExecutorService executor = Executors.newFixedThreadPool(10);... newForkJoinPool(). ...

随机推荐

  1. Managing Network Usage

    This lesson describes how to write applications that have fine-grained control over their usage of n ...

  2. 硬件设计--DC/DC电源芯片详解

    本文参考:http://www.elecfans.com/article/83/116/2018/20180207631874.html https://blog.csdn.net/wangdapao ...

  3. I/O:FileChannel

    FileChannel: abstract void force(boolean metaData) :强制将所有对此通道的文件更新写入包含该文件的存储设备中. abstract MappedByte ...

  4. 【题解】【A % B Problem(P1865)】-C++

    题目背景 题目名称是吸引你点进来的 实际上该题还是很水的 题目描述 区间质数个数 输入输出格式 输入格式: 一行两个整数 询问次数n,范围m 接下来n行,每行两个整数 l,r 表示区间 输出格式: 对 ...

  5. python 中_init_函数以及参数self

    1)class类包含: 类的属性:类中所涉及的变量 类的方法:类中函数 2)_init_函数(方法) 1.首先说一下,带有两个下划线开头的函数是声明该属性为私有,不能在类地外部被使用或直接访问. 2. ...

  6. 个人永久性免费-Excel催化剂功能第31波-数量金额分组凑数功能,财务表哥表姐最爱

    在财务工作过程中,很大时候需要使用到凑数的需求,花了两三天时间认真研究了一下,本人水平也只能做代码搬运工,在用户体验上作了一下完善.完成了Excel版的凑数功能. 文章出处说明 原文在简书上发表,再同 ...

  7. C#7.0 新增功能

    连载目录    [已更新最新开发文章,点击查看详细] C# 7.0 向 C# 语言添加了许多新功能 01 out 变量 支持 out 参数的现有语法已在此版本中得到改进. 现在可以在方法调用的参数列表 ...

  8. vscode在win10 / linux下的.vscode文件夹的配置 (c++/c)

    系统方面配置自行查找 linux: launch.json { // 使用 IntelliSense 了解相关属性. // 悬停以查看现有属性的描述. // 欲了解更多信息,请访问: https:// ...

  9. Apache SSI 远程命令执行漏洞复现

    Apache SSI 远程命令执行漏洞复现 一.漏洞描述 当目标服务器开启了SSI与CGI支持,我们就可以上传shtml,利用<!--#exec cmd=”id” -->语法执行命令. 使 ...

  10. NPM - 检查并更新项目依赖的版本

    原文地址:https://acme.top/nodejs-npm-check-updates 前言 经常会遇到 package.json 中的库有更新,但是太多一个一个的来很费事,幸好有个工具 npm ...