JAVA并发-基于AQS实现自己的显示锁

原文链接:http://www.studyshare.cn/blog/details/1131/1

一、了解什么是AQS

AQS是AbstractQueuedSynchronizer (抽象队列同步器)的简称java中近一半的显示锁是基于AQS实现的。例如:ReentrantLock(独占锁)、Semaphore(信号量)、ReentrantReadWriteLock(读写锁)、CountDownLatch(并发工具类)、ThreadPoolExecutor(线程池)

 AQS原理:


1.采用双向链表的数据结构,当多线程同时竞争锁的时候,第一个线程拿到锁后,后续的线程封装成Node节点依次进入同步队列进行排队等待。

2.AQS内部会采取自旋(死循环)的机制,一直判断头节点是否满足获取锁的条件,当锁被第一个线程释放后,队列中头节点条件满足(检查锁的状态是否为0),然后让头节点获取到锁,并脱离队列,如下图:

AQS原理其实并不复杂,就是采用一个同步队列来存储节点(对线程进行包装,node中有thread,prev,next等),并自旋判断头节点是否拿到锁,拿到锁就从队列中移除,如果有新的线程进入则加到队列的尾部。

更多深度技术文章,在这里。 java开发工具下载地址及安装教程大全,点这里

二、AQS采用的设计模式及主要方法

(1)AQS采用的是模板方法模式,对模板方法模式不清楚请参考:https://www.cnblogs.com/stonefeng/p/5743673.html

(2)模板方法

    独占式获取

         acquire()

         acquireInterrutpibly()

         tryAcquireNanos()


         共享式获取

         acquireShared()

         acquireSharedInterruptibly()

         tryAcquireSharedNanos()


         独占式释放

         release()


         共享式释放

         releaseShared()


         需要覆盖的流程方法

         独占式获取

         tryAcquire()


         共享式获取

         tryAcquireShared()


         独占式释放

         tryRelease()


         共享式释放

         tryReleaseShared()

         isHeldExclusively() :该方法返回同步状态,需要自己覆盖实现

  以上方法是AQS的几个核心方法,在下面我们分析源码的时候会具体解释


三、实现一个案例:继承AQS实现一个自己的独占锁

//实现Lock接口

public class MyReentrantLock implements Lock {

        //使用state做锁的状态标志,state=1表示获取到锁,state=0表示释放锁,其他线程可以竞争锁

        private static class Sync extends AbstractQueuedSynchronizer{

        /**

        * 尝试获取锁的方法

        * 需要自己实现的流程方法

        * @param arg

        * @return

        */

        @Override

        protected boolean tryAcquire(int arg) {

                //CAS比较内存中的原始值为0,则修改为传入的状态值1,当前线程获取到锁

                if(compareAndSetState(0 , arg)){

                        setExclusiveOwnerThread(Thread.currentThread());//当前线程得到了锁,则将当前得到锁的线程设置为独占线程

                        return true;

                }

                return false;

        }

        /**

        * 释放锁的方法,需要实现

        * @param arg

        * @return

        */

        @Override

        protected boolean tryRelease(int arg) {

            if(getState() == 0){//判断状态是否为0,为0则直接抛出不支持操作的异常,增强健壮性的代码

                    throw new UnsupportedOperationException();

            }

            setExclusiveOwnerThread(null);//将当前独占线程设置为null

            setState(0);//将当前标志锁状态的值设置为0,表示锁已经释放

            return true;

        }

            /**

            * 是否同步独占,true--已被独占,false--未被独占

            * @return

            */

            @Override

            protected boolean isHeldExclusively() {

                return getState() == 1 ;

            }

            Condition newCondition(){

                    return new ConditionObject();//AQS已经实现Condition,此处只需要直接实例化并使用AQS中的实现即可

            }

         }

        private Sync sync = new Sync();

        @Override

        public void lock() {

                sync.acquire(1); //获取锁

        }

        @Override

        public void lockInterruptibly() throws InterruptedException {

                sync.acquireInterruptibly(1);//获取锁,允许获取过程中有中断

        }

        @Override

        public boolean tryLock() {

                return sync.tryAcquire(1);

        }

        @Override

        public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {

                return sync.tryAcquireNanos(1,unit.toNanos(time));//获取锁,有超时机制

        }

        @Override

        public void unlock() {

                sync.release(1) ;//释放锁

        }

        @Override

        public Condition newCondition() {

                return sync.newCondition();//获取AQS中的Condition实例,用于等待、唤醒操作

        }

}

1、显示锁的实现机制是实现Lock接口,使用内部类去继承AQS,为何这样做其实就是java是单继承的,AQS是抽象类,Lock是接口,使用接口更具有扩展性。

2、需要自己覆盖的流程方法

tryAcquire():获取锁

tryRelease():释放锁

isHeldExclusively():是否同步独占,true--已被独占,false--未被独占(根据state状态值判断)

测试类:

public class LockTest {

        static MyReentrantLock lock = new MyReentrantLock();

        public static void main(String[] args) {

        for(int i=0;i<5;i++){

                Thread threadA = new Thread(new Runnable() {

                @Override

                public void run() {

                lock.lock();

                try{

                        System.out.println("获取到锁处理业务逻辑");

                        Thread.sleep(1000) ;

                } catch (InterruptedException e) {

                        e.printStackTrace();

                } finally{

                        lock.unlock();

                        System.out.println("释放锁");

                }

            }

        });

        threadA.start();

        }

    }

}

四、深入源码,解读AQS原理

1、多线程并发获取锁,lock.lock(),该方法调用后会执行 sync.acquire(1); 进入这个方法的源码

  tryAcquire()就是我们自己覆盖实现的方法,尝试去获取锁,得到则返回true,没得到则返回false,如果返回true,则acquire()则直接执行完毕,不会继续往下执行,如果返回false,说明没有得到锁,则需要将当前线程加入等待队列中。addWaiter()就是加入等待队列的方法,进入源码:

  接着看看enq()方法的源码:

  以上就成功将一个需要等待的线程封装成节点类后加入等待队列了。如果有更多的线程需要加入,则enq(队列还没有任何节点)方法不需要执行,在addWaiter()中代码实现了直接往尾节点后面继续加入。这里是入队列的代码实现,接着看看出队列的具体实现

  该方法实现出队列,看源码:

  以上方法就走完了获取锁的流程,执行到这一步,在头节点的线程其实是处于阻塞状态的,它需要等待当前持有锁的那个线程去唤醒它,才会继续执行自己的业务代码

  接着看看释放锁的源码实现:

总结:到这里其实整个AQS的流程就已经走完了,AQS是使用双向链表数据机构实现一个入队和出队的操作(先进先出),新增节点挂在尾节点的后面,自旋判断当前节点的前驱节点是否是头节点,如果是头节点并尝试获取锁,获取成功就将头节点和后面的节点脱钩,并处于阻塞状态,等待释放锁的方法去唤醒。

  以上代码只讲解了AQS实现了一个基本的显示锁,如果要实现可重入锁或者读写锁,需要加入其它控制和实现其它方法,在此就不讨论,有兴趣可以自己去看可重入锁和读写锁的实现。

更多深度技术文章,在这里。 java开发工具下载地址及安装教程大全,点这里

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,640评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,254评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,011评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,755评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,774评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,610评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,352评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,257评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,717评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,894评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,021评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,735评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,354评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,936评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,054评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,224评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,974评论 2 355