AQS4源码
@SuppressWarnings("restriction")
public abstract class AbstractQueuedSynchronizer1 extends AbstractOwnableSynchronizer1 implements java.io.Serializable {
private static final long serialVersionUID = 7373984972572414691L;
protected AbstractQueuedSynchronizer1() {}
protected final int getState() {
return state;
}
protected final void setState(int newState) {
state = newState;
}
static final long spinForTimeoutThreshold = 1000L; //写成员变量时候用到cas,读的时候不用cas,有可能读到之后改变了,所以写的时候就会失败。
//写成员变量都要CAS,除非是要强制修改,覆盖别人的修改。 private Node enq(final Node node) {//修改尾节点失败
for (;;) {
Node t = tail;
if (t == null) {//如果尾指针为null,则头指针也一定为null,表示等待队列未初始化,就CAS初始化队列。
if (compareAndSetHead(new Node()))//相当于加锁,因为不会重新获取头结点。
tail = head;//初始化头尾节点为空节点,
} else {//如果尾指针非null,则队列已初始化,就CAS尝试在尾节点后插入新的节点node。
node.prev = t;
if (compareAndSetTail(t, node)) {//修改尾节点为新节点(成员变量变了,局部变量没变),失败了node.prev=t也无效。
t.next = node;//for的死循环,所以不相当于加锁,因为重新获取了尾节点,里面可以多线程都进来,但是不影响。
return t;
}
}
}
} //mode:Node.EXCLUSIVE独占, Node.SHARED共享的。
private Node addWaiter(Node mode) {
// 构造节点,mode有两种:EXCLUSIVE(独占)和SHARED(共享)
Node node = new Node(Thread.currentThread(), mode);//多个线程在排队,一个线程附在一个Node节点,
Node pred = tail;
if (pred != null) {//尾节点不为null,其实被enq(node)重复了。
node.prev = pred;
if (compareAndSetTail(pred, node)) {//修改共享变量cas, 成员变量变了,局部变量没变。失败node.prev=pred无效。
pred.next = node;//里面可以多线程都进来,但是不影响。因为pred=tail=新的节点node,重新获取了尾节点。
return node;//返回新尾节点
}
}
enq(node);//修改尾节点失败。自旋的方式继续加入等待队列。
return node;//返回新尾节点
} //将队列的头设置为节点,从而使其出列。仅由Acquire方法调用。为了GC,还可以空出未使用的字段,并抑制不必要的信号和遍历。
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
} //node可能是head节点可能是失效节点。
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < )//-1就设置为0,异常节点这里是1。 头结点要么是0要么是-1。头结点设置为0.
compareAndSetWaitStatus(node, ws, ); /*head下一个节点,从头结点开始一个个的找后继,直到把队列找完。
next节点是正常的就找next,next不是正常的,就不能再找next,因为next.next有可能是自己。
就tail往前找,从tail往前找prev一定能够吧所有正常节点找到(还会找到不正常的节点)。
如果找到的这个节点status<=0,然后唤醒,但是这个节点异常了只是状态没有修改过来,
那么唤醒的这个假正常节点就不会去获取锁,而是帮着正常节点做 ,做完退出。
*/ Node s = node.next;//node=head,head.next不一定是初始节点,可能被改过指向曾经正常的一个节点,这个节点现在正常与否未知。
//下一个节点s被取消,node还没来得及重新设置新的正常后继节点。队列还没有处于稳定状态。
//第一个节点被取消,就从后往前找下一个状态正常节点。也相当于是head后面第一个status正常节点。 if (s == null || s.waitStatus > ) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev) //右边的正常节点。
if (t.waitStatus <= )// 0/-1都唤醒
s = t;
}
if (s != null)//s可能=head,head.thread=null就什么都不做。
LockSupport.unpark(s.thread);
} private void doReleaseShared() {// 读锁释放(共享锁),这个方法只会唤醒一个节点
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {//-1, 如果头节点状态为SIGNAL,说明要唤醒下一个节点,并且设置0成功就唤醒下一个,
if (!compareAndSetWaitStatus(h, Node.SIGNAL, ))//设置为0成功(没有异常)就去unparkSuccessor,设置为0失败就跳过unparkSuccessor再次从head开始,
continue; unparkSuccessor(h);//唤醒下一个节点 //等于0 并且 设置-3失败,就跳过, 把头节点的状态改为PROPAGATE成功才会跳到下面的if
} else if (ws == && !compareAndSetWaitStatus(h, , Node.PROPAGATE))
continue; //
}
if (h == head) // head变化了,继续唤醒head,
break;//头结点变了,head后面节点自行出队了,
}
} private void setHeadAndPropagate(Node node, int propagate) {
Node h = head;
setHead(node);
// 如果旧的头节点或新的头节点为空或者其等待状态小于0(表示状态为SIGNAL/PROPAGATE)
if (propagate > || h == null || h.waitStatus < || (h = head) == null || h.waitStatus < ) {
Node s = node.next;//node下一个节点,需要传播
if (s == null || s.isShared()) // 如果下一个节点为空,或者是需要获取读锁的节点
doReleaseShared();//唤醒head下一个节点
//一个节点A获取了读锁后,会唤醒下一个读节点B,这时候B也会获取读锁,然后B继续唤醒C,依次往复,
//也就是说这里的节点是一个唤醒一个这样的形式,而不是一个节点获取了读锁后一次性唤醒后面所有的读节点。
}
} /* 所看到的所有成员变量都在工作内存里面,局部变量更在工作内存里面。 */ //有可能是0变到1,也有可能是-1变到1,
private void cancelAcquire(Node node) {
if (node == null)
return;
node.thread = null;//异常节点,thread先清空, /*失效节点:不断改变自己的前驱 在改变status 最后改变next*/ Node pred = node.prev;//node就是头结点,pred就是null,
while (pred.waitStatus > )//找到新的-1/0,跳过已经取消的节点。
node.prev = pred = pred.prev;//断开node.prev。node.prev只有节点自己线程修改不用cas,
//while出来时候pred=0/-1,while之后pred可能变为1。 0/-1节点在任何时候都有可能变为1,每次判断都只是瞬间的值。 /*pred = pred.prev;不是改变属性
node.prev = pred是改变属性*/ Node predNext = pred.next;//此时pred可能是0/-1节点也可能是1节点. /* 节点异常了只能通过status和thread看得出来。否则外界不知道异常了。别人也是通过status来看这个节点是不是异常了,所以有延时。 */ node.waitStatus = Node.CANCELLED;//强制覆盖,不用cas,以这个为主。 //修改队列。去掉的节点是尾节点就要修改,尾节点就不需要prev.next了。
if (node == tail && compareAndSetTail(node, pred)) {//死循环的cas会重来,一次cas失败了就放弃让别人去做,优先级低于直接赋值。
//修改tail为pred,修改失败:不是尾节点了,加进来了新的节点。
//pred.next!=predNext就是有节点加进来了,并且pred.next=新的节点,就不置位null。
compareAndSetNext(pred, predNext, null);//tail的next置位null,GC } else {//不是尾节点,或者是尾节点但是加进来了新的节点,
int ws;
if (pred != head //node前面是head,就不要去设置后继关系,而是唤醒,
&&
/* 再次判断pred的状态是正常=0/-1
有可能这时候prev=head,并且有可能head=0已经出队了,pred.thread = null就要唤醒node后面*/ /*prev不是头结点并且异常了,如果不管了,如果后面正常节点阻塞了,并且前面没有正常节点
那么head=0 unlock出队失败时候,队列里面的就全阻塞了,就永远不能出队。*/ ( (ws = pred.waitStatus) == Node.SIGNAL|| (
ws <= && compareAndSetWaitStatus(pred, ws, Node.SIGNAL) ) )
&& pred.thread != null
)
{
//pred不是头节点,并且,pred正常
//有可能这时候prev=head,但是head=-1,可以正常唤醒。
Node next = node.next;
if (next != null && next.waitStatus <= )//next节点也可能是取消了。
//pred.next没有被修改。next不可能为null,因为线程退出了节点可能还在,
compareAndSetNext(pred, predNext, next);//把node也跳过,失败不重来,因为别的正常-1节点也会修改,让正常节点。 } else {//node.prev=head,ndoe异常,
//node不一定是紧挨着head的节点,正常节点在node后面并且在第一个栅栏里面,
//如果这里不唤醒,只是依靠unlock唤醒,但是unlock在head=0时候是不会唤醒的什么都不做的,unlock在这种情况不负责唤醒线程获取锁,
//那么就该第一个栅栏里面的正常节点去获取锁,防止正常节点阻塞了,这里就要去唤醒,否则unlock出队失败就永远不能唤醒。 //或者,node不是第一个节点但是prev状态值=1了,或者,node不是第一个节点prev状态值=0/-1但是prev的thread=null了
unparkSuccessor(node);//唤醒node这个异常节点的后面节点
} node.next = node; // help GC。断开node.next
}
} //前驱节点是-1就阻塞。前驱节点是0就是初始化设置为-1,并且清理失效的节点。直到找到-1为止。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//第一次进来,pred.waitStatus=默认值0,然后设置=-1,parkAndCheckInterrupt()失败会再次进来,直接返回true,表示应该阻塞。
//第一次进来不阻塞,第二次进来阻塞。
int ws = pred.waitStatus;//0 1 -1 -2 -3。 //前驱=-1,当前节点node应该阻塞。SIGNAL表示释放后需要唤醒后继节点。
if (ws == Node.SIGNAL)//这一行pred=-1,下一行pred可能会变为1,
return true; //其余返回false,当前节点不应该阻塞。
//清理失效的节点,并把新的前驱节点设置为-1。跳过现在是1的,并且是1之后不会变回-1。
if (ws > ) {//waitStatus = 1,清理CACELLED节点。
do {
node.prev = pred = pred.prev;//pred往前进一个,
} while (pred.waitStatus > );//<=0作为边界
pred.next = node;//回指肯定成功。 直接赋值覆盖cas赋值。 } else {//0或者-3 -2,是1不会变为-1。就是前驱后继关系。
//ws此时=0/-3/-2就说明没有取消就设置为-1,ws=1说明取消了,不能设置为-1。 /* 再次判断pred的状态为正常=0 */ compareAndSetWaitStatus(pred, ws, Node.SIGNAL);//设置新的前驱waitStatus=-1,有可能失败。被自己改为了1就以1为主,这边失败算了。
//有可能马上被设置为1,即使cas了也不保证cas下一行状态没改变,只是保证获取ws到改变期间没有变化,这一行之后变化了不管。
} /*找前驱节点:不断改变自己的前驱 在改变找到节点的next 最后修改找到节点的status*/ return false;
} static void selfInterrupt() {
//线程被打上中断标记。但是线程正常执行,只有判断是否中断然后return线程才会停止。否则跟没有中断是一样的。
Thread.currentThread().interrupt();
} private final boolean parkAndCheckInterrupt() throws InterruptedException {//阻塞并看阻塞是否成功
Thread t = Thread.currentThread();
if(t.getName().equals("") ) {
throw new InterruptedException();
}
LockSupport.park(this);//阻塞,this=ReentrantLock对象。直到被唤醒或者中断。
return Thread.interrupted();//唤醒时候,中断唤醒返回true,interrupted=true,获取到锁(设置了成员属性的值)之后还是会中断。并复位,
} //返回true当前线程就阻塞(就没有获取锁),返回false就不阻塞(就获取了锁)。一次只能一个线程执行(exclusiveOwnerThread=的线程执行),其余阻塞
// 节点加进去之后,就死循环(死循环也是在最前面才获取锁,否则死循环或者阻塞),
final boolean acquireQueued(final Node node, int arg) throws InterruptedException {
boolean failed = true;
try {
boolean interrupted = false;
//旋转1次,旋转2次,旋转3次,或者多次阻塞,compareAndSetWaitStatus(pred, ws, Node.SIGNAL)有可能一直失败,多次在于这里。
for (;;) {
final Node p = node.predecessor();
//是最前面的节点(节点从前到后依次获取锁),并且获取锁成功,就不阻塞,这个节点退出链表,更新链表,Lock方法返回。 if (p == head && tryAcquire(arg)) {//否则死循环(自旋出队)。获取锁的线程只有一个,所以这里是加锁。tryAcquire方法在Write锁里面会重写
//setHead()加锁了不用CAS(只有修改成员变量cas否则都是线程的局部内部变量)。
//head=node,node.thread=null,node.prev=null。thread就是调用这个函数的线程设置为null(已经获取了锁就移除这个节点)。 //head指向正在运行的线程的节点,就是不在排队中的节点的线程。 如果不在队列的线程获取了锁,head不变继续指向。
//如果队列中的线程获取了锁,head就指向新的获取锁的线程的节点。
setHead(node);//node变成空节点并升级为头节点。 status=0/-1没变。
p.next = null; // help GC,node变为头结点,head节点gc,
// if(Thread.currentThread().getName().equals("1号窗口") ) {
// throw new InterruptedException();
// }
failed = false;//不走cancelAcquire(node),
// if(Thread.currentThread().getName().equals("1号窗口") ) {
// throw new InterruptedException();
// }
return interrupted;//已经获取了锁,就不用中断了,lock方法返回void,不用阻塞了。先执行finally再return。
} //不是最前面的节点,或者 是最前面的节点但是获取锁失败。判断当前线程是否需要阻塞。 //shouldParkAfterFailedAcquire()判断当前线程是否需要阻塞 (通过前驱节点判断)
//parkAndCheckInterrupt()阻塞当前线程 ,阻塞失败继续死循环。
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
interrupted = true;//如果状态为true说明发生过中断,会补发一次中断,中断唤醒的线程应该去中断而不是继续执行,即调用interrupt()方法
}
} finally {//不异常也走这里然后return,
if (failed)//node成为了头节点异常,线程退出,head节点还在,
//抛出任何异常,则取消任务。这里不catch,外层函数要catch,否则异常一直在。
cancelAcquire(node);
}
} //这个线程能够响应中断,即中断线程的等待状态
private void doAcquireInterruptibly(int arg) throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);//添加一个独占的节点,
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
throw new InterruptedException();//线程中断唤醒抛出异常
}
} finally {
if (failed)
cancelAcquire(node);
}
} private boolean doAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
final long deadline = System.nanoTime() + nanosTimeout;//获取截止时间点
final Node node = addWaiter(Node.EXCLUSIVE);//节点模式是独占的
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return true;
}
nanosTimeout = deadline - System.nanoTime();//截止时间过了没有
if (nanosTimeout <= 0L)// 超过时间,依然获取不到,则返回false;否则返回true
return false;
//不会一直阻塞等待唤醒,只会阻塞一定时间,
if (shouldParkAfterFailedAcquire(p, node) && nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);//阻塞剩余的时间
if (Thread.interrupted())// 等待过程中,可以被中断,中断就抛异常,不像别的中断后继续运行,只是设置中断标记,然后我们自己去处理。
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
//ReadLock获取锁走这个方法,AQS排队,
private void doAcquireShared(int arg) throws InterruptedException {
final Node node = addWaiter(Node.SHARED);//ReadLock节点是共享模式,nextWaiter是一个空节点(共享模式是null【ReentrantLock和WriteLock】)
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {//自旋
final Node p = node.predecessor();
if (p == head) {//是第一个节点。获取锁
int r = tryAcquireShared(arg);//尝试获取锁,返回-1获取锁失败,返回1获取锁成功。
if (r >= ) {//获取成功,
// 头节点后移并传播。传播即唤醒后面连续的读节点
setHeadAndPropagate(node, r);//node变为头结点
p.next = null; // help p GC
if (interrupted)//中断唤醒在获取的锁
selfInterrupt();//设置中断标记
failed = false;
return;
}
} //设置前面-1然后阻塞,
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
interrupted = true;
}
} finally {//不异常也走这里
if (failed)
cancelAcquire(node);//异常设置自己=1,并帮助清理AQS的异常节点,建立后驱或者唤醒后面节点
}
} private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {//Semaphore排队,多线程
final Node node = addWaiter(Node.SHARED);//共享节点,Semaphore可以多线程进入,所以跟读锁是一样的,
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);//获取许可,返回减1后的许可
if (r >= ) {
setHeadAndPropagate(node, r);//r=0,说明没有许可,就不会去唤醒下一个节点
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
} /**
以共享定时模式获取
*/
private boolean doAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
final long deadline = System.nanoTime() + nanosTimeout;
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= ) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return true;
}
}
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L)
return false;
if (shouldParkAfterFailedAcquire(p, node) && nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
} protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
} protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
} protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
} protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
} protected boolean isHeldExclusively() {
throw new UnsupportedOperationException();
} //ReentrantLock和WriteLock获取锁,走这个方法,tryAcquire在ReentrantReadWrite里面有重写
public final void acquire(int arg) throws InterruptedException {//tryAcquire会先去获取锁,获取成功返回true否则false。
//addWaiter()穿建一个独占的节点添加到尾节点去。
//如果获取失败,则向等待队列中添加一个独占模式的节点,并通过acquireQueued()阻塞的等待该节点被调用(即当前线程被唤醒)。
//如果是因为被中断而唤醒的,则复现中断信号。
//acquireQueued()检查node的前继节点是否是头节点。如果是,则尝试获取锁;如果不是,或是但获取所失败,都会尝试阻塞等待。
//addWaiter时候线程异常了,不会吧head置为1.
if (!tryAcquire(arg) &&
//返回是否中断,如果返回中断,则调用当前线程的interrupt()方法
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt();//interrupted = true当前线程中断执行。重放acquireQueued里面的中断。
} //跟acquire差不多
public final void acquireInterruptibly(int arg) throws InterruptedException {
if (Thread.interrupted())//线程中断标志为1
throw new InterruptedException();//线程中断抛出异常
if (!tryAcquire(arg))//获取一次锁
doAcquireInterruptibly(arg);
} public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquire(arg) || doAcquireNanos(arg, nanosTimeout);
} public final boolean release(int arg) {//只有一个线程访问
if (tryRelease(arg)) {//true就表示OwnerThread=null了state=0了,减少state和OwnerThread。
//tryRelease()返回true表示已完全释放,可唤醒所有阻塞线程;否则没有完全释放,不需要唤醒。这个线程要再次unlock才去唤醒队列的节点。
Node h = head; //头结点只能是0/-1不可能是1。头结点由第一个入队节点创建,第一个入队节点线程异常了也只会设置第一个节点=1,不会影响到头结点,不会设置到头结点=1。
//头结点看成一直是正常节点,
if (h != null && h.waitStatus != )
unparkSuccessor(h);//head.waitStatus=-1才进来,唤醒head的后继 //h=null,头结点都还没有建立,队列也还没有建立。
/*或者h!=null&&head.waitStatus==0,
最近的正常节点不一定是紧挨着的节点,最近的正常节点中间可能还有异常节点。
head.waitStatus变成-1是由head后面的最近正常节点设置的(不一定是紧挨着的节点),说明曾经有一个正常节点执行完了3步,这个正常节点是否还正常未知。
head.waitStatus==0说明head后面都异常了,或者后面第一个正常节点(不一定是紧挨着的节点)还没有自旋到这一步,还没被阻塞 。说明没有曾经一个正常节点完成了3步,把他设置成-1。
如果head后面的正常节点都阻塞了(仅仅只需要看最近的正常节点),必然会设置head=-1,并且是由最近正常节点设置的。
*/
//lock时候head.waitStatus==0不出对,说明都不做,head不改变。唤醒时候head不变,只有获取到锁之后,head变化。
return true;
}
return false;//unlock就不会去唤醒等待的第一个节点
} //ReadLock获取锁走这个方法,tryAcquireShared在ReentrantReadWrite里面,doAcquireShared在AQS里面,
public final void acquireShared(int arg) throws InterruptedException {
if (tryAcquireShared(arg) < )//获取共享锁,读锁,获取失败,排队。
doAcquireShared(arg);//排队
} public final void acquireSharedInterruptibly(int arg) throws InterruptedException {//Semaphore获取一个许可
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < )//Semaphore获取一个许可,有许可只是没有获取到,就死循环获取许可,
doAcquireSharedInterruptibly(arg);//只有在许可没了才去排队,Semaphore排队
} public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquireShared(arg) >= || doAcquireSharedNanos(arg, nanosTimeout);
} public final boolean releaseShared(int arg) {// 读锁释放(共享锁),多线程访问
if (tryReleaseShared(arg)) {//释放锁,释放之后state=0返回true。读锁全部释放完了,才会去唤醒AQS
doReleaseShared();//唤醒head下一个
return true;
}
return false;
} public final boolean hasQueuedThreads() {
return head != tail;
} public final boolean hasContended() {
return head != null;
} public final Thread getFirstQueuedThread() {
//前面是快速失败
return (head == tail) ? null : fullGetFirstQueuedThread();
} private Thread fullGetFirstQueuedThread() {
/*
第一个节点通常是head.next,尝试获取其线程字段,确保一致的读取:
如果线程字段为空或s.prev不再是head,我们试了2次。
*/
Node h, s;
Thread st;
if (((h = head) != null && (s = h.next) != null && s.prev == head && (st = s.thread) != null)
|| ((h = head) != null && (s = h.next) != null && s.prev == head && (st = s.thread) != null))
return st; Node t = tail;
Thread firstThread = null;
while (t != null && t != head) {
Thread tt = t.thread;
if (tt != null)
firstThread = tt;
t = t.prev;
}
return firstThread;
} public final boolean isQueued(Thread thread) {
if (thread == null)
throw new NullPointerException();
for (Node p = tail; p != null; p = p.prev)
if (p.thread == thread)
return true;
return false;
} //第一个节点线程部位null并且不是共享的读锁
protected final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
return (h = head) != null && (s = h.next) != null && !s.isShared() && s.thread != null;
} //h=t有可能是指向同一个,有可能都是null。h!=t有可能指向不同的,有可能head!=null但tail=null。
//注意;一个节点变成null只能通过GC,有人指向就不会GC变成null,没有主动置为null的语句。中间节点的next不可能指向一个null,next指向的节点有人指向不会被GC,也没有主动设置为null的语句,
/*队列的状态:1(false,获取锁)head tail都是null 2(true,排队)head!=null tail=null,head的next和prev都=null,有人在初始化队列,
3(false,获取锁,此时有线程正在入队,不准确)head=tail!=null,但是next和prev都是null 4(false,获取锁)有第一个节点是当前线程,head tail都!=null,head.next=tail tail.next=null
5(true,排队)有第一个节点不是当前线程,head tail都!=null,head.next=tail tail.next=null */
//h!=t && h.next=null,那么tail就等于null还没有初始化,head!=null。
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
/*
公平原则:没人占用锁时候,要不要去获取锁。有线程在队列里面,说明那些是获取锁失败的才去排队。没有线程获取锁,线程是第一个节点获取锁,第一个节点不是当前线程排队。
当然这个不一定准确:有可能别的线程已经开始排队了,只是没有完全加入到队列,另一个线程过来还是可以立马获取锁。但是单单只看第一个节点存不存在,也不准确。
*/ //返回true排队,true&&(true||)。true&&(false||true)。有第一个节点不是当前线程,或者 ,有别人在初始化队列,.
//返回fasle获取锁: false。true&&(false||false):对裂空没有线程,有第一个节点是当前线程
return h != t && ((s = h.next) == null || s.thread != Thread.currentThread());
//h!=t&&h.next=null 或者 h!=t&&h.next.thread!=currentThread() 返回true排队。否则获取锁。
// 有人在初始化 或者 有第一个节点不是当前线程。
} public final int getQueueLength() {//不是线程安全
int n = ;
for (Node p = tail; p != null; p = p.prev) {
if (p.thread != null)
++n;
}
return n;
} public final Collection<Thread> getQueuedThreads() {//不是线程安全
ArrayList<Thread> list = new ArrayList<Thread>();
for (Node p = tail; p != null; p = p.prev) {
Thread t = p.thread;
if (t != null)
list.add(t);
}
return list;
} public final Collection<Thread> getExclusiveQueuedThreads() {//不是线程安全
ArrayList<Thread> list = new ArrayList<Thread>();
for (Node p = tail; p != null; p = p.prev) {
if (!p.isShared()) {
Thread t = p.thread;
if (t != null)
list.add(t);
}
}
return list;
} public final Collection<Thread> getSharedQueuedThreads() {//不是线程安全
ArrayList<Thread> list = new ArrayList<Thread>();
for (Node p = tail; p != null; p = p.prev) {
if (p.isShared()) {
Thread t = p.thread;
if (t != null)
list.add(t);
}
}
return list;
} public String toString() {
int s = getState();
String q = hasQueuedThreads() ? "non" : "";
return super.toString() + "[State = " + s + ", " + q + "empty queue]";
} final boolean isOnSyncQueue(Node node) {
//=-2就是不在AQS的同步队列,在condition的等待队列, prev=null肯定不在AQS,便说明节点还在Condition队列中
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
// 说明当前Node的状态不是CONDITION,同时它既有prev节点也有next节点,那么说明它在AQS队列里
if (node.next != null)
return true;
return findNodeFromTail(node);//node在不在AQS同步队列中
} private boolean findNodeFromTail(Node node) {//node在不在AQS同步队列中
Node t = tail;//从tail往前可以找到所有AQS的节点
for (;;) {
if (t == node)
return true;
if (t == null)
return false;
t = t.prev;
}
} final boolean transferForSignal(Node node) {
//唤醒从-2变成0,condition加到AQS時候狀態是0,唤醒失败就是=1,await里面唤醒AQS时候异常了,设置为1了,就去唤醒下一个,
//此时他不移到AQS继续放到condition队列,不是-2就表示已经加入到队列去了,
if (!compareAndSetWaitStatus(node, Node.CONDITION, ))
return false;
Node p = enq(node);//把节点加到AQS队列,直接把condition節點全部拿過來,屬性值=-2不變,返回AQS尾节点就是前面一个节点
int ws = p.waitStatus;
if (ws > || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))//AQS前面节点异常了,或者设置-1失败了,就唤醒自己,防止自己在AQS上的时候不能够唤醒去获取锁。
LockSupport.unpark(node.thread);//node移到AQS唤醒并且return true,
//ws<=0&&compareAndSetWaitStatus(p,ws,-1)成功,node移到AQS不唤醒并且return true。
return true;
} //中断唤醒时候=-2没有开始加入AQS返回-1,=0开始加入AQS返回1,
final boolean transferAfterCancelledWait(Node node) {
//中断唤醒,但是!=-2,说明还没有signal(有可能signal了只是还没有改变状态),還沒有加入隊列,幫助加入AQS隊列,
if (compareAndSetWaitStatus(node, Node.CONDITION, )) {
enq(node);
return true;
}
//不是-2已经设置为0了,已经signal了改变状态=0了,只是没有执行到加入到队列这行,等待signal线程加入到AQS队列,
while (!isOnSyncQueue(node))//不在AQS队列就让步。等著加到AQS去
Thread.yield();
return false;
} final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState();//Sync的state
//unlock调用release(1),这里全部释放。
if (release(savedState)) {//通过head唤醒AQS队列中下一个节点。state变成0。release成功返回true没有唤醒成功返回false
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)//release(savedState)异常了,就设置condition队列中的自己=1,
node.waitStatus = Node.CANCELLED;//出现异常=1
}
} public final boolean owns(ConditionObject condition) {
return condition.isOwnedBy(this);
} public final boolean hasWaiters(ConditionObject condition) {//不是线程安全
if (!owns(condition))
throw new IllegalArgumentException("Not owner");
return condition.hasWaiters();
} public final int getWaitQueueLength(ConditionObject condition) {//不是线程安全
if (!owns(condition))
throw new IllegalArgumentException("Not owner");
return condition.getWaitQueueLength();
} public final Collection<Thread> getWaitingThreads(ConditionObject condition) {//不是线程安全
if (!owns(condition))
throw new IllegalArgumentException("Not owner");
return condition.getWaitingThreads();
} //内部类,可以使用外部类的方法和属性
public class ConditionObject implements Condition, java.io.Serializable {
private static final long serialVersionUID = 1173984872572414699L;
private transient Node firstWaiter;//Condition的等待队列的头尾。AQS有一个队列,Condition有一个队列。
private transient Node lastWaiter; public ConditionObject() {} private Node addConditionWaiter() {
//Condition对队列的操作没考虑并发,因为对应的操作都是在线程获得锁之后才进行的 Node t = lastWaiter;
//尾节点!=-2就清除,Condition里面的节点状态只能是0和-2,否则就是无效节点
if (t != null && t.waitStatus != Node.CONDITION) {//=1,
unlinkCancelledWaiters();
t = lastWaiter;
}
//创建新的节点放到Condition队列,移除AQS节点。
//AQS节点prev!=null,next只有最后一个=null。
//Condition节点,prev=next=null,nextWaiter只有最有一个=null。
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
} //
private void doSignal(Node first) {
do {
if ((firstWaiter = first.nextWaiter) == null)//只有一个,并且移除了,就都是null。
lastWaiter = null;
first.nextWaiter = null;//把nextWaiter置为了null,
//把condition的第一個移到AQS去,不一定喚醒線程,
} while (!transferForSignal(first) && (first = firstWaiter) != null);
} //condition隊列移到AQS隊列,
private void doSignalAll(Node first) {
lastWaiter = firstWaiter = null;
do {
Node next = first.nextWaiter;
first.nextWaiter = null;
transferForSignal(first);
first = next;
} while (first != null);
} //从第一个到最后一个,清除无效!=-2的节点。单向链表。
private void unlinkCancelledWaiters() {
Node t = firstWaiter;
Node trail = null;
while (t != null) {
Node next = t.nextWaiter;
if (t.waitStatus != Node.CONDITION) {
t.nextWaiter = null;
if (trail == null)
firstWaiter = next;
else
trail.nextWaiter = next;
if (next == null)
lastWaiter = trail;
} else
trail = t;
t = next;
}
} //唤醒Condition队列第一个非CANCELLED节点。
public final void signal() {
if (!isHeldExclusively())//获得锁的线程是不是当前线程,当前线程没有获取锁,
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
} //condition全部移到AQS
public final void signalAll() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignalAll(first);
} //当前线程进入等待状态直到被通知,在此过程中对中断信号不敏感,不支持中断当前线程
public final void awaitUninterruptibly() {
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
boolean interrupted = false;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if (Thread.interrupted())//和await()区别是线程中断不会退出循环
interrupted = true;
}
try { //恢复之前的锁状态并相应中断
if (acquireQueued(node, savedState) || interrupted)
selfInterrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
} private static final int REINTERRUPT = ;//线程在等待过程中发生了中断,但不需要抛出异常
private static final int THROW_IE = -;//线程在等待过程中发生了中断,且需要抛出异常 //没有中断返回0,中断返回-1说明中断时候没有调用signal抛出异常,返回1说明中断时候调用了signal设置自己中断标记。
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ? (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) : ;
} private void reportInterruptAfterWait(int interruptMode) throws InterruptedException {
if (interruptMode == THROW_IE)
throw new InterruptedException();
else if (interruptMode == REINTERRUPT)
selfInterrupt();
} //当前获取lock的线程进入到等待队列。 只有获取锁的线程才进来,单线程的。
public final void await() throws InterruptedException {
if (Thread.interrupted()) throw new InterruptedException();
//将当前线程包装成Node(只要了線程屬性,其餘屬性沒要),尾插入到Condition等待队列中,
Node node = addConditionWaiter(); //释放锁,设置state=0,清空owenerThread,唤醒AQS,
//记录之前的state,唤醒后需要恢复状态,后续unlock()也要unlock这么多次不然将报错。
//线程从lock开始到这一行,都是要获取锁的,所以是线程安全的,。
int savedState = fullyRelease(node);
//await开始一直到上面一行,都是线程安全的,下面就不线程安全了。 int interruptMode = ;
while (!isOnSyncQueue(node)) {//当前节点到AQS队列之后就退出while循环,唤醒时候有可能不在AQS队列上就阻塞直到在AQS队列上。
LockSupport.park(this);//当前线程在condition上阻塞,等著移到AQS,然後在AQS隊列裡面喚醒。喚醒時候已經在AQS隊列裡面了。 /* 如果是中断唤醒,发生时期是任意的。可能在condition里面可能在AQS里面 */ //中断唤醒,不是AQS的head唤醒,有可能还不在AQS队列里面。checkInterruptWhileWaiting会帮助加入队列,
if ((interruptMode = checkInterruptWhileWaiting(node)) != )//等于0就不是中断唤醒,=-1抛出异常 =1设置中断标记
//没有开始加入AQS返回-1,=0开始加入AQS返回1, break;//中断唤醒就跳出while,正常唤醒就继续while看是不是在AQS队列
}
//在AQS队列上唤醒,尝试获取AQS的鎖,可能会再次在AQS上阻塞,恢复await前的状态savedState,
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)//-1
//AQS唤醒了,获取到锁了,
//后面代码又是线程安全的。
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // 清除ConditioN队列中 != -2 的节点
unlinkCancelledWaiters();
if (interruptMode != )
reportInterruptAfterWait(interruptMode);//-1就抛出异常,1就设置自己中断标记。
} public final long awaitNanos(long nanosTimeout) throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();//加入Condition队列
int savedState = fullyRelease(node);//释放锁
final long deadline = System.nanoTime() + nanosTimeout;//过期时间点
int interruptMode = ;
while (!isOnSyncQueue(node)) {
if (nanosTimeout <= 0L) {
transferAfterCancelledWait(node);//加入队列
break;
}
if (nanosTimeout >= spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
//判断线程是否被中断
if ((interruptMode = checkInterruptWhileWaiting(node)) != )
break;
nanosTimeout = deadline - System.nanoTime();
}
//响应中断
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters();
if (interruptMode != )
reportInterruptAfterWait(interruptMode);
return deadline - System.nanoTime();//返回耗时
} public final boolean awaitUntil(Date deadline) throws InterruptedException {
long abstime = deadline.getTime();
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
boolean timedout = false;
int interruptMode = ;
while (!isOnSyncQueue(node)) {
if (System.currentTimeMillis() > abstime) {
timedout = transferAfterCancelledWait(node);
break;
}
LockSupport.parkUntil(this, abstime);
if ((interruptMode = checkInterruptWhileWaiting(node)) != )
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters();
if (interruptMode != )
reportInterruptAfterWait(interruptMode);
return !timedout;
} public final boolean await(long time, TimeUnit unit) throws InterruptedException {
long nanosTimeout = unit.toNanos(time);
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
final long deadline = System.nanoTime() + nanosTimeout;
boolean timedout = false;
int interruptMode = ;
while (!isOnSyncQueue(node)) {
if (nanosTimeout <= 0L) {
timedout = transferAfterCancelledWait(node);
break;
}
if (nanosTimeout >= spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if ((interruptMode = checkInterruptWhileWaiting(node)) != )
break;
nanosTimeout = deadline - System.nanoTime();
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters();
if (interruptMode != )
reportInterruptAfterWait(interruptMode);
return !timedout;
}
//判断Condition是否属于sync.
final boolean isOwnedBy(AbstractQueuedSynchronizer1 sync) {
return sync == AbstractQueuedSynchronizer1.this;
} protected final boolean hasWaiters() {//不是线程安全
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
for (Node w = firstWaiter; w != null; w = w.nextWaiter) {
if (w.waitStatus == Node.CONDITION)
return true;
}
return false;
} protected final int getWaitQueueLength() {//不是线程安全
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
int n = ;
for (Node w = firstWaiter; w != null; w = w.nextWaiter) {
if (w.waitStatus == Node.CONDITION)
++n;
}
return n;
} protected final Collection<Thread> getWaitingThreads() {//不是线程安全
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
ArrayList<Thread> list = new ArrayList<Thread>();
for (Node w = firstWaiter; w != null; w = w.nextWaiter) {
if (w.waitStatus == Node.CONDITION) {
Thread t = w.thread;
if (t != null)
list.add(t);
}
}
return list;
}
} static final class Node {
//节点是共享节点
static final Node SHARED = new Node();
// 节点是独占节点
static final Node EXCLUSIVE = null;
//由于超时或中断,此节点被取消。
static final int CANCELLED = ;
//-1,唤醒,这个节点成为head结点时候,unlock会去通过head唤醒后继节点
static final int SIGNAL = -;
//表示当前节点在等待condition,即在condition队列中;
static final int CONDITION = -;
//表示releaseShared需要被传播给后续节点(仅在共享模式下使用);
static final int PROPAGATE = -;
volatile int waitStatus;//默认0,
//前继节点;
volatile Node prev;
//后继节点;
volatile Node next;
//当前线程。
volatile Thread thread; //condition节点的属性,ReentrantLock时候nextWaiter=null,
Node nextWaiter; final boolean isShared() {
return nextWaiter == SHARED;
} final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
} Node() { // Used to establish initial head or SHARED marker
this.name = "头结点";
}
volatile String name;
Node(Thread thread, Node mode) { //用于锁
this.nextWaiter = mode;//独占锁的nextWaiter是模式=Node.EXCLUSIVE=null,
this.thread = thread;
this.name = this.thread.getName();
} Node(Thread thread, int waitStatus) { //用于Condition
this.waitStatus = waitStatus;
this.thread = thread;
this.name = this.thread.getName();
}
public String toString() {
return "name:"+this.name+",prev:" + prev.name + ",next:" + next.name;
}
} //head指向获取锁的线程的节点,仅仅保存next结点的引用。。如果是没有入队的线程获取锁,head不变。head节点是一个哨兵节点 ,头结点不存储Thread,仅
private transient volatile Node head; private transient volatile Node tail;//tail指向链表的最后一个节点 //如果没有记录重入次数,则第一次释放锁时,会一次性把ownerThread多次重入的锁都释放掉,而此时“锁中的代码”还没有执行完成,造成混乱。
private volatile int state;//所有者线程已经重复获取该锁的次数,每次加1。 private static Unsafe unsafe;
private static final long stateOffset;//state
private static final long headOffset;//head
private static final long tailOffset;//tail
private static final long waitStatusOffset;//Node.waitStatus
private static final long nextOffset;//Node.next static {
try {
unsafe = java.security.AccessController
.doPrivileged(new java.security.PrivilegedExceptionAction<sun.misc.Unsafe>() {
public sun.misc.Unsafe run() throws Exception {
Class<sun.misc.Unsafe> k = sun.misc.Unsafe.class;
for (java.lang.reflect.Field f : k.getDeclaredFields()) {
f.setAccessible(true);
Object x = f.get(null);
if (k.isInstance(x))
return k.cast(x);
}
throw new NoSuchFieldError("the Unsafe");
}
});
stateOffset = unsafe.objectFieldOffset(AbstractQueuedSynchronizer1.class.getDeclaredField("state"));
headOffset = unsafe.objectFieldOffset(AbstractQueuedSynchronizer1.class.getDeclaredField("head"));
tailOffset = unsafe.objectFieldOffset(AbstractQueuedSynchronizer1.class.getDeclaredField("tail"));
waitStatusOffset = unsafe.objectFieldOffset(Node.class.getDeclaredField("waitStatus"));
nextOffset = unsafe.objectFieldOffset(Node.class.getDeclaredField("next")); } catch (Exception ex) {
throw new Error(ex);
}
} protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
} private final boolean compareAndSetHead(Node update) {
return unsafe.compareAndSwapObject(this, headOffset, null, update);
} private final boolean compareAndSetTail(Node expect, Node update) {
return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
} private static final boolean compareAndSetWaitStatus(Node node, int expect, int update) {
return unsafe.compareAndSwapInt(node, waitStatusOffset, expect, update);
} private static final boolean compareAndSetNext(Node node, Node expect, Node update) {
return unsafe.compareAndSwapObject(node, nextOffset, expect, update);
}
}
AQS4源码的更多相关文章
- AQS源码分析笔记
经过昨晚的培训.对AQS源码的理解有所加强,现在写个小笔记记录一下 同样,还是先写个测试代码,debug走一遍流程, 然后再总结一番即可. 测试代码 import java.util.concurre ...
- 【原】Android热更新开源项目Tinker源码解析系列之三:so热更新
本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ...
- C# ini文件操作【源码下载】
介绍C#如何对ini文件进行读写操作,C#可以通过调用[kernel32.dll]文件中的 WritePrivateProfileString()和GetPrivateProfileString()函 ...
- 【原】FMDB源码阅读(三)
[原]FMDB源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 FMDB比较优秀的地方就在于对多线程的处理.所以这一篇主要是研究FMDB的多线程处理的实现.而 ...
- 从源码看Azkaban作业流下发过程
上一篇零散地罗列了看源码时记录的一些类的信息,这篇完整介绍一个作业流在Azkaban中的执行过程,希望可以帮助刚刚接手Azkaban相关工作的开发.测试. 一.Azkaban简介 Azkaban作为开 ...
- 【原】Android热更新开源项目Tinker源码解析系列之一:Dex热更新
[原]Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Tinker是微信的第一个开源项目,主要用于安卓应用bug的热修复和功能的迭代. Tinker github地址:http ...
- 【原】Android热更新开源项目Tinker源码解析系列之二:资源文件热更新
上一篇文章介绍了Dex文件的热更新流程,本文将会分析Tinker中对资源文件的热更新流程. 同Dex,资源文件的热更新同样包括三个部分:资源补丁生成,资源补丁合成及资源补丁加载. 本系列将从以下三个方 ...
- 多线程爬坑之路-Thread和Runable源码解析之基本方法的运用实例
前面的文章:多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类) 多线程爬坑之路-Thread和Runable源码解析 前面 ...
- SDWebImage源码解读之SDWebImageDownloaderOperation
第七篇 前言 本篇文章主要讲解下载操作的相关知识,SDWebImageDownloaderOperation的主要任务是把一张图片从服务器下载到内存中.下载数据并不难,如何对下载这一系列的任务进行设计 ...
随机推荐
- python 数据结构之图的储存方式
参考链接:https://blog.csdn.net/u014281392/article/details/79120406 所描述的图的结构为: 下面介绍不同的储存方式,我想不必详细分别是每个名称都 ...
- 【开发笔记】- Idea启动Gradle报错:Warning:Unable to make the module: reading, related gradle configuration was not found. Please, re-import the Gradle project and try again
报错信息: Warning:Unable to make the module: reading, related gradle configuration was not found. Please ...
- Delphi中窗体的事件
Delphi中窗体的事件 Form窗体可以响应各种各样的时间,在Object Inspector的Events页面中罗列了一大堆,如下图: 下面将要列出一些常用的事件. 1.OnActivate 当窗 ...
- JavaScript中数组相关的属性方法
下面的这些方法会改变调用它们的对象自身的值: Array.prototype.copyWithin() 在数组内部,将一段元素序列拷贝到另一段元素序列上,覆盖原有的值. Array.prototype ...
- Excel单元格锁定及解锁
Excel VBA 宏 学习使用: 一.工作表单元格的锁定: 1.选择需要锁定的单元格. 2.鼠标右键----设置单元格格式. 3.设置 “保护”--锁定 -- 确定. 4.回到表头,[审阅]--- ...
- ELK日志系统之说说logstash的各种配置
当我们在设置配置logstash的conf文件内容时,日志数据的来源有以下几种配置: tcp形式:一个项目或其他日志数据来源用tcp协议的远程传输方式,将日志数据传入logstash input { ...
- day 68
目录 表单指令 条件指令 循环指令 分隔符 过滤器 计算属性 监听属性 表单指令 v-model="变量",变量值与表单标签的value相关 v-model可以实现数据的双向绑定, ...
- django rest_framework vue 实现用户列表分页
django rest_framework vue 实现用户列表分页 后端 配置urls # 导入view from api.appview.userListView import userListV ...
- mysql 连接数用完,root也无法登陆的处理方法
gdb -p $(pidof mysqld) -ex "set max_connections=1500" -batch 使用 gdb 临时调大 参数 max_connection ...
- cookie跨域解决方案
cookie的名/值对中的值不允许出现分号.逗号和空白符,因此在设置cookie前要用encodeURIComponent()编码,读取时再用decodeURIComponent()解码. cooki ...