一、概述

  队列同步器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. Tkinter关于新建窗口内Entry无法获取值(值全为空)的解决办法

    最近在做Python的课程作业,遇到一个问题,描述如下: 使用Python内置的Tkinter模块进行GUI编程 给一个按钮(或菜单)绑定事件,打开一个新窗口,新窗口内有Entry若干,通过textv ...

  2. Flask笔记(一)

    first_flask_project.py # 从flask这个包中导入Flask这个类 # Flask这个类是项目的核心,以后很多操作都是基于这个类的对象 # 注册url.注册蓝图等都是基于这个类 ...

  3. Thread.sleep()和Thread.currentThread().sleep()区别

    先看一下代码 public class Thread1 extends Thread{ @Override public void run() { try { System.out.println(& ...

  4. 前端学习笔记--html入门

    1.什么是html 2.标签和元素: 标签可嵌套,要注意缩进 html文档中的元素分为:内容文本.标签 3.标签和属性: 4.html的文件结构: 5.标签 a标签: 还不知道跳转到哪里,可以使用虚拟 ...

  5. 用Thymeleaf在实际项目中遇到的坑

    最近搭建了基于的springboot的新项目,抛弃了jsp,使用了官方推荐的Thymeleaf(怎么读?[taim][li:f])模板,在实际开发遇到了很多的坑,等项目告一段落,我再一一记录一下,有交 ...

  6. 关于Mybatis查询结果的封装

    1.结果封装为List<Object> 接口示例: public List<Members> selectMembersListByName(String name); 配置文 ...

  7. 洛谷P2051 中国象棋【dp】

    题目:https://www.luogu.org/problemnew/show/P2051 题意:n*m的格子里放炮,使他们不能互相攻击. 如果两个炮在同一行同一列并且中间还有一个棋子的话就可以攻击 ...

  8. iOS入门及ObjC语法

    iOS入门:http://www.jonathanhui.com/ios ObjC语法: http://www.jonathanhui.com/objective-c https://github.c ...

  9. UEFI分区损坏重建指南

    自从国庆假期发了这两篇博客后,我这个人就像是从博客园消失了一样,半个多月没更新..自从10月5号把UEFI分区删掉之后,我的电脑就因为没有引导,找不到系统而无法使用了.所以这篇博客,就分享一下我在这半 ...

  10. Postgresql vacuum freeze相关参数

    先看3个参数:autovacuum_freeze_max_age           | 500000vacuum_freeze_min_age               | 10vacuum_fr ...