关于ReentrantLock、Condition和ReadWriteLock的学习笔记

ReentrantLock、Condition及ReadWriteLock

先看一下ReentrantLock和Condition

  • 以一个简单的ReentrantLock和Condition的例子为入口

    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class ConditionDemo {
    
        public static void main(String[] args) {
            final ReentrantLock lock = new ReentrantLock();
            final Condition condition = lock.newCondition();
    
            Thread thread1 = new Thread(new Runnable() {
                public void run() {
                    try {
                        lock.lock();
                        // Thread1首先获得锁,然后调用condition.await()等待信号
                        System.out.println("Thread1 get lock, then wait for new signal");
                        condition.await();
                        // 当Thread2中调用condition.signal()/signalAll()之后,Thread1被唤醒继续执行
                        System.out.println("Thread1 got new signal and continue...");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        lock.unlock();
                        System.out.println("Thread1 unlock");
                    }
                }
            });
            thread1.start();
    
            Thread thread2 = new Thread(new Runnable() {
                public void run() {
                    lock.lock();
                    // Thread2获得锁
                    System.out.println("Thread2 get lock, then send a signal.");
                    // 调用signalAll()通知所有等待状态的线程
                    condition.signalAll();// 此时Thread1被唤醒,并开始争夺锁,但是由于thread2还没有释放锁,因此无法继续运行,thread1继续阻塞
                    
                    System.out.println("Thread2 sent a signal and continue");
                    try {
                        Thread.sleep(1000);// 等待1秒钟后再释放锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    lock.unlock();
                    // Thread2释放锁之后Thread1重新获得锁继续运行
                    System.out.println("Thread2 unlock");
                }
            });
            thread2.start();
        }
    }
    
  • 执行结果

    Thread1 get lock, then wait for new signal
    Thread2 get lock, then send a signal.
    Thread2 sent a signal and continue
    Thread2 unlock
    Thread1 got new signal and continue...
    Thread1 unlock
    
  • 从上面的结果可以看出,thread1执行之后首先获得了锁,但是随后执行了condition.await()之后又释放了锁进入等待状态。然后thread2执行之后获得锁,再发送signal给thread1,但此时还没有释放锁,所以thread1依旧阻塞直到一秒后thread2释放锁thread1才能继续运行。

  • 因此Condition非常适合线程间的通信,下面以消费者生产者的例子来说明展示Condition的强大

    地主家的傻儿子和慢慢收租的地主

  • 银行卡

    public class BankCard {
        private Long balance = 1000000L;
    
        public BankCard(Long balance) {
            this.balance = balance;
        }
    
        public BankCard() {
    
        }
    
        public Long getBalance() {
            return balance;
        }
    
        public void setBalance(Long balance) {
            this.balance = balance;
        }
    }
    
  • 傻儿子

    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    
    public class StupidSon implements Runnable {
    
        private BankCard bankCard;
        private int id;
        private Lock lock;
        private Condition condition;
    
        public StupidSon(BankCard bankCard, int id, Lock lock, Condition condition) {
            this.bankCard = bankCard;
            this.id = id;
            this.lock = lock;
            this.condition = condition;
        }
    
        public void run() {
            try {
                while (true) {
                    lock.lock();
                    // 傻儿子每次消费1000,当余额不足时等老父亲赚钱存到银行卡
                    while (bankCard.getBalance() - 1000 <= 0) {
                        System.out.println("StupidSon" + id + ", $" + bankCard.getBalance() + " no more money, wait for father's signal..");
                        condition.await();
                    }
                    bankCard.setBalance(bankCard.getBalance() - 1000);
                    System.out.println("StupidSon" + id + " cost $1000, remains:" + bankCard.getBalance());
                    lock.unlock();
                    // 一秒钟消费一次
                    Thread.sleep(1000L);
                }
            } catch (Exception e) {
                e.printStackTrace();
                lock.unlock();
            }
        }
    }
    
  • 老父亲

    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    
    public class HardFather implements Runnable {
    
        private BankCard bankCard;
        private Lock lock;
        private Condition condition;
    
        public HardFather(BankCard bankCard, Lock lock, Condition condition) {
            this.bankCard = bankCard;
            this.lock = lock;
            this.condition = condition;
        }
    
        public void run() {
    
            while (true) {
                lock.lock();
                bankCard.setBalance(bankCard.getBalance() + 500);
                System.out.println("Father earn $500, remains:" + bankCard.getBalance());
                if (bankCard.getBalance() > 1000 && bankCard.getBalance() < 2000) {
                    // 当金额超过1000之后告诉傻儿子们可以来拿钱了
                    condition.signalAll();
                    System.out.println("Money above 1000 again, sent signal.");
                }
                lock.unlock();
                try {
                    // 三秒存一次钱
                    Thread.sleep(3000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
    
        }
    }
    
  • 测试实例

    public class Main {
        public static void main(String[] args) {
              // 初始化金额为1万
            BankCard bankCard = new BankCard(10000L);
            Lock lock = new ReentrantLock();
            Condition condition = lock.newCondition();
              // 这里创建两个儿子
            StupidSon son1 = new StupidSon(bankCard, 1, lock, condition);
            StupidSon son2 = new StupidSon(bankCard, 2, lock, condition);
              // 一个父亲,公用一个lock和同一个condition
            HardFather father = new HardFather(bankCard, lock, condition);
              // 放入线程池执行
            ExecutorService pool = Executors.newCachedThreadPool();
            pool.submit(father);
            pool.submit(son1);
            pool.submit(son2);
        }
    }
    
    
  • 执行结果

    Father earn $500, remains:10500
    StupidSon1 cost $1000, remains:9500
    StupidSon2 cost $1000, remains:8500
    StupidSon1 cost $1000, remains:7500
    StupidSon2 cost $1000, remains:6500
    StupidSon1 cost $1000, remains:5500
    StupidSon2 cost $1000, remains:4500
    Father earn $500, remains:5000
    StupidSon1 cost $1000, remains:4000
    StupidSon2 cost $1000, remains:3000
    StupidSon1 cost $1000, remains:2000
    StupidSon2 cost $1000, remains:1000
    StupidSon1, $1000 no more money, wait for father's signal..
    StupidSon2, $1000 no more money, wait for father's signal..
    Father earn $500, remains:1500
    Money above 1000 again, sent signal.
    StupidSon1 cost $1000, remains:500
    StupidSon2, $500 no more money, wait for father's signal..
    StupidSon1, $500 no more money, wait for father's signal..
    Father earn $500, remains:1000
    Father earn $500, remains:1500
    Money above 1000 again, sent signal.
    StupidSon2 cost $1000, remains:500
    StupidSon1, $500 no more money, wait for father's signal..
    StupidSon2, $500 no more money, wait for father's signal..
    Father earn $500, remains:1000
    Father earn $500, remains:1500
    Money above 1000 again, sent signal.
    StupidSon1 cost $1000, remains:500
    StupidSon2, $500 no more money, wait for father's signal..
    
  • 从结果可以看出,当金额少于1000之后两个儿子都在等待父亲的信号,然后当他们的父亲存钱达到1500之后发送信号,这两个儿子便开始争夺锁然后消耗金额继续等待。这其实就是最基本的一个生产者消费者问题,使用ReentrantLock和Conditon之后处理生产者消费者问题就变得非常简单。

下面来看一下读写锁ReadWriteLock

  • 银行卡使用上例中的就不贴代码了

  • 傻儿子如下

    
    import java.util.concurrent.locks.ReadWriteLock;
    
    public class StupidSon implements Runnable {
    
        private ReadWriteLock lock;
        private int id;
        private BankCard bankCard;
    
        public StupidSon(ReadWriteLock lock, BankCard bankCard, int id) {
            this.lock = lock;
            this.bankCard = bankCard;
            this.id = id;
        }
    
        public void run() {
            int cost = 1000;
            while (true) {
                lock.writeLock().lock();
                // 获取写锁,读锁和写锁是互斥、写锁和写锁也是互斥的,此时其他线程读、写锁都将无法获取只能等待
                if (bankCard.getBalance() - cost < 0) {
                    System.out.println("StupidSon" + id + " no more money");
                    lock.writeLock().unlock();
                    // 判断金额不足之后释放写锁,跳出while循环结束线程
                    break;
                }
                bankCard.setBalance(bankCard.getBalance() - cost);
                System.out.println("StupidSon" + id + " cost $" + cost + ", remain:" + bankCard.getBalance());
                try {
                    // 等待500毫秒之后再释放写锁
                    System.out.print("StupidSon" + id + " wait for 500ms...");
                    Thread.sleep(500);
                    // 修改金额之后释放写锁,此时其他线程可以继续竞争获取写锁和读锁
                    lock.writeLock().unlock();
                    System.out.println("StupidSon" + id + "release writeLock.");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("StupidSon" + id + " exit.");
        }
    }
    
  • 父母类 起到监督金额的作用

    
    import java.util.concurrent.locks.ReadWriteLock;
    
    
    public class Parents implements Runnable {
    
        private ReadWriteLock lock;
        private BankCard bankCard;
        private int type;
    
        public Parents(ReadWriteLock lock, BankCard bankCard, int type) {
            this.lock = lock;
            this.bankCard = bankCard;
            this.type = type;
        }
    
        public void run() {
            String mof = type == 1 ? "Mother" : "Father";
            while (true) {
                lock.readLock().lock();
                // 获取读锁, 读锁和读锁是不互斥的 也就是说多个线程可以同时获取读锁,但是读写锁是互斥的,此时写锁无法被获得只能等待
                if (bankCard.getBalance() <= 0) {
                    System.out.println(mof + " saw no more money. stop check!");
                    lock.readLock().unlock();// 这里一定要释放读锁,否则会导致死锁
                    break;
                }
                System.out.println(mof + " check balance:" + bankCard.getBalance());
                try {
                    // 等待500毫秒之后再释放读锁
                    System.out.print(mof + " wait for 500ms...");
                    Thread.sleep(500);
                    System.out.println(mof + " release readLock.");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 读取结束后释放读锁,写锁可以被其他线程竞争获取到
                lock.readLock().unlock();
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(mof+" exit");
        }
    }
    
  • 测试

    
    public class Main {
        public static void main(String[] args) {
            ReadWriteLock lock = new ReentrantReadWriteLock();
            BankCard bankCard = new BankCard(10000L);
              // 初始化四个儿子和两老
            Runnable son = new StupidSon(lock, bankCard, 1);
            Runnable son2 = new StupidSon(lock, bankCard, 2);
            Runnable son3 = new StupidSon(lock, bankCard, 3);
            Runnable son4 = new StupidSon(lock, bankCard, 4);
            Runnable mother = new Parents(lock, bankCard, 1);
            Runnable father = new Parents(lock, bankCard, 2);
            ExecutorService threadPoolExecutor = Executors.newCachedThreadPool();
    
            threadPoolExecutor.submit(son3);
            threadPoolExecutor.submit(son4);
            threadPoolExecutor.submit(son2);
            threadPoolExecutor.submit(mother);
            threadPoolExecutor.submit(father);
            threadPoolExecutor.submit(son);
        }
    }
    
  • 运行结果

    StupidSon4 cost $1000, remain:9000
    StupidSon4 wait for 500ms...StupidSon4release writeLock.
    StupidSon2 cost $1000, remain:8000
    StupidSon2 wait for 500ms...StupidSon2release writeLock.
    StupidSon3 cost $1000, remain:7000
    StupidSon3 wait for 500ms...StupidSon3release writeLock.
    Mother check balance:7000
    // 从这里可以看出读锁不互斥,因为母亲未释放读锁的时候父亲依旧可以获取读锁
    Mother wait for 500ms...Father check balance:7000
    Father wait for 500ms...Father release readLock.
    Mother release readLock.
    StupidSon1 cost $1000, remain:6000
    StupidSon1 wait for 500ms...StupidSon1release writeLock.
    StupidSon4 cost $1000, remain:5000
    StupidSon4 wait for 500ms...StupidSon4release writeLock.
    StupidSon2 cost $1000, remain:4000
    StupidSon2 wait for 500ms...StupidSon2release writeLock.
    StupidSon3 cost $1000, remain:3000
    StupidSon3 wait for 500ms...StupidSon3release writeLock.
    StupidSon4 cost $1000, remain:2000
    StupidSon4 wait for 500ms...StupidSon4release writeLock.
    StupidSon2 cost $1000, remain:1000
    StupidSon2 wait for 500ms...StupidSon2release writeLock.
    Mother check balance:1000
    Mother wait for 500ms...Father check balance:1000
    Father wait for 500ms...Father release readLock.
    Mother release readLock.
    StupidSon1 cost $1000, remain:0
    StupidSon1 wait for 500ms...StupidSon1release writeLock.
    StupidSon3 no more money
    StupidSon3 exit.
    StupidSon4 no more money
    StupidSon4 exit.
    StupidSon2 no more money
    StupidSon2 exit.
    Father saw no more money. stop check!
    Father exit
    Mother saw no more money. stop check!
    Mother exit
    StupidSon1 no more money
    StupidSon1 exit.
    
  • 由上面的结果可以很直观的了解到读写锁互斥、写锁也写锁之间互斥、读锁和读锁之间不互斥。

  • 读写锁的使用时一定要注意的是要及时释放锁,否则容易导致死锁的发生,最保险的方式是将unlock代码放在finally代码块中去

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

推荐阅读更多精彩内容