Java多线程(6):锁与AQS(下)

您好,我是湘王,这是我的博客园,欢迎您来,欢迎您再来~

 

之前说过,AQS(抽象队列同步器)Java锁机制的底层实现。既然它这么优秀,是骡子是马,就拉出来溜溜吧。

首先用重入锁来实现简单的累加,就像这样:

/**  * 用重入锁实现累加  *  * @author 湘王  */ public class MyLockTest {     private final Lock lock = new ReentrantLock();     private int value;     public int getNext() {         lock.lock();         try {             value++;         } finally {             lock.unlock();         }         return value;     }     public static void main(String[] args) {         MyLockTest myLock = new MyLockTest();         for (int i = 0; i < 5; i++) {             new Thread(new Runnable() {                 @Override                 public void run() {                     for (int i = 0; i < 5; i++) {                         System.out.println(myLock.getNext());                     }                 }             }).start();         }     } }

 

运行结果显示数据有重复:

Java多线程(6):锁与AQS(下)

 

 

 

这么简单的计算都能出现重复,这肯定是无法接受的。

再用独占锁来试试看:

/**  * 利用AQS实现自定义独占锁  *  * @author 湘王  */ public class MyExclusiveLock implements Lock {     @Override     public void lock() {      }      @Override     public void lockInterruptibly() throws InterruptedException {      }      @Override     public boolean tryLock() {         return false;     }      @Override     public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {         return false;     }      @Override     public void unlock() {      }      @Override     public Condition newCondition() {         return null;     } }

 

 

可以看到,实现lock接口,就需要实现若干自定义的接口。然后以内部类继承AQS的方式,实现排他锁,昨天也说过,AQS中tryAcquire()和tryRelease()是一一对应的,也就是也管获取,一个管释放,所以代码是:

/**  * 内部类继承AQS的方式,实现排他锁  */ private static class SyncHelper extends AbstractQueuedSynchronizer {     private static final long serialVersionUID = -7666580981453962426L;      /**      * 第一个线程进来,拿到锁就返回true;后面的线程进来,拿不到锁就返回false      */     @Override     protected boolean tryAcquire(int arg) {         // 获取资源状态         int state = getState();         if (0 == state) {// 如果没有线程拿到资源的锁             if (compareAndSetState(0, arg)) {                 // 保存当前持有同步锁的线程                 setExclusiveOwnerThread(Thread.currentThread());                 return true;             }         } else if (Thread.currentThread() == getExclusiveOwnerThread()) {             // 如果当前线程再次进来,state + 1,可重入             // 如果这里没有这个判断,那么程序会卡死             setState(state + arg);             return true;         }         return false;     }      /**      * 锁的获取和释放需要一一对应      */     @Override     protected boolean tryRelease(int arg) {         // 获取资源状态         int state = getState();         // 返回最后一个通过setExclusiveOwnerThread()方法设置过的线程,或者null         if (Thread.currentThread() != getExclusiveOwnerThread()) {             throw new RuntimeException();         }         setState(state - arg);         if (0 == state) {             setExclusiveOwnerThread(null);             return true;         }         return false;     }      protected Condition newCondition() {         return new ConditionObject();     } }

 

 

然后再用AQS实现lock接口的方法:

/**  * 利用AQS实现自定义独占锁  *  * @author 湘王  */ public class MyExclusiveLock implements Lock {     private final SyncHelper synchepler = new SyncHelper();      @Override     public void lock() {         synchepler.acquire(1);     }      @Override     public void lockInterruptibly() throws InterruptedException {         synchepler.acquireInterruptibly(1);     }      @Override     public boolean tryLock() {         return synchepler.tryAcquire(1);     }      @Override     public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {         return synchepler.tryAcquireNanos(1, unit.toNanos(time));     }      @Override     public void unlock() {         synchepler.release(1);     }      @Override     public Condition newCondition() {         return synchepler.newCondition();     }      /**      * 内部类继承AQS的方式,实现排他锁      */     private static class SyncHelper extends AbstractQueuedSynchronizer {         private static final long serialVersionUID = -7666580981453962426L;              /**          * 第一个线程进来,拿到锁就返回true;后面的线程进来,拿不到锁就返回false          */         @Override         protected boolean tryAcquire(int arg) {             // 获取资源状态             int state = getState();             if (0 == state) {// 如果没有线程拿到资源的锁                 if (compareAndSetState(0, arg)) {                     // 保存当前持有同步锁的线程                     setExclusiveOwnerThread(Thread.currentThread());                     return true;                 }             } else if (Thread.currentThread() == getExclusiveOwnerThread()) {                 // 如果当前线程再次进来,state + 1,可重入                 // 如果这里没有这个判断,那么程序会卡死                 setState(state + arg);                 return true;             }             return false;         }              /**          * 锁的获取和释放需要一一对应          */         @Override         protected boolean tryRelease(int arg) {             // 获取资源状态             int state = getState();             // 返回最后一个通过setExclusiveOwnerThread()方法设置过的线程,或者null             if (Thread.currentThread() != getExclusiveOwnerThread()) {                 throw new RuntimeException();             }             setState(state - arg);             if (0 == state) {                 setExclusiveOwnerThread(null);                 return true;             }             return false;         }              protected Condition newCondition() {             return new ConditionObject();         }     } }

 

 

然后再运行测试:

/**  * 实现Lock接口方法并运行排他锁测试  *  * @author 湘王  */ public class MyExclusiveLockTester {     // 用自定义AQS独占锁实现     private Lock lock = new MyExclusiveLock();     private int value;      public int accmulator() {         lock.lock();         try {             ++value;         } finally {             lock.unlock();         }          return value;     }      public static void main(String[] args) throws InterruptedException {         MyExclusiveLockTester test = new MyExclusiveLockTester();         for (int i = 0; i < 5; i++) {             new Thread(new Runnable() {                 @Override                 public void run() {                     for (int i = 0; i < 5; i++) {                         System.out.println(test.accmulator());                     }                 }             }).start();         }     } }

 

 

可以看到,结果无论怎么样都不会再重复了。

 

这个只是简单的累加,接下来用AQS来实现一个实际的生活场景。比如周末带女票或男票去步行街吃饭,这时候人特别多,需要摇号,而且一次只能进去三张号(不按人头算,按叫到的号来算),该怎么实现呢?

可以顺着这个思路:摇号机虽有很多号,但它本质上是个共享资源,很多人可以共享,但是每次共享的数量有限。这其实就是个可以指定数量的共享锁而已。

既然有了思路,那接下来就好办了。

/**  * 利用AQS实现自定义共享锁  *  * @author 湘王  */ public class MyShareLock implements Lock {     @Override     public void lock() {     }      @Override     public void lockInterruptibly() throws InterruptedException {     }      @Override     public boolean tryLock() {         return false;     }      @Override     public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {         return false;     }      @Override     public void unlock() {     }      @Override     public Condition newCondition() {         return null;     } }

 

 

还是一样实现Lock接口,但这次是用AQS实现共享锁。

/**  * 内部类继承AQS实现共享锁  *  */ private static class SyncHelper extends AbstractQueuedSynchronizer {     private static final long serialVersionUID = -7357716912664213942L;      /**      * count表示允许几个线程能同时获得锁      */     public SyncHelper(int count) {         if (count <= 0) {             throw new IllegalArgumentException("锁资源数量必须大于0");         }         // 设置资源总数         setState(count);     }      /**      * 一次允许多少个线程进来,允许数量的线程都能拿到锁,其他的线程进入队列      */     @Override     protected int tryAcquireShared(int acquires) {         // 自旋         for (;;) {             int state = getState();             int remain = state - acquires;             // 判断剩余锁资源是否已小于0或者CAS执行是否成功             if (remain < 0 || compareAndSetState(state, remain)) {                 return remain;             }         }     }      /**      * 锁资源的获取和释放要一一对应      */     @Override     protected boolean tryReleaseShared(int releases) {         // 自旋         for (;;) {             // 获取当前state             int current = getState();             // 释放状态state增加releases             int next = current + releases;             if (next < current) {// 溢出                 throw new Error("Maximum permit count exceeded");             }             // 通过CAS更新state的值             // 这里不能用setState()             if (compareAndSetState(current, next)) {                 return true;             }         }     }      protected Condition newCondition() {         return new ConditionObject();     } }

 

 

然后再来改造之前实现的接口:

/**  * 利用AQS实现自定义共享锁  *  * @author 湘王  */ public class MyShareLock implements Lock {     public static int count;     private final SyncHelper synchepler = new SyncHelper(count);      @Override     public void lock() {         synchepler.acquireShared(1);     }      @Override     public void lockInterruptibly() throws InterruptedException {         synchepler.acquireSharedInterruptibly(1);     }      @Override     public boolean tryLock() {         return synchepler.tryAcquireShared(1) > 0;     }      @Override     public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {         return synchepler.tryAcquireSharedNanos(1, unit.toNanos(time));     }      @Override     public void unlock() {         synchepler.releaseShared(1);     }      @Override     public Condition newCondition() {         return synchepler.newCondition();     }      /**      * 内部类继承AQS实现共享锁      *      */     private static class SyncHelper extends AbstractQueuedSynchronizer {         private static final long serialVersionUID = -7357716912664213942L;          /**          * count表示允许几个线程能同时获得锁          */         public SyncHelper(int count) {             if (count <= 0) {                 throw new IllegalArgumentException("锁资源数量必须大于0");             }             // 设置资源总数             setState(count);         }          /**          * 一次允许多少个线程进来,允许数量的线程都能拿到锁,其他的线程进入队列          */         @Override         protected int tryAcquireShared(int acquires) {             // 自旋             for (;;) {                 int state = getState();                 int remain = state - acquires;                 // 判断剩余锁资源是否已小于0或者CAS执行是否成功                 if (remain < 0 || compareAndSetState(state, remain)) {                     return remain;                 }             }         }          /**          * 锁资源的获取和释放要一一对应          */         @Override         protected boolean tryReleaseShared(int releases) {             // 自旋             for (;;) {                 // 获取当前state                 int current = getState();                 // 释放状态state增加releases                 int next = current + releases;                 if (next < current) {// 溢出                     throw new Error("Maximum permit count exceeded");                 }                 // 通过CAS更新state的值                 // 这里不能用setState()                 if (compareAndSetState(current, next)) {                     return true;                 }             }         }          protected Condition newCondition() {             return new ConditionObject();         }     } }

 

 

接下来就该测试咱们需要的效果是否能实现了:

public class MyShareLockTester {     public static void main(String[] args) throws InterruptedException {         // 用自定义AQS共享锁实现         // 一次允许发放三把锁         MyShareLock.count = 3;         final Lock lock = new MyShareLock();          // 模拟20个客户端访问         for (int i = 0; i < 20; i++) {             new Thread(new Runnable() {                 @Override                 public void run() {                     try {                         lock.lock();                         System.out.println("持有 " + Thread.currentThread().getName() + " 的客人可以进餐厅就餐");                         // 每两次叫号之间间隔一段时间,模拟真实场景                         Thread.sleep(3000);                     } catch (InterruptedException e) {                         e.printStackTrace();                     } finally {                         // 使用完成释放锁                         lock.unlock();                     }                 }             }).start();         }     } }

 

这里有20个号,每次只能发放3张,运行之后就可以看到确实如此。

AQS是个很神奇也很好玩的东西,就像它的作者(也是除了高司令就是对Java影响最大的那个人,整个Java的多线程juc包代码就是他编写的Doug LeaAbstractQueuedSynchronizer的注释中所说:AQS只是一个框架,至于怎么玩,就是你的事了!

 

 


 

 

感谢您的大驾光临!咨询技术、产品、运营和管理相关问题,请关注后留言。欢迎骚扰,不胜荣幸~

 

发表评论

评论已关闭。

相关文章