Java中的队列同步器AQS

一、AQS概念

  1、队列同步器是用来构建锁或者其他同步组件的基础框架,使用一个int型变量代表同步状态,通过内置的队列来完成线程的排队工作。

  2、下面是JDK8文档中对于AQS的部分介绍

1  public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements Serializable 2  提供一个框架,用于实现依赖先进先出(FIFO)等待队列的阻塞锁和相关同步器(信号量,事件等)。 该类被设计为大多数类型的同步器的有用依据,这些同步器依赖于单个原子int值来表示状态。子类必须定义改变此状态的protected方法,以及根据该对象被获取或释放来定义该状态的含义。给定这些,这个类中的其他方法执行所有排队和阻塞机制。 子类可以保持其他状态字段,但只以原子方式更新int使用方法操纵值getState()setState(int)和compareAndSetState(int, int)被跟踪相对于同步。 3  此类支持默认独占模式和共享模式。 当以独占模式获取时,尝试通过其他线程获取不能成功。 多线程获取的共享模式可能(但不需要)成功。 除了在机械意义上,这个类不理解这些差异,当共享模式获取成功时,下一个等待线程(如果存在)也必须确定它是否也可以获取。 在不同模式下等待的线程共享相同的FIFO队列。 通常,实现子类只支持这些模式之一,但是两者都可以在ReadWriteLock中发挥作用。仅支持独占或仅共享模式的子类不需要定义支持未使用模式的方法。

  总结来说就是:

  ①子类通过继承AQS并实现其抽象方法来管理同步状态,对于同步状态的更改通过提供的getState()、setState(int state)、compareAndSetState(int expect, int update)来进行操作,因为使用CAS操作保证同步状态的改变是原子的

  ②子类被推荐定义为自定义同步组件的静态内部类,同步器本身并没有实现任何的同步接口,仅仅是定义了若干状态获取和释放的方法来提供自定义同步组件的使用。

  ③同步器既可以支持独占式的获取同步状态,也可以支持共享式的获取同步状态(ReentrantLock、ReentrantReadWriteLock、CountDownLatch等不同类型的同步组件)

  3、同步器是实现锁的关键,在锁的实现中聚合同步器,利用同步器实现锁的语义;

二、AQS的接口和实例

1、同步器的设计实现原理

  继承同步器并且重写指定的方法,然后将同步器组合在自定义同步组件的实现中,并且调用同步器提供的模板方法(这些模板方法会调用重写的方法);而重写指定的方法的时候,需要使用**getState()、setState(int state)、compareAndSetState(int expect, int update)**来访问或者更新同步状态。下面是源码中state变量和三个方法的定义声明实现

1 1   /** 2 2 * .(同步状态) 3 3 */ 4 4 private volatile int state; 5 5 6 6 /** 7 7 * (返回当前的同步状态) 8 8 * 此操作的内存语义为@code volatile read 9 9 */ 1010 protected final int getState() { 1111 return state; 1212 } 1313 1414 /** 1515 * (设置新的同步状态) 1616 * 此操作的内存语义为@code volatile read 1717 */ 1818 protected final void setState(int newState) { 1919 state = newState; 2020 } 2121 2222 /** 2323 * (如果要更新的状态和期望的状态相同,那就通过原子的方式更新状态) 2424 * ( 此操作的内存语义为@code volatile read 和 write) 2525 * (如果更新的状态和期望的状态不同就返回false) 2626 */ 2727 protected final boolean compareAndSetState(int expect, int update) { 2828 return unsafe.compareAndSwapInt(this, stateOffset, expect, update); 2929 }

2、下面介绍AQS提供可被重写的方法

1 1 /** 2 2 * 独占式的获取同步状态,实现该方法需要查询当前状态并判断同步状态是否符合预期,然后再进行CAS设置同步状态 3 3 * 4 4 */ 5 5 protected boolean tryAcquire(int arg) { 6 6 throw new UnsupportedOperationException(); 7 7 } 8 8 9 9 /** 1010 * 独占式的释放同步状态,等待获取同步状态的线程可以有机会获取同步状态 1111 * 1212 */ 1313 protected boolean tryRelease(int arg) { 1414 throw new UnsupportedOperationException(); 1515 } 1616 1717 /** 1818 * 尝试以共享模式获取。 该方法应该查询对象的状态是否允许在共享模式下获取该对象,如果是这样,就可以获取它。 该方法总是由执行获取的线程调用。 1919 * 如果此方法报告失败,则获取方法可能将线程排队(如果尚未排队),直到被其他线程释放为止。 获取失败时返回负值,如果在获取成共享模式下功但没 2020 * 有后续共享模式获取可以成功,则为零; 并且如果以共享模式获取成功并且随后的共享模式获取可能成功,则为正值,在这种情况下,后续等待线程必须检查可用性。 2121 */ 2222 protected int tryAcquireShared(int arg) { 2323 throw new UnsupportedOperationException(); //如果不支持共享模式 ,会抛出该异常 2424 } 2525 2626 /** 2727 * 尝试将状态设置为以共享模式释放同步状态。 该方法总是由执行释放的线程调用。 2828 */ 2929 protected int tryReleaseShared(int arg) { 3030 throw new UnsupportedOperationException(); //如果不支持共享模式 ,会抛出该异常 3131 } 3232 3333 /** 3434 * 当前同步器是否在独占模式下被线程占用,一般该方法表示是否被当前线程所独占 3535 */ 3636 protected int isHeldExclusively(int arg) { 3737 throw new UnsupportedOperationException(); //如果不支持共享模式 ,会抛出该异常 3838 }

3、同步器提供的模板方法

  在实现自定义同步组件的时候,需要重写上面的方法,而下面的模板方法会调用上面重写的方法。下面介绍同步器提供的模板方法

1 1 /** 2 2 * 以独占模式获取,忽略中断。 通过调用至少一次tryAcquire(int)实现,成功返回。 否则线 3 3 * 程排队,可能会重复阻塞和解除阻塞,直到成功才调用tryAcquire(int) 4 4 */ 5 5 public final void acquire(int arg) {...} 6 6 7 7 /** 8 8 * 以独占方式获得,如果中断,中止。 通过首先检查中断状态,然后调用至少一次 9 9 * tryAcquire(int) ,成功返回。 否则线程排队,可能会重复阻塞和解除阻塞,调用 1010 * tryAcquire(int)直到成功或线程中断。 1111 */ 1212 public final void acquireInterruptibly(int arg) throws InterruptedException {...} 1313 1414 /** 1515 * 尝试以独占模式获取,如果中断则中止,如果给定的超时时间失败。 首先检查中断状态,然 1616 * 后调用至少一次tryAcquire(int) ,成功返回。 否则,线程排队,可能会重复阻塞和解除阻 1717 * 塞,调用tryAcquire(int)直到成功或线程中断或超时 1818 */ 1919 public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {...} 2020 2121 /** 2222 * 以共享模式获取,忽略中断。 通过首次调用至少一次执行 tryAcquireShared(int),成功返 2323 * 回。 否则线程排队,可能会重复阻塞和解除阻塞,直到成功调用tryAcquireShared(int) 。 2424 */ 2525 public final void acquireShared(int arg){...} 2626 2727 /** 2828 * 以共享方式获取,如果中断,中止。 首先检查中断状态,然后调用至少一次 2929 * tryAcquireShared(int) ,成功返回。 否则线程排队,可能会重复阻塞和解除阻塞,调用 3030 * tryAcquireShared(int)直到成功或线程中断。 3131 */ 3232 public final void acquireSharedInterruptibly(int arg) throws InterruptedException{...} 3333 3434 /** 3535 * 尝试以共享模式获取,如果中断则中止,如果给定的时间超过,则失败。 通过首先检查中断 3636 * 状态,然后调用至少一次tryAcquireShared(int) ,成功返回。 否则,线程排队,可能会重 3737 * 复阻塞和解除阻塞,调用tryAcquireShared(int)直到成功或线程中断或超时。 3838 */ 3939 public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException{...} 4040 4141 /** 4242 * 独占式的释放同步状态,该方法会在释放同步状态之后,将同步队列中的第一个节点包含的线程唤醒 4343 */ 4444 public final boolean release(int arg){...} 4545 4646 /** 4747 * 共享式的释放同步状态 4848 */ 4949 public final boolean releaseShared(int arg){...} 5050 5151 /** 5252 * 获取在等待队列上的线程集合 5353 */ 5454 public final Collection<Thread> getQueuedThreads(){...}

三、队列同步器的实现分析

 1、同步队列

a)t同步队列的实现原理

  AQS内部维护一个同步队列来完成同步状态的管理,当前线程获取同步状态失败的时候,AQS会将当前线程以及等待状态信息构造成一个结点Node并将其加入同步队列中,同时阻塞当前线程,当同步状态由持有线程释放的时候,会将同步队列中的首节点唤醒使其再次尝试获取同步状态。同步队列中的结点用来保存获取同步状态失败的线程的线程引用、等待状态以及前驱结点和后继结点。下面是Node的属性分析

1 1 static final class Node { 2 2 /** 共享模式下构造结点 */ 3 3 static final Node SHARED = new Node(); 4 4 /** 独占模式下构造结点 */ 5 5 static final Node EXCLUSIVE = null; 6 6 7 7 /** 用于指示线程已经取消的waitStatus值(由于在同步队列中等待的线程等待超时或者发生中断,需要从同步队列中取消等待,结点进入该状态将不会发生变化)*/ 8 8 static final int CANCELLED = 1; 9 9 /** waitstatus值指示后续线程需要取消等待(后继结点的线程处于等待状态,而当前结点的线程如果释放了同步状态或者CANCELL,将会通知后继结点的线程以运行) */ 1010 static final int SIGNAL = -1; 1111 /**waitStatus值表示线程正在等待条件(原本结点在等待队列中,结点线程等待在Condition上,当其他线程对Condition调用了signal()方法之后)该结点会从         等待队列中转移到同步队列中,进行同步状态的获取 */ 1212 static final int CONDITION = -2; 1313 /** 1414 * waitStatus值表示下一个共享式同步状态的获取应该无条件传播下去 1515 */ 1616 static final int PROPAGATE = -3; 1717 1818 /** 1919 * 不同的等到状态的int值 2020 */ 2121 volatile int waitStatus; 2222 2323 /** 2424 * 前驱结点,当结点加入同步队列将会被设置前驱结点信息 2525 */ 2626 volatile Node prev; 2727 2828 /** 2929 * 后继结点 3030 */ 3131 volatile Node next; 3232 3333 /** 3434 * 当前获取到同步状态的线程 3535 */ 3636 volatile Thread thread; 3737 3838 /** 3939 * 等待队列中的后继结点,如果当前结点是共享的,那么这个字段是一个SHARED常量;也就是说结点类型(独占和共享)和等待队列中的后继结点公用一个字段 4040 */ 4141 Node nextWaiter; 4242 4343 /** 4444 * 如果是共享模式下等待,那么返回true(因为上面的Node nextWaiter字段在共享模式下是一个SHARED常量) 4545 */ 4646 final boolean isShared() { 4747 return nextWaiter == SHARED; 4848 } 4949 5050 final Node predecessor() throws NullPointerException { 5151 Node p = prev; 5252 if (p == null) 5353 throw new NullPointerException(); 5454 else 5555 return p; 5656 } 5757 5858 Node() { // 用于建立初始头结点或SHARED标记 5959 } 6060 6161 Node(Thread thread, Node mode) { // 用于添加到等待队列 6262 this.nextWaiter = mode; 6363 this.thread = thread; 6464 } 6565 6666 Node(Thread thread, int waitStatus) { // Used by Condition 6767 this.waitStatus = waitStatus; 6868 this.thread = thread; 6969 } 7070 }

b)同步队列示意图和简单分析

  ①同步队列示意图:当一个线程获取了同步状态后,其他线程不能获取到该同步状态,就会被构造称为Node然后添加到同步队列之中,这个添加的过程基于CAS保证线程安全性。

  ②同步队列遵循先进先出(FIFO),首节点是获取到同步状态的结点,首节点的线程在释放同步状态的时候将会唤醒后继结点(然后后继结点就会变成新的首节点等待获取同步状态)

2、独占式同步状态的获取和释放

  ①前面说过,同步器的acquire()方法会获取同步状态,这个方法对不会响应中断,也就是说当线程获取通同步状态失败后会被构造成结点加入到同步队列中,当线程被中断时不会从同步队列中移除。

1 1 /** 2 2 * ①首先调用tryAcquire方法尝试获取同步状态,如果获取同步状态失败,就进行下面的操作 3 3 * ②获取失败:按照独占式的模式构造同步结点并通过addWaiter方法将结点添加到同步队列的尾部 4 4 * ③通过acquireQueue方法自旋获取同步状态。 5 5 * ④如果获取不到同步状态,就阻塞结点中的线程,而结点中的线程唤醒主要是通过前驱结点的出队或者被中断来实现 6 6 */ 7 7 public final void acquire(int arg) { 8 8 if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 9 9 selfInterrupt(); 1010 }

   ②下面是addWaiter、enq和自旋获取同步状态acquireQueue方法的实现(该方法的主要作用就是将获取同步状态失败的线程构造成结点然后添加到同步队列的队尾)

1 1 private Node addWaiter(Node mode) { 2 2 Node node = new Node(Thread.currentThread(), mode); 3 3 //尝试直接放在队尾 4 4 Node pred = tail; //直接获取同步器的tail结点 5 5 if (pred != null) { 6 6 node.prev = pred; 7 7 if (compareAndSetTail(pred, node)) { 8 8 //队尾结点不为空通过原子操作将构造的结点置为队尾结点 9 9 pred.next = node; 1010 return node; 1111 } 1212 } 1313 //采用自旋方式保证构造的结点添加到同步队列中 1414 enq(node); 1515 return node; 1616 } 1717 private Node enq(final Node node) { 1818 for (;;) { //死循环知道添加成功 1919 Node t = tail; 2020 if (t == null) { // Must initialize 2121 if (compareAndSetHead(new Node())) 2222 tail = head; 2323 } else { 2424 node.prev = t; 2525 //通过CAS方式将结点添加到同步队列之后才会返回,否则就会不断尝试添加(这样实际上就是在并发情况下,把向同步队列添加Node变得串行化了) 2626 if (compareAndSetTail(t, node)) { 2727 t.next = node; 2828 return t; 2929 } 3030 } 3131 } 3232 } 3333 /** 3434 * 通过tryAcquire()和addWaiter(),表示该线程获取同步状态已经失败,被放入同步 3535 * 队列尾部了。线程阻塞等待直到其他线程(前驱结点获得同步装填或者被中断)释放同步状 3636 * 态后唤醒自己,自己才能获得。 3737 */ 3838 final boolean acquireQueued(final Node node, int arg) { 3939 boolean failed = true; 4040 try { 4141 boolean interrupted = false; 4242 //线程在死循环的方式中尝试获取同步状态 4343 for (;;) { 4444 final Node p = node.predecessor(); //获取前驱结点 4545 //只有前驱接待是头结点的时候才能尝试获取同步状态 4646 if (p == head && tryAcquire(arg)) { 4747 setHead(node); //获取到同步状态之后,就将自己设置为头结点 4848 p.next = null; //前驱结点已经获得同步状态去执行自己的程序了,所以需要释放掉占用的同步队列的资源,由JVM回收 4949 failed = false; 5050 return interrupted; 5151 } 5252 //如果获取同步状态失败,应该自旋等待继续获取并且校验自己的中断标志位信息 5353 if (shouldParkAfterFailedAcquire(p, node) && 5454 parkAndCheckInterrupt()) 5555 interrupted = true; //如果被中断,就改变自己的中断标志位状态信息 5656 } 5757 } finally { 5858 if (failed) 5959 cancelAcquire(node); 6060 } 6161 }

  ③独占式获取同步状态的整个流程

  ④独占式同步器的释放:release方法执行时,会唤醒头结点的后继结点线程

1public final boolean release(int arg) { 2 if (tryRelease(arg)) { 3 Node h = head;//头结点 4 //唤醒头结点的后继结点线程 5 if (h != null && h.waitStatus != 0) 6 unparkSuccessor(h); 7 return true; 8 } 9 return false; 10}

3、共享式同步状态的获取和释放 

  ①共享式获取和独占式获取最主要的区别是能否有多个线程同时获取到同步状态。如图所示简易描述二者的区别(共享式访问的时候,可以允许多个线程访问资源,但是存在独占式访问的时候,同一时刻其他的不管是共享还是独占都会被阻塞)

  ②关于共享式获取同步状态的方法

1 1 /** 2 2 * 此方法是共享模式下线程获取共享同步状态的顶层入口。它会尝试去获取同步状态,获取成功则直接返回, 3 3 * 获取失败则进入等待队列一直尝试获取(执行doAcquireShared方法体中的内容),直到获取到资源为止(条件就是tryAcquireShared方法返回值大于等于0),整个过程忽略中断 4 4 */ 5 5 public final void acquireShared(int arg) { 6 6 if (tryAcquireShared(arg) < 0) 7 7 doAcquireShared(arg); 8 8 } 9 9 /** 1010 * "自旋"尝试获取同步状态 1111 */ 1212 private void doAcquireShared(int arg) { 1313 //首先将该线程包括线程引用、等待状态、前驱结点和后继结点的信息封装台Node中,然后添加到等待队列里面(一共享模式添加) 1414 final Node node = addWaiter(Node.SHARED); 1515 boolean failed = true; 1616 try { 1717 boolean interrupted = false; //当前线程的中断标志 1818 for (;;) { 1919 final Node p = node.predecessor(); //获取前驱结点 2020 if (p == head) { 2121 //当前驱结点是头结点的时候就会以共享的方式去尝试获取同步状态 2222 int r = tryAcquireShared(arg); 2323 //判断tryAcquireShared的返回值 2424 if (r >= 0) { 2525 //如果返回值大于等于0,表示获取同步状态成功,就修改当前的头结点并将信息传播都后续的结点队列中 2626 setHeadAndPropagate(node, r); 2727 p.next = null; // 释放掉已经获取到同步状态的前驱结点的资源 2828 if (interrupted) 2929 selfInterrupt(); //检查中断标志 3030 failed = false; 3131 return; 3232 } 3333 } 3434 if (shouldParkAfterFailedAcquire(p, node) && 3535 parkAndCheckInterrupt()) 3636 interrupted = true; 3737 } 3838 } finally { 3939 if (failed) 4040 cancelAcquire(node); 4141 } 4242 }

  根据源代码我们可以了解共享式获取同步状态的整个过程

  首先同步器会调用tryAcquireShared方法来尝试获取同步状态,然后根据这个返回值来判断是否获取到同步状态(当返回值大于等于0可视为获取到同步状态);如果第一次获取失败的话,就进入'自旋'状态(执行doAcquireShared方法)一直尝试去获取同步状态;在自旋获取中,如果检查到当前前驱结点是头结点的话,就会尝试获取同步状态,而一旦获取成功(tryAcquireShared方法返回值大于等于0)就可以从自旋状态退出。

  另外,还有一点就是上面说到的一个处于等待队列的线程要想开始尝试去获取同步状态,需要满足的条件就是前驱结点是头结点,那么它本身就是整个队列中的第二个结点。当头结点释放掉所有的临界资源之后,我们考虑每个线程运行所需资源的不同数量问题,如下图所示

  ③共享式同步状态的释放

  对于支持共享式的同步组件(即多个线程同同时访问),它们和独占式的主要区别就是tryReleaseShared方法必须确保同步状态的释放是线程安全的(CAS的模式来释放同步状态,因为既然是多个线程能够访问,那么释放的时候也会是多个线程的,就需要保证释放时候的线程安全)

1 1 /** 2 2 * 该方法是共享模式下线程释放共享资源的顶层入口。它会释放指定量的资源,如果成功释放且允许唤醒等待线程,它会唤醒等待队列里的其他线程来获取资源。 3 3 */ 4 4 public final boolean releaseShared(int arg) { 5 5 if (tryReleaseShared(arg)) { 6 6 doReleaseShared(); // 7 7 return true; 8 8 } 9 9 return false; 1010 }

四、自定义同步组件的实现

  1、共享式锁的实现

  ①、自定义一个同步组件,可以允许两个线程访问(共享式同步组件),超过两个线程就会被阻塞。

  ②、既然是共享式同步组件,按照前面所说的,组件本身需要使用AQS提供的共享式模板方法acquireShared等;组件的内部类需要实现AQS,并且重写关于共享式获取同步状态的方法(tryAcquireShared()、tryReleaseShared()等共享模式下的方法)。

  ③、既然是两个线程能够同时访问的话,那么状态数的取值范围就是0、1、2了,每当一个线程获取到同步状态的时候state值减1,反之就会增加1;当state值为0的时候就会阻塞其他想要获取同步状态的线程。对于同步状态的更改需要使用CAS来进行保证原子性。

1 1 package cn.source.concurrent; 2 2 3 3 import java.util.concurrent.TimeUnit; 4 4 import java.util.concurrent.locks.AbstractQueuedSynchronizer; 5 5 import java.util.concurrent.locks.Condition; 6 6 import java.util.concurrent.locks.Lock; 7 7 8 8 public class TestAQS implements Lock{ 9 9 1010 private Sync sync = new Sync(2); 1111 1212 private static class Sync extends AbstractQueuedSynchronizer { 1313 1414 Sync(int num) { 1515 if(num <= 0) { 1616 throw new RuntimeException("num需要大于0"); 1717 } 1818 setState(num); 1919 } 2020 2121 @Override 2222 protected int tryAcquireShared(int arg) { 2323 for(; ;) { 2424 int currentState = getState(); 2525 int newState = currentState - arg; 2626 if(newState < 0 || compareAndSetState(currentState, newState)) { 2727 return newState; 2828 } 2929 } 3030 } 3131 3232 @Override 3333 protected boolean tryReleaseShared(int arg) { 3434 for(; ;) { 3535 int currentState = getState(); 3636 int newState = currentState + arg; 3737 if(compareAndSetState(currentState, newState)) { 3838 return true; 3939 } 4040 } 4141 } 4242 4343 4444 } 4545 @Override 4646 public void lock() { 4747 sync.acquireShared(1); 4848 } 4949 5050 @Override 5151 public void unlock() { 5252 sync.releaseShared(1); 5353 } 5454 5555 //...... 5656 }

共享式锁

1 1 /** 2 2 * 测试结果:输出的线程名称是成对的,保证同一时刻只有两个线程能够获取到锁 3 3 * 4 4 */ 5 5 public class TestLockShare { 6 6 @Test 7 7 public void test() { 8 8 Lock lock = new TestAQS(); 9 9 class Worker extends Thread { 1010 1111 @Override 1212 public void run() { 1313 while(true) { 1414 lock.lock(); 1515 try { 1616 Thread.sleep(1000); 1717 System.out.println(Thread.currentThread().getName()); 1818 Thread.sleep(1000); 1919 } catch (Exception e) { 2020 e.printStackTrace(); 2121 } finally { 2222 lock.unlock(); 2323 } 2424 } 2525 } 2626 2727 } 2828 2929 for (int i = 0; i < 8; i++) { 3030 Worker worker = new Worker(); 3131 worker.setDaemon(true); 3232 worker.start(); 3333 3434 } 3535 for (int i = 0; i < 8; i++) { 3636 try { 3737 Thread.sleep(1000); 3838 } catch (InterruptedException e) { 3939 // TODO Auto-generated catch block 4040 e.printStackTrace(); 4141 } 4242 System.out.println(); 4343 } 4444 } 4545 }

共享式锁测试

  2、独占式锁的实现

1 1 package cn.source.concurrent; 2 2 3 3 import java.util.concurrent.TimeUnit; 4 4 import java.util.concurrent.locks.AbstractQueuedSynchronizer; 5 5 import java.util.concurrent.locks.Condition; 6 6 import java.util.concurrent.locks.Lock; 7 7 8 8 public class Mutex implements Lock{ 9 9 1010 private Sync sync = new Sync(); 1111 1212 private static class Sync extends AbstractQueuedSynchronizer { 1313 1414 /** 1515 * 尝试获取资源,立即返回。成功则返回true,否则false。 1616 */ 1717 @Override 1818 protected boolean tryAcquire(int arg) { 1919 if(compareAndSetState(0, 1)) {//state为0才设置为1,不可重入! 2020 setExclusiveOwnerThread(Thread.currentThread());//设置为当前线程独占资源 2121 return true; 2222 } 2323 return false; 2424 } 2525 2626 /** 2727 * 尝试释放资源,立即返回。成功则为true,否则false。 2828 */ 2929 @Override 3030 protected boolean tryRelease(int arg) { 3131 if(getState() == 0) { //既然来释放,那肯定就是已占有状态了。只是为了保险,多层判断! 3232 throw new IllegalMonitorStateException(); 3333 } 3434 setExclusiveOwnerThread(null); 3535 setState(0); 3636 return true; 3737 } 3838 3939 @Override 4040 protected boolean isHeldExclusively() { 4141 // 判断是否锁定状态 4242 return getState() == 1; 4343 } 4444 4545 } 4646 4747 @Override 4848 public void lock() { 4949 sync.acquire(1); 5050 } 5151 5252 @Override 5353 public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { 5454 return sync.tryAcquire(1); 5555 } 5656 5757 @Override 5858 public void unlock() { 5959 sync.release(1); 6060 } 6161 6262 }

独占式锁

1 1 public class TestMutex { 2 2 @Test 3 3 public void test() { 4 4 Lock lock = new Mutex(); 5 5 class Worker extends Thread { 6 6 7 7 @Override 8 8 public void run() { 9 9 while(true) { 1010 lock.lock(); 1111 try { 1212 Thread.sleep(1000); 1313 System.out.println(Thread.currentThread().getName()); 1414 Thread.sleep(1000); 1515 } catch (Exception e) { 1616 e.printStackTrace(); 1717 } finally { 1818 lock.unlock(); 1919 } 2020 } 2121 } 2222 2323 } 2424 2525 for (int i = 0; i < 8; i++) { 2626 Worker worker = new Worker(); 2727 worker.setDaemon(true); 2828 worker.start(); 2929 3030 } 3131 for (int i = 0; i < 8; i++) { 3232 try { 3333 Thread.sleep(1000); 3434 } catch (InterruptedException e) { 3535 e.printStackTrace(); 3636 } 3737 System.out.println(); 3838 } 3939 } 4040 }

独占式锁测试

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )