在多线程情景下,如果不会某一共享变量采取一些同步机制,很可能发生数据不安全现象,比如购买车票时,当多个人购买时,不加锁就会产生多人买同一张票的现象,显然这是不可取的。所以要有一种同步机制,在某一时刻只能有一个线程处理该共享变量。

同步器的加锁

我将自己实现的同步器成为RoadAQS.

主要变量如下:

//当前锁的状态,1表示加锁,0表示未加锁
private volatile int state = 0;
private final static Unsafe unsafe = UnsafeInstance.reflectUnsafe();
//state在内存中的偏移量
private final static long stateOffset;
//当前持有锁的线程
private Thread lockHoder;
//是一个线程安全的队列,记录等待获取锁的线程
private ConcurrentLinkedQueue<Thread> waiters = new ConcurrentLinkedQueue<>(); static {
try {
stateOffset = unsafe.objectFieldOffset(RoadAQS.class.getDeclaredField("state"));
} catch (NoSuchFieldException e) {
throw new Error(e);
}
}

整体思想:

刚一开始初始化时会利用反射获取一个Unsafe魔法类,然后获取变量state在内存中的偏移量,为后续的CAS操作做准备。然后开始尝试获取锁,当等待队列为空或者当前线程等于等待队列的第一个线程,然后CAS更新状态为1成功,说明获得锁成功,并将同步器的拥有者设置为当前线程。如果加锁失败,就将该线程放入到等待队列中,然后开始无限for循环。

进入循环背内部,再尝试一次获取锁,仍然失败后,开始调用LockSupport.park()将该线程进行阻塞,与Object.wait一个最大的区别就是park()、unpark()能够指定具体的线程进行唤醒,而object.notify只能随机唤醒一个。

阻塞后当其他线程执行完退出后,会调用LockSupport.unpark(t)对等待队列中的第一个线程进行唤醒,唤醒后会继续执行for循环内部的代码,再尝试获得锁。获得锁后,从等待队列中取出,并将同步器的拥有者改为该线程。

public void lock() {
if(acquire()){
return;
}
Thread current = Thread.currentThread();
waiters.add(current);
for(;;) {
if(current == waiters.peek() && acquire()) {
waiters.poll();
return;
}
LockSupport.park();
}
}
public boolean acquire() {
Thread t = Thread.currentThread();
if ((waiters.size() == 0 || t == waiters.peek()) && compareAndSwapInt(0, 1)) {
setLockHoder(t);
return true;
}
return false;
}

同步器的解锁

获取当前的锁状态,并尝试更新为0,成功后将同步器的拥有者设为null,然后获取等待队列的第一个队列,将该队列进行唤醒。

public void unlock() {
if (Thread.currentThread() != getLockHolder()) {
throw new RuntimeException("lockHolder is not current Thread");
}
int state = getState();
if (compareAndSwapInt(state, 0)) {
setLockHoder(null);
Thread t = waiters.peek();
if (t != null) {
LockSupport.unpark(t);
}
}
}

测试用例

public class RoadAQSTest {
public static void main(String[] args) {
Goods goods = new Goods();
for(int i=0; i<100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
goods.reduceCount();
}
}, "Thread-" + i + "------").start();
}
} private static class Goods{
private int count = 10;
private RoadAQS lock = new RoadAQS();
public void reduceCount() { lock.lock(); if (count > 0) {
System.out.println("线程" + lock.getLockHolder() + " 获取第 " + count + "件商品");
count--;
} else {
System.out.println("商品已卖完!");
}
lock.unlock();
}
}
}

测试结果:

动手实现一个同步器(AQS)的更多相关文章

  1. Java 中队列同步器 AQS(AbstractQueuedSynchronizer)实现原理

    前言 在 Java 中通过锁来控制多个线程对共享资源的访问,使用 Java 编程语言开发的朋友都知道,可以通过 synchronized 关键字来实现锁的功能,它可以隐式的获取锁,也就是说我们使用该关 ...

  2. 基于AQS自己实现一个同步器

    前面说了这个多,我们可以自己尝试实现一个同步器,我们可以简单的参考一下ReentrantLock这个类的实现方式,我们就简单的实现一个不可重入的独占锁吧! 一.简单分析ReentrantLock的结构 ...

  3. 死磕 java同步系列之自己动手写一个锁Lock

    问题 (1)自己动手写一个锁需要哪些知识? (2)自己动手写一个锁到底有多简单? (3)自己能不能写出来一个完美的锁? 简介 本篇文章的目标一是自己动手写一个锁,这个锁的功能很简单,能进行正常的加锁. ...

  4. JAVA并发-同步器AQS

    什么是AQS aqs全称为AbstractQueuedSynchronizer,它提供了一个FIFO队列,可以看成是一个用来实现同步锁以及其他涉及到同步功能的核心组件,常见的有:ReentrantLo ...

  5. Java 显示锁 之 队列同步器AQS(六)

    1.简述 锁时用来控制多个线程访问共享资源的方式,一般情况下,一个锁能够防止多个线程同时访问共享资源.但是有些锁可以允许多个线程并发的访问共享资源,比如读写锁. 在Java 5.0之前,在协调对共享对 ...

  6. 并发——抽象队列同步器AQS的实现原理

    一.前言   这段时间在研究Java并发相关的内容,一段时间下来算是小有收获了.ReentrantLock是Java并发中的重要部分,所以也是我的首要研究对象,在学习它的过程中,我发现它是基于抽象队列 ...

  7. 《动手实现一个网页加载进度loading》

    loading随处可见,比如一个app经常会有下拉刷新,上拉加载的功能,在刷新和加载的过程中为了让用户感知到 load 的过程,我们会使用一些过渡动画来表达.最常见的比如"转圈圈" ...

  8. C#中自己动手创建一个Web Server(非Socket实现)

    目录 介绍 Web Server在Web架构系统中的作用 Web Server与Web网站程序的交互 HTTPListener与Socket两种方式的差异 附带Demo源码概述 Demo效果截图 总结 ...

  9. 自己动手实现一个简单的JSON解析器

    1. 背景 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.相对于另一种数据交换格式 XML,JSON 有着诸多优点.比如易读性更好,占用空间更少等.在 ...

随机推荐

  1. Affinity Propagation

    1. 调用方法: AffinityPropagation(damping=0.5, max_iter=200, convergence_iter=15, copy=True, preference=N ...

  2. 机器学习总结-sklearn参数解释

    本文转自:lytforgood 机器学习总结-sklearn参数解释 实验数据集选取: 1分类数据选取 load_iris 鸢尾花数据集 from sklearn.datasets import lo ...

  3. Codeforces_327_C

    http://codeforces.com/problemset/problem/327/C 等比求和相加,有mod的出现,所以要算逆元. #include<iostream> #incl ...

  4. codeforces 540D Bad Luck Island (概率DP)

    题意:会出石头.剪刀.布的人分别有r,s,p个,他们相互碰到的概率相同,输的人死掉,问最终活下去的人是三种类型的概率 设状态dp(i,j,k)为还有i个石头,j个剪刀,k个布时的概率,dp(r,s,p ...

  5. PC微信逆向--实现消息防撤回

    自从聊天软件消息撤回功能问世后,对于撤回的消息,我们对它一直有种强烈的好奇感."Ta刚撤回了什么?是骂我的话?还是说喜欢我?还是把发给其他人的消息误发给了我?好气呀,都看不到了...&quo ...

  6. Java开发最佳实践(一) ——《Java开发手册》之"编程规约"

    Java开发手册版本更新说明 专有名词解释 一. 编程规约 (一) 命名风格 (二) 常量定义 (三) 代码格式 (四) OOP 规约 (五) 集合处理 (六) 并发处理 (七) 控制语句 (八) 注 ...

  7. space transport protocols

    VSAT系统对TCP的改进 https://www.vsat-systems.com/broadband-satellite-internet/index.html TCP/IP over satel ...

  8. OSPF理论

    OSPF简介 OSPF(Open Shortest Path First 开放式最短路径优先)协议是IETF为IP网络开发的IGP路由选择协议.它是一种典型的链路状态(link-state)路由协议. ...

  9. PBFT 算法 java实现(下)

    PBFT 算法的java实现(下) 在上一篇博客中(如果没有看上一篇博客建议去看上一篇博客),我们介绍了使用Java实现PBFT算法中节点的加入,view的同步等操作.在这篇博客中,我将介绍PBFT算 ...

  10. VMware vCenter Server6.0安装及群集配置介绍

    在本项目中,将在VMware Workstation 模拟的Windows Server 2008 R2虚拟机中安装VMware vCenter Server ,并且使用vCenter Server捆 ...