5. Java中的锁

本文将介绍Java并发包中与锁相关的API和组件, 以及这些API和组件的使用方式和实现细节

1. Lock接口

锁是用来控制多个线程访问共享资源的方式, 像独占锁, 读写锁

在Lock接口出现之前, java程序考synchronized关键字实现锁功能, 在JDK1.5之后, 并发包中新增了Lock接口, 用来实现锁功能

Lock使用方式, 有两个注意点:

  1. 在finally中释放锁, 目的是保证在获取到锁之后, 最终能够被释放
  2. 在try语句之前获取锁, 因为获取锁的过程中可能会产生异常, 而获取不到锁, 获取不到锁, 再去释放锁, 就会抛出IllegalMonitorStateException异常
public class LockExample {
    public static void main(String[] args) {
        // 实例化锁
        Lock lock = new ReentrantLock();
        // 获取锁
        lock.lock();
        try {
            // doSomething...
        } finally {
            // 释放锁
            lock.unlock();
        }
    }
}
1.1 Lock接口和synchronized关键字对比, 优势
  1. 尝试非阻塞的获取锁, 当线程尝试获取锁, 如果这一时刻锁没有被其他线程获取到, 则成功获取并持有锁
  2. 能够被中断的获取锁, 获取到锁的线程能够响应中断, 当获取到锁的线程被中断时, 中断异常将会被抛出, 同时锁会被释放
  3. 超时获取锁, 在指定的截止时间内获取锁, 如果超时之后仍旧无法获取锁, 那么立即返回
1.2 Lock接口API
  1. void lock(), 获取锁, 调用该方法时, 当前线程将会获取锁, 当锁获取之后, 从该方法返回
  2. void lockInterruptibly(), 可中断的获取锁, 和lock()方法的不同之处在于该方法会相应中断, 即在锁的获取过程中, 可以终端当前线程
  3. boolean tryLock(), 尝试非阻塞的获取锁, 调用该方法后立即返回
    • 如果能获取到锁, 返回true
    • 如果获取不到锁, 返回false
  4. void unlock(), 释放锁
  5. Condition newCondition(), 获取等待通知组件, 该组件和当前的锁绑定, 当前线程获取锁之后, 才能调用Condition组件的方法, Condition接口与Lock接口配合, 实现了线程等待/通知机制, 实现了Object类的wait和notify的功能
    • Condition#await()作用: 当前线程将释放锁, 当其他线程调用Condition#signal或signalAll方法, 当前线程获取到锁之后, 才会从await()方法返回, 作用类似于Object#wait(), 如果当前线程等待过程中被中断, 那么抛出被中断异常
    • Condition#signal()作用, 唤醒一个等待Condition的线程, 将该线程从等待队列移动到同步队列中, 被唤醒队列参与到Lock的竞争中, 如果获取到Lock, 会从等待方法中返回

2. 队列同步器AQS

队列同步器AbstractQueuedSynchronizer,用来构建锁或者其他同步其他组件的基础框架,它使用int类型的成员变量state表示同步状态,通过内置的FIFO队列来完成线程排队工作

同步器是实现锁的关键,利用同步器实现锁的语义

  1. 锁面向使用者, 定义了使用者与锁交互的接口, 隐藏了锁的实现细节
  2. 同步器面试锁的实现者, 简化了锁的实现方式, 屏蔽了同步状态管理, 线程排队, 等待与唤醒等底层操作
  3. 锁和同步器很好的隔离了使用者和实现者所需关注的领域
2.1 同步器DEMO

独占锁MutexLock是一个自定义同步组件, 再同一时刻只有一个线程站有锁

因为是独占锁, 所以Sync不需要实现acquireShared和releaseShared等方法

//独占锁
public class MutexLock implements Lock {
    //同步器
    private Sync sync = new Sync();

    //独占上锁, 不响应中断
    //先尝试获取锁
    //失败的话新建一个Node节点, 将当前线程加入到同步队列尾部
    //再尝试CAS获取锁
    //获取不到锁的话, 进入等待状态, 等待被唤醒
    //唤醒之后继续获取锁
    @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);
    }

    //Condition类, 多线程间协调通信的工具类
    //Condition#await()方法相当于Object#wait()方法
    //Confition#signal()方法相当于Object#notify()方法
    @Override
    public Condition newCondition() {
        return sync.newCondition();
    }

    //同步器
    private class Sync extends AbstractQueuedSynchronizer {
        //尝试获取锁
        @Override
        protected boolean tryAcquire(int arg) {
            if (compareAndSetState(0, 1)) {
                //CAS将state字段设置为1
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        //尝试释放锁
        @Override
        protected boolean tryRelease(int arg) {
            if (getState() == 0) {
                throw new IllegalMonitorStateException();
            }
            //将state设为0
            setState(0);
            return true;
        }

        @Override
        protected boolean isHeldExclusively() {
            //独占锁
            return true;
        }

        Condition newCondition() {
            return new ConditionObject();
        }
    }
}
2.2 同步器实现原理

同步器主要由同步队列, 独占状态的获取与释放, 共享状态的获取与释放等模板方法构成

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

推荐阅读更多精彩内容

  • 1.Lock接口 一般来说,一个锁能够防止多个线程同时访问共享资源(但有些锁可以允许多个线程并发的访问共享资源,比...
    加夕阅读 457评论 0 1
  • Lock接口 需要显式的获取和释放锁,支持非阻塞的获取锁,支持中断的获取锁,支持超时获取锁; Synchronze...
    星冉子阅读 130评论 0 0
  • 5.1 LOCK接口 锁是用于控制多个线程访问共享资源的方式。一般来说,一个锁能够防止多个线程同一时间访问一个共享...
    伊凡的一天阅读 389评论 0 1
  • layout: posttitle: 《Java并发编程的艺术》笔记categories: Javaexcerpt...
    xiaogmail阅读 5,815评论 1 19
  • 5.4 读写锁 之前提到的锁如Mutex和ReentrantLock都是排它锁,这些锁同一时刻都只允许一个线程进行...
    伊凡的一天阅读 543评论 0 1