一、概述

  队列同步器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. Django modle基础样版

    定义一个基类模版, from django.db import models class ModelBase(models.Model): """ "" ...

  2. 【转】provisional headers are shown 知多少

    前言 请求里面provisional headers are shown(显示临时报头) 出现的情况很多,但原因是多样的.如果你去直接匹配关键字搜索,得到的结果可能与你自己的情况大相径庭.网上大部分都 ...

  3. windows10下安装tensorflow2.0-GPU和Cupy(不用搞CUDA+cudnn)

    0.前言 今年暑假买了个1660ti的游戏本学python,后来发现跑一些数据量比较大的代码和深度学习的时候太慢了,遂想装一下GPU版本,看了网上的资料搞了好几天,又是CUDA又是cudnn的,网速慢 ...

  4. 题解 洛谷P1290 【欧几里德的游戏】

    这题没必要那么麻烦,只需要推理一下即可: 假设我们有两个数\(x,y\),先把\(x\)设为较大值,\(y\)设为较小值.现在分成三种情况: \(1\).若两数为倍数关系,操作的一方赢. \(2\). ...

  5. P2P system: GNUTELLA

    P2P system: GNUTELLA GNUTELLA是第一个经论证的分布式的peer-to-peer system. Napster的一个重大问题是涉及到间接侵权,所以GNUTELLA消除the ...

  6. java中的assert

    Java陷阱之assert关键字   一.概述   在C和C++语言中都有assert关键,表示断言. 在Java中,同样也有assert关键字,表示断言,用法和含义都差不多.   二.语法   在J ...

  7. python定义函数时的参数&调用函数时的传参

    一.定义函数: 1.位置参数:直接定义参数 2.默认参数(或者关键字参数):参数名 = "默认值" 3.位置参数必须在默认参数之前 二.调用函数: 1.按位置传,直接写参数的值 2 ...

  8. Nginx 负载均衡条件下 Tomcat 共享Session (Java)(一)

    1.修改tomcat 下 conf/context.xml  在</Context>里面加入以下代码 <Valve className="com.orangefunctio ...

  9. React组件属性/方法/库属性

    1. propTypes 用于进行props的类型检查:来自于prop-types库. // V15.5之后 import PropTypes from 'prop-types'; 该方法适用于函数组 ...

  10. HTML 文字剧中

    HTML 内想使文字剧中的办法  就是 text-align:center 剧中前效果图 剧中后效果图 代码: