首页 > 其他分享 >坚石诚信加密狗复制 公众~号:纯真网络

坚石诚信加密狗复制 公众~号:纯真网络

时间:2022-11-20 19:01:52浏览次数:55  
标签:Node node 加密狗 int 纯真 线程 arg 节点 坚石

坚石诚信加密狗复制全称 AbstractQueuedSynchronizer,队列同步器,该组件是JUC包下的实现锁和其他同步组件的基础框架。我们先从JavaDoc看看是如何介绍的。因原文过长,这里直接理解摘取第一段,该段能大致了解该类的作用。若需要了解具体细节仍需要查看其他注释段落。翻译如下:

提供一个依赖先进先出(FIFO)等待队列实现阻塞锁和相关同步器的基础框架。该类被设计为有用的基础是因为大多数同步器都依赖于一个原子的int值来作为他们的状态。子类必须定义更改此状态的protected方法和状态获取和释放对该子类意味着什么。基于以上方法,该类中的其他方法则提供排队和阻塞的机制。子类还可以维护其他状态字段,但是需要使用 ​​getState()​​, ​​setState(int)​​ 和 ​​compareAndSetState(int, int)​​来原子同步的更新int值。

简单总结一下:

  • AQS类
  • 提供FIFO队列及其排队和阻塞机制的方法
  • 提供一个int类型的值​​state​​来作为子类同步器的状态
  • 子类
  • 提供更改此状态的protected方法
  • 当实际需要获取或更改状态时需要使用对应方法

state字段

/**
* The synchronization state.
*/
private volatile int state;

//返回同步状态的当前值
protected final int getState() {
return state;
}
//设置当前同步状态
protected final void setState(int newState) {
state = newState;
}
//使用CAS设置当前状态,该方法能够保证状态设置的原子性
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

该字段就是作为子类同步器的状态,volidate修饰该字段是为了保证多线程之间的可见性,具体在子类中意味着什么由实现子类决定。所以理解每个同步器类的​​state​​ 字段都至关重要。比如在​​ReentrantLock​​ 中该字段就表示当前线程持有了几次锁。

FIFO等待队列

从介绍中看出AQS提供了FIFO等待队列,我们先看看AQS是如何提供FIFO等待队列及其对应的方法。队列中存储着什么呢?AQS类中首先定义了一个Node类作为队列的节点:节点存在2种模式,​​SHARED​​和​​EXCLUSIVE​​;存在5种状态用来控制线程的唤醒和阻塞:​​CANCELLED​​,​​SIGNAL​​,​​CONDITION​​,​​PROPAGATE​​ ,​​INITAL​​;并且该节点为双向的,存在前驱节点和后续节点。(其实每一个节点就是一个等待获取锁的线程)。

public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
static final class Node {
/** 共享模式 */
static final Node SHARED = new Node();
/** 排他模式 */
static final Node EXCLUSIVE = null;
//节点等待状态
// CANCELLED(1) :因为超时或者中断,节点会被设置为取消状态,被取消的节点时不会参与到竞争中的,他会一直保持取消状态不会转变为其他状态
// SIGNAL (-1): 后继节点的线程处于等待状态,而当前节点的线程如果释放了同步状态或者被取消,将会通知后继节点,使后继节点的线程得以运行
// CONDITION (-2): 节点在等待队列中,节点线程等待在Condition上,当其他线程对Condition调用了signal()后,该节点将会从等待队列中转移到同步队列中,加入到同步状态的获取中
// PROPAGATE (-3): 表示下一次共享式同步状态获取将会无条件地传播下去
volatile int waitStatus;
//前驱节点
volatile Node prev;
//后续节点
volatile Node next;
//获取同步的线程
volatile Thread thread;
//等待队列中的后续节点
Node nextWaiter;
//返回是否为共享
final boolean isShared() {
return nextWaiter == SHARED;
}
//返回前继节点
final Node predecessor() {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}

Node() { // Used to establish initial head or SHARED marker
}

Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}

Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}

//头节点
private transient volatile Node head;
//尾节点
private transient volatile Node tail;

}

加入队列

因为是FIFO队列,整个步骤是根据当前线程和传入的节点模式创建新节点并加入队列尾部。这里尾节点使用cas来操作就是保证尾节点是线程安全的。

private Node addWaiter(Node mode) {
//创建节点
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
//获取尾部节点
Node pred = tail;
if (pred != null) { //若队列中已有节点
//则设置新节点的prev指向当前尾节点
node.prev = pred;
if (compareAndSetTail(pred, node)) { //若cas操作将新节点设置为尾节点
//则将原来的尾节点的next指向新节点
pred.next = node;
return node;
}
}
//循环尝试,直至成功
enq(node);
return node;
}

​enq​​方法通过一个死循环尝试,直到把新节点入队列。

private Node enq(final Node node) {
for (;;) {
//获取尾节点
Node t = tail;
if (t == null) { // Must initialize 若为空队列
if (compareAndSetHead(new Node())) //cas设置新节点为头节点
tail = head; //尾节点也指向头结点
} else { //非空队列 (操作同上)
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}


该图简单的展示了节点入队的过程。图中数字表示执行顺序。红色表示原先队列中的节点,而蓝色表示新增节点。

出队列

因为是FIFO队列,首节点在释放同步状态时将会通知后继节点,当后继节点得到同步状态时,会把自己设置为 首节点。这个过程中不需要是不需要cas操作的,因为只有一个线程,可以获得同步状态。

/**
* Sets head of queue to be node, thus dequeuing. Called only by
* acquire methods. Also nulls out unused fields for sake of GC
* and to suppress unnecessary signals and traversals.
*
* @param node the node
*/
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}

过程图如图所示。

其他方法

AQS还定义了其他的方法来作为排队和阻塞的机制。AQS中定义了大量的模板方法来实现同步机制,由子类继承实现。主体上可以分为三大类:独占式获取、释放锁;共享式获取、释放锁;查看同步队列线程情况。

  1. 独占式:同一时刻只有一个线程可以获得锁
  • ​acquire(int arg)​​:独占式获取同步状态,如果当前线程获取同步状态成功,则由该方法返回,否则,将会进入同步队列等待,该方法将会调用可重写的tryAcquire(int arg)方法;
  • ​acquireInterruptibly(int arg)​​:与acquire(int arg)相同,但是该方法响应中断,当前线程为获取到同步状态而进入到同步队列中,如果当前线程被中断,则该方法会抛出InterruptedException异常并返回;
  • ​tryAcquire(int arg)​​:独占式获取同步状态,获取同步状态成功后,其他线程需要等待该线程释放同步状态才能获取同步状态;
  • ​tryAcquireNanos(int arg,long nanos)​​:超时获取同步状态,如果当前线程在nanos时间内没有获取到同步状态,那么将会返回false,已经获取则返回true;
  • ​release(int arg)​​:独占式释放同步状态,该方法会在释放同步状态之后,将同步队列中第一个节点包含的线程唤醒;
  • ​tryRelease(int arg)​​:独占式释放同步状态;
  1. 共享式:同一时刻可以有多个线程获得锁,比如读锁多个线程获得,写锁一个线程获得
  • ​acquireShared(int arg)​​:共享式获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待,与独占式的主要区别是在同一时刻可以有多个线程获取到同步状态;
  • ​acquireSharedInterruptibly(int arg)​​:共享式获取同步状态,响应中断;
  • ​tryAcquireShared(int arg)​​:共享式获取同步状态,返回值大于等于0则表示获取成功,否则获取失败;
  • ​tryAcquireSharedNanos(int arg, long nanosTimeout)​​:共享式获取同步状态,增加超时限制;
  • ​releaseShared(int arg)​​:共享式释放同步状态;
  • ​tryReleaseShared(int arg)​​:共享式释放同步状态;
  1. 查询同步队列线程情况
  • ​isHeldExclusively()​​:当前同步器是否在独占式模式下被线程占用,一般该方法表示是否被当前线程所独占;
  • ...

我们接下来就挑选最基础的获取和释放锁来看看是怎么处理的。

独占式

获取锁

public final void acquire(int arg) {

if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}

//子类实现
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}

final boolean acquireQueued(final Node node, int arg) {
//是否失败
boolean failed = true;
try {
//是否中断
boolean interrupted = false;
//死循环自旋
for (;;) {
//获取前节点
final Node p = node.predecessor();
//若前节点为头节点且获取锁成功
if (p == head && tryAcquire(arg)) {
//设置头节点
setHead(node);
p.next = null; // help GC
fail = false;
return interrupted;
}
//获取失败,判断是否需要阻塞 后文介绍(线程阻塞)
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}

​tryAcquire​​ 是交由子类同步器实现的方法,具体就是尝试获取锁,如果成功则直接返回;否则执行​​addWaiter(Node.EXCLUSIVE)​​ 该线程加入到FIFO队列尾部;然后执行 ​​acquireQueued​​ 线程会进入一个死循环自旋的过程去获取锁直到退出,并返回是否被中断。

释放锁

public final boolean release(int arg) {
if (tryRelease(arg)) { //尝试释放锁
Node h = head;
if (h != null && h.waitStatus != 0)
//唤醒后继节点 该方法后面会介绍
unparkSuccessor(h);
return true;
}
return false;
}

//子类实现
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}

​tryRelease​​ 和 ​​tryAcquire​​ 一样是交由子类同步器实现的方法,具体则是尝试释放锁;如果成功则调用​​unparkSuccessor​​唤醒后继节点。

共享式

获取锁

public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
//由子类实现
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}


private void doAcquireShared(int arg) {
//加入队列尾部
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
//死循环自旋
for (;;) {
//前继节点
final Node p = node.predecessor();
if (p == head) { //头结点
//尝试获取锁
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}

​tryAcquireShared​​ 则不多作介绍都是由子类实现,这里要注意的是共享式返回值是 int ,只要该值大于等于0则表示获取到了锁,否则通过​​doAcquireShared​​加入队列自旋获取锁。   (整个过程类似于独占式获取锁)

释放锁

public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
//子类实现
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}

private void doReleaseShared() {
//死循环 自旋
for (;;) {
//获取头节点
Node h = head;
if (h != null && h != tail) { //不是只有一个节点
int ws = h.waitStatus; //获取节点状态
if (ws == Node.SIGNAL) { //唤醒后继节点
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) //cas释放
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 && //初始化状态
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) //cas释放
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}

可能存在多个线程同时进行释放同步资源(读锁),所以通过CAS操作需要确保同步状态安全成功释放。

标签:Node,node,加密狗,int,纯真,线程,arg,节点,坚石
From: https://blog.51cto.com/u_13971202/5871613

相关文章

  • 加密狗逻辑:判断权限,延时加载遮罩层
    之前新人做的加密狗显示水印遮罩的功能,一直有问题,逻辑还找不出来哪有问题,自己简单写了个demo,照着套:<!doctypehtml><htmllang="en"translate="no"><head><met......
  • 简单了解一下加密狗以及加密狗复制的方法
    什么是加密狗?加密狗是一种用于软件加密的小型外置硬件设备,常见的有并口与USB接口两种类型,加密狗被广泛应用于各种软件之中,软件开发的技术人员,可以把实现此项功能的软件橙块......
  • D8加密狗的使用过程记录
    1、安装VSCode,并且安装插件   2、打开商家给提供的文件夹【打开的是整个文件夹】如下: 打开后效果如下: 3、编写加密锁里的逻辑打......
  • 纯真IP数据库转mysql方法详解
    纯真ip数据库转mysql_如何把纯真ip数据库导入到MySQL数据表中纯真中国IP地理位置  https://www.cz88.net一、下载最新版的QQWry.Dat二、下载IPLook使用IPLook把QQWry......