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. mysql重启遇到的问题

    不知道是不是每次更新 MySQL 软件之后都需要执行数据库升级指令?在我进行过的几次软件升级之后,总会在 MySQL 的日志中见到 “[ERROR] Missing system table mysq ...

  2. 2019.6.16完成classstack任务

    最终信息 4 ShineEternal 任务完成,账号已注销 120 149 80.537%

  3. 个人永久性免费-Excel催化剂功能第81波-指定单元格区域内容及公式填充

    在日常数据处理过程中,需要对缺失数据进行填充时,按一定逻辑规则进行处理,实现快速填充,规范数据源.此篇给大家带来多种填充数据的场景. 业务使用场景 对各种系统中导出的数据,很多时候存在数据缺失的情况, ...

  4. C语言入门6-选择结构--f语句-switch

    一.     什么是选择结构? 选择结构,也称为分支结构!! 选择结构就是根据    给定的判定条件,判断结果, 并根据  判断的结果   来控制程序的流程 (流程图中,   菱形框 是有来判断的 , ...

  5. python课堂整理16---内置函数

    1. abs :求绝对值 print(abs(-1)) 2. all()传入一个可迭代对象,对该对象进行bool值运算,若都为True 就返回True,有一个为假,就返回False print(all ...

  6. AMD CPU环境下使用android studio,eclipse的Genymotion插件

    1.下载安装VirtualBox Genymotion的运行需要此环境(链接) 2.下载安装android模拟器Genymotion 由于官网的下载速度过慢,建议直接百度下载Genymotion(链接 ...

  7. markdown常用方法

    Markdown格式的普及流行要归功于Github和StackOverflow的流行,随着它们越来越流行,它们支持的Markdown格式也越来越流行. 1.优点 1.Markdown通过内容和样式相分 ...

  8. spring 的权限控制:security

    下面我们将实现关于Spring Security3的一系列教程. 最终的目标是整合Spring Security + Spring3MVC 完成类似于SpringSide3中mini-web的功能. ...

  9. drf之序列化

    在django视图中使用serializer 只是使用serializer类编写API视图,没有用到REST框架 app01下的models.py from django.db import mode ...

  10. MyBatis 简介与入门

    简介 什么是 MyBatis ? MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.My ...