一、概述

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

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

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

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

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

package com.github.bjlhx15.common.thread.juc.collection;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock; public class MutexLock implements Lock {
private final Sync sync = new Sync(); private static final class Sync extends AbstractQueuedSynchronizer {
// 当状态为0的时候获取锁,通过CAS将状态置为1
public boolean tryAcquire(int acquires) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
} return false;
} // 释放锁,将状态置为0
public boolean tryRelease(int releases) {
if (getState() == 0) {
throw new IllegalMonitorStateException();
} setExclusiveOwnerThread(null);
setState(0);
return true;
}
} @Override
public void lock() {
sync.acquire(1);
} @Override
public void unlock() {
sync.release(1);
} @Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
} @Override
public Condition newCondition() {
return null;
} @Override
public boolean tryLock() {
return sync.tryAcquire(1);
} @Override
public boolean tryLock(long arg0, TimeUnit arg1) throws InterruptedException {
return sync.tryAcquireNanos(1, arg1.toNanos(arg0));
}
}

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

  测试

public class MutexLockTest {
private final static MutexLock mutex = new MutexLock(); private static final class Worker extends Thread {
@Override
public void run() {
super.run();
while (true) {
// 获取锁
mutex.lock();
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放锁
mutex.unlock();
}
}
}
} public static void main(String[] args) throws InterruptedException {
// 启动10个线程
for (int i = 0; i < 10; i++) {
Worker worker = new Worker();
worker.setDaemon(true);
worker.start();
} Thread.sleep(10000);
}
}

结果

Thread-0
Thread-0
Thread-0
Thread-0
Thread-0
Thread-0
Thread-0
Thread-1
Thread-1
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源码如下:

public class TwinsLock implements Lock {
private final Sync sync = new Sync(2); private static final class Sync extends AbstractQueuedSynchronizer {
public Sync(int count) {
if (count <= 0) {
throw new IllegalArgumentException("count must larger than zero.");
}
setState(count);
} public int tryAcquireShared(int reduceCount) {
for (;;) {
int currentCount = getState();
int newCount = currentCount - reduceCount; if (newCount < 0 || compareAndSetState(currentCount, newCount)) {
return newCount;
}
}
} public boolean tryReleaseShared(int returnCount) {
for (;;) {
int currentCount = getState();
int newCount = currentCount + returnCount; if (compareAndSetState(currentCount, newCount)) {
return true;
}
}
}
} @Override
public void lock() {
sync.acquireShared(1);
} @Override
public void unlock() {
sync.releaseShared(1);
} @Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
} @Override
public Condition newCondition() {
return null;
} @Override
public boolean tryLock() {
return false;
} @Override
public boolean tryLock(long arg0, TimeUnit arg1) throws InterruptedException {
return false;
}
}

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

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

public class TwinsLockTest {
private final static TwinsLock twinsLock = new TwinsLock(); private static final class Worker extends Thread {
@Override
public void run() {
super.run();
while (true) {
twinsLock.lock();
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
twinsLock.unlock();
}
}
}
} public static void main(String[] args) throws InterruptedException {
// 启动10个线程
for (int i = 0; i < 10; i++) {
Worker worker = new Worker();
worker.setDaemon(true);
worker.start();
} Thread.sleep(10000);
}
}

运行结果(不唯一):

Thread-1
Thread-0
Thread-3
Thread-2
Thread-2
Thread-3
Thread-2
Thread-3
Thread-2
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. 天兔 -Lepus 慢查询分析平台配置

    想要实现慢查询查询分析,需要在被监控端安装percona-toolkit工具.   1.被监控端安装软件包 yum -y install perl-IO-Socket-SSL yum -y insta ...

  2. MOOC下载器的文档整理

    1.背景   最近学习中国大学MOOC的课程,想把课程的pdf下载下来本地保存并浏览.工具: Setup-Mooc-3.4.0.exe   但是,却发现所下载的文档在不同的文件夹里,浏览很不方便.于是 ...

  3. GoogLeNet网络的Pytorch实现

    1.文章原文地址 Going deeper with convolutions 2.文章摘要 我们提出了一种代号为Inception的深度卷积神经网络,它在ILSVRC2014的分类和检测任务上都取得 ...

  4. python学习之多线程多进程

    python基础 进程&线程 进程是一组资源的集合,运行一个系统就是打开了一个进程,如果同时打开了两个记事本就是开启了两个进程,进程是一个笼统的概念,进程中由线程干活工作,由进程统一管理 一个 ...

  5. THINKPHP SQL注入处理方式

    //注入的产生一般都是对用户输入的参数未做任何处理直接对条件和语句进行拼装. //不安全的写法举例1 $_GET['id']=8;//希望得到的是正整数 $data=M('Member')->w ...

  6. Django2-settings常用配置

    1. MySQL 数据库连接 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'bms', # 要连接 ...

  7. LevelDB的源码阅读(三) Get操作

    在Linux上leveldb的安装和使用中我们写了这么一段测试代码,内容以及输出结果如下: #include <iostream> #include <string> #inc ...

  8. Wireshark远程抓包

    1.被监控主机上,在安装目录下点击rpcapd.exe,运行server服务,并关闭防火墙 2.监控机器上,Capture Options,点击Manage Interfaces,弹出如下对话框 选择 ...

  9. vue子父组件传值

    https://blog.csdn.net/weixin_38888773/article/details/81902789 https://blog.csdn.net/jsxiaoshu/artic ...

  10. Mysql5.7.27.msi的下载与安装

    1.下载地址链接:https://dev.mysql.com/downloads/windows/installer/8.0.html 2. 点击下载后可以选择不用登录直接下载 3.下载的mysql安 ...