一、概述

  队列同步器AbstractQueuedSynchronizer,是用来构建锁或者其他同步组件的基础框架。

1.1、自定义独占锁同步组件

  设计一个同步工具:该工具在同一时刻,只允许一个线程访问,超过一个线程的访问将被阻塞,我们将这个同步器命名为Mutex。

  首先,确定访问模式,MutexLock在同一时刻只支持一个线程的访问,这显然是独占式访问,因此,需要使用AQS提供的acquire(int)方法以及release(int)等方法,这就要求Mutex必须重写tryAcquire(int)和tryRelease(int)方法,才能保证AQS的独占式同步状态的获取与释放方法得以执行。

  其次,定义同步状态(或者资源数),MutexLock在同一时刻只支持一个线程的访问,我们用0表示同步状态未被获取,用其他数值表示已被获取,这里,我们就用1表示,当一个线程进行获取,通过CAS操作将同步状态设置为1,该线程释放时,将同步状态置为0,这样就实现了一个简单的独占式同步组件。MutexLock源码如下:

  1. package com.github.bjlhx15.common.thread.juc.collection;
  2.  
  3. import java.util.concurrent.TimeUnit;
  4. import java.util.concurrent.locks.AbstractQueuedSynchronizer;
  5. import java.util.concurrent.locks.Condition;
  6. import java.util.concurrent.locks.Lock;
  7.  
  8. public class MutexLock implements Lock {
  9. private final Sync sync = new Sync();
  10.  
  11. private static final class Sync extends AbstractQueuedSynchronizer {
  12. // 当状态为0的时候获取锁,通过CAS将状态置为1
  13. public boolean tryAcquire(int acquires) {
  14. if (compareAndSetState(0, 1)) {
  15. setExclusiveOwnerThread(Thread.currentThread());
  16. return true;
  17. }
  18.  
  19. return false;
  20. }
  21.  
  22. // 释放锁,将状态置为0
  23. public boolean tryRelease(int releases) {
  24. if (getState() == 0) {
  25. throw new IllegalMonitorStateException();
  26. }
  27.  
  28. setExclusiveOwnerThread(null);
  29. setState(0);
  30. return true;
  31. }
  32. }
  33.  
  34. @Override
  35. public void lock() {
  36. sync.acquire(1);
  37. }
  38.  
  39. @Override
  40. public void unlock() {
  41. sync.release(1);
  42. }
  43.  
  44. @Override
  45. public void lockInterruptibly() throws InterruptedException {
  46. sync.acquireInterruptibly(1);
  47. }
  48.  
  49. @Override
  50. public Condition newCondition() {
  51. return null;
  52. }
  53.  
  54. @Override
  55. public boolean tryLock() {
  56. return sync.tryAcquire(1);
  57. }
  58.  
  59. @Override
  60. public boolean tryLock(long arg0, TimeUnit arg1) throws InterruptedException {
  61. return sync.tryAcquireNanos(1, arg1.toNanos(arg0));
  62. }
  63. }

  定义了一个静态内部类,该内部类继承了同步器并实现了独占式获取和释放锁的同步状态操作。在tryAcquire(int)方法中,如果通过CAS设置成功,则代表获取到了同步状态,而在tryRelease(int)方法中,只是将同步状态置为0,而不用CAS操作

  测试

  1. public class MutexLockTest {
  2. private final static MutexLock mutex = new MutexLock();
  3.  
  4. private static final class Worker extends Thread {
  5. @Override
  6. public void run() {
  7. super.run();
  8. while (true) {
  9. // 获取锁
  10. mutex.lock();
  11. try {
  12. Thread.sleep(1000);
  13. System.out.println(Thread.currentThread().getName());
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. } finally {
  17. // 释放锁
  18. mutex.unlock();
  19. }
  20. }
  21. }
  22. }
  23.  
  24. public static void main(String[] args) throws InterruptedException {
  25. // 启动10个线程
  26. for (int i = 0; i < 10; i++) {
  27. Worker worker = new Worker();
  28. worker.setDaemon(true);
  29. worker.start();
  30. }
  31.  
  32. Thread.sleep(10000);
  33. }
  34. }

结果

  1. Thread-0
  2. Thread-0
  3. Thread-0
  4. Thread-0
  5. Thread-0
  6. Thread-0
  7. Thread-0
  8. Thread-1
  9. Thread-1
  10. Thread-1

运行过程中可以看到,每次只有一个线程输出,也就是同一时刻只有一个线程获取了锁,这表明Mutex能够完成独占锁的功能,但是Mutex还存在如下的问题:

不可重入:重入是指任意线程在获取锁之后能够再次获取该锁而不会被锁所阻塞,在上述代码中,当一个调用mutex.lock()获取锁之后,如果再次调用mutex.lock(),那么该线程将会被自己阻塞,因为在Mutex在实现tryAcquire(int)方法时,没有考虑占有锁的线程再次获取锁的情况,而在线程再次调用tryAcquire(int)方法时返回了false,导致线程被阻塞。

非公平:通过程序输出我们可以发现,在10秒之内,获取锁的线程只有Thread-0和Thread-1,而其他的线程都只能阻塞,这很容易产生饥饿现象。

这两个问题在JDK提供的重入锁ReentrantLock中,都得到了解决,我们后续会对ReentrantLock进行详解。

1.2、自定义共享锁同步组件

设计一个同步工具:该工具在同一时刻,只允许最多两个线程访问,超过两个线程的访问将被阻塞,我们将这个同步器命名为TwinsLock。

首先,确定访问模式,TwinsLock在同一时刻支持多个线程的访问,这显然是共享式访问,因此,需要使用AQS提供的acquireShared(int)方法以及releaseShared(int)等方法,这就要求TwinsLock必须重写tryAcquireShared(int)和tryReleaseShared(int)方法,才能保证AQS的共享式同步状态的获取与释放方法得以执行。

其次,定义同步状态(或者资源数),TwinsLock在同一时刻允许最多两个线程的访问,我们可以将同步状态初始值设置为2,表示可用的同步资源数目,当一个线程进行获取,status减1,该线程释放时,status加1,状态的合法范围为0、1、2,其中0表示当前已经有两个线程获取了同步资源,此时再有其他线程获取同步状态,该线程只能被阻塞。同步状态的变更都通过CAS操作做原子性保障。TwinsLock源码如下:

  1. public class TwinsLock implements Lock {
  2. private final Sync sync = new Sync(2);
  3.  
  4. private static final class Sync extends AbstractQueuedSynchronizer {
  5. public Sync(int count) {
  6. if (count <= 0) {
  7. throw new IllegalArgumentException("count must larger than zero.");
  8. }
  9. setState(count);
  10. }
  11.  
  12. public int tryAcquireShared(int reduceCount) {
  13. for (;;) {
  14. int currentCount = getState();
  15. int newCount = currentCount - reduceCount;
  16.  
  17. if (newCount < 0 || compareAndSetState(currentCount, newCount)) {
  18. return newCount;
  19. }
  20. }
  21. }
  22.  
  23. public boolean tryReleaseShared(int returnCount) {
  24. for (;;) {
  25. int currentCount = getState();
  26. int newCount = currentCount + returnCount;
  27.  
  28. if (compareAndSetState(currentCount, newCount)) {
  29. return true;
  30. }
  31. }
  32. }
  33. }
  34.  
  35. @Override
  36. public void lock() {
  37. sync.acquireShared(1);
  38. }
  39.  
  40. @Override
  41. public void unlock() {
  42. sync.releaseShared(1);
  43. }
  44.  
  45. @Override
  46. public void lockInterruptibly() throws InterruptedException {
  47. sync.acquireInterruptibly(1);
  48. }
  49.  
  50. @Override
  51. public Condition newCondition() {
  52. return null;
  53. }
  54.  
  55. @Override
  56. public boolean tryLock() {
  57. return false;
  58. }
  59.  
  60. @Override
  61. public boolean tryLock(long arg0, TimeUnit arg1) throws InterruptedException {
  62. return false;
  63. }
  64. }

TwinsLock实现了Lock接口,提供了面向使用者的接口,使用者调用lock()方法获取锁,随后调用unlock()方法释放锁,而同一时刻只能有最多两个线程获取锁。TwinsLock包含了一个自定义同步器Sync,而该同步器面向线程访问和同步状态控制。线程获取锁时,同步器会先计算出获取后的同步状态,然后通过CAS确保状态的正确设置,当tryAcquireShared(int)方法返回值大于等于0时,表示线程获取了同步状态,对于上层TwinsLock而言,表示当前线程获取了锁。

下面编写了一个程序来对TwinsLock进行验证:

  1. public class TwinsLockTest {
  2. private final static TwinsLock twinsLock = new TwinsLock();
  3.  
  4. private static final class Worker extends Thread {
  5. @Override
  6. public void run() {
  7. super.run();
  8. while (true) {
  9. twinsLock.lock();
  10. try {
  11. Thread.sleep(1000);
  12. System.out.println(Thread.currentThread().getName());
  13. Thread.sleep(1000);
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. } finally {
  17. twinsLock.unlock();
  18. }
  19. }
  20. }
  21. }
  22.  
  23. public static void main(String[] args) throws InterruptedException {
  24. // 启动10个线程
  25. for (int i = 0; i < 10; i++) {
  26. Worker worker = new Worker();
  27. worker.setDaemon(true);
  28. worker.start();
  29. }
  30.  
  31. Thread.sleep(10000);
  32. }
  33. }

运行结果(不唯一):

  1. Thread-1
  2. Thread-0
  3. Thread-3
  4. Thread-2
  5. Thread-2
  6. Thread-3
  7. Thread-2
  8. Thread-3
  9. Thread-2
  10. Thread-3

运行过程中可以看到,线程的名称都是成对输出,也就是同一时刻有两个线程获取了锁,这表明TwinsLock可以按照预期正常工作。但是,TwinsLock同样也存在重入和非公平的问题。

011-多线程-基础-基于AbstractQueuedSynchronizer自定义同步组件的更多相关文章

  1. 学习JUC源码(2)——自定义同步组件

    前言 在之前的博文(学习JUC源码(1)--AQS同步队列(源码分析结合图文理解))中,已经介绍了AQS同步队列的相关原理与概念,这里为了再加深理解ReentranLock等源码,模仿构造同步组件的基 ...

  2. java实现自定义同步组件的过程

    实现同步组件twinsLock:可以允许两个线程同时获取到锁,多出的其它线程将被阻塞. 以下是自定义的同步组件类,一般我们将自定义同步器Sync定义为同步组件TwinsLock的静态内部类. 实现同步 ...

  3. Java显式锁学习总结之二:使用AbstractQueuedSynchronizer构建同步组件

    Jdk1.5中包含了并发大神Doug Lea写的并发工具包java.util.concurrent,这个工具包中包含了显示锁和其他的实用同步组件.Doug Lea在构建锁和组件的时候,大多是以队列同步 ...

  4. salesforce lightning零基础学习(十三) 自定义Lookup组件(Single & Multiple)

    上一篇简单的介绍了自定义的Lookup单选的组件,功能为通过引用组件Attribute传递相关的sObject Name,捕捉用户输入的信息,从而实现搜索的功能. 我们做项目的时候,可能要从多个表中获 ...

  5. AQS 原理以及 AQS 同步组件总结

    1 AQS 简单介绍 AQS 的全称为(AbstractQueuedSynchronizer),这个类在 java.util.concurrent.locks 包下面. AQS 是一个用来构建锁和同步 ...

  6. 构建锁与同步组件的基石AQS:深入AQS的实现原理与源码分析

    Java并发包(JUC)中提供了很多并发工具,这其中,很多我们耳熟能详的并发工具,譬如ReentrangLock.Semaphore,它们的实现都用到了一个共同的基类--AbstractQueuedS ...

  7. 源码分析:同步基础框架——AbstractQueuedSynchronizer(AQS)

    简介 AQS 全称是 AbstractQueuedSynchronizer,位于java.util.concurrent.locks 包下面,AQS 提供了一个基于FIFO的队列和维护了一个状态sta ...

  8. 基础篇:JAVA原子组件和同步组件

    前言 在使用多线程并发编程的时,经常会遇到对共享变量修改操作.此时我们可以选择ConcurrentHashMap,ConcurrentLinkedQueue来进行安全地存储数据.但如果单单是涉及状态的 ...

  9. Java 多线程基础(五)线程同步

    Java 多线程基础(五)线程同步 当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题. 要解决上述多线程并发访问一个资源的安全性问题,Java中提供了同步机制 ...

随机推荐

  1. zookeeper学习(2)----zookeeper和kafka的关系

    转载: Zookeeper 在 Kafka 中的作用 leader 选举 和 follower 信息同步 如上图所示,kafaka集群的 broker,和 Consumer 都需要连接 Zookeep ...

  2. TODO Android +jacoco的增量覆盖率测试和一些概念

    查了下资料,工具要用mac开发,,,,陷入窘境,正在寻找替代方案. Android中的jacoco只支持offline模式,spring支持on-the-fly(在加载class文件进行,运用java ...

  3. Linux环境下安装mysql5.6(二进制包不是rpm格式)

    一.准备: 1.CentOS release 6.8 2.mysql-5.6.31-linux-glibc2.5-x86_64.tar.gz 3.Linux下MySQL5.6与MySQL5.7安装方法 ...

  4. windows下递归删除指定文件和文件夹

    //删除文件del *.后缀 /s//删除文件夹for /r 目录 %a in (文件夹名\) do @if exist "%a" rd /s/q "%a"

  5. Spring Cloud 功能使用的层面组件(一)

    来源:赤峰seo 实际上,Spring Cloud 是一个全家桶式的技术栈,它包含了很多组件.本文先从最核心的几个组件,也就是 Eureka.Ribbon.Feign.Hystrix.Zuul 入手 ...

  6. BZOJ 3636 教义问答手册 (分治)

    题意 一个整数数列,多次询问某段区间[li,ri][l_i,r_i][li​,ri​]内,选出若干个长度为LLL且不相交的连续段使选出来的数和最大. 分析 首先想朴素的区间DPDPDP 设f[i][j ...

  7. 如何使用python内置的request发送JSON格式的数据

    使用步骤如下: 一.如果想发送json格式的数据,需要使用request模块中的Request类来创建对象,作为urlopen函数的参数 二.header中添加content-type为applica ...

  8. margin与padding的区别

    当子元素使用padding-top,不会影响到父元素的高度 使用margin-top,则会影响

  9. spark2.1.0的源码编译

    本文介绍spark2.1.0的源码编译 1.编译环境: Jdk1.8或以上 Hadoop2.7.3 Scala2.10.4 必要条件: Maven 3.3.9或以上(重要) 点这里下载 http:// ...

  10. 用python实现不同格式99乘法表输出

    前言:学习python已经有一段时间了,最近发现有时候会出现一个东西知道,也能写出来,但是说不出来的情况.思考后觉得是基础还不够扎实,只一味写代码,没有深入思考具体实现的逻辑,以及各个点之间的关联.所 ...