Java并发编程实战
王宝令
资深架构师
立即订阅
15151 人已学习
课程目录
已完结 50 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 你为什么需要学习并发编程?
免费
学习攻略 (1讲)
学习攻略 | 如何才能学好并发编程?
第一部分:并发理论基础 (13讲)
01 | 可见性、原子性和有序性问题:并发编程Bug的源头
02 | Java内存模型:看Java如何解决可见性和有序性问题
03 | 互斥锁(上):解决原子性问题
04 | 互斥锁(下):如何用一把锁保护多个资源?
05 | 一不小心就死锁了,怎么办?
06 | 用“等待-通知”机制优化循环等待
07 | 安全性、活跃性以及性能问题
08 | 管程:并发编程的万能钥匙
09 | Java线程(上):Java线程的生命周期
10 | Java线程(中):创建多少线程才是合适的?
11 | Java线程(下):为什么局部变量是线程安全的?
12 | 如何用面向对象思想写好并发程序?
13 | 理论基础模块热点问题答疑
第二部分:并发工具类 (14讲)
14 | Lock和Condition(上):隐藏在并发包中的管程
15 | Lock和Condition(下):Dubbo如何用管程实现异步转同步?
16 | Semaphore:如何快速实现一个限流器?
17 | ReadWriteLock:如何快速实现一个完备的缓存?
18 | StampedLock:有没有比读写锁更快的锁?
19 | CountDownLatch和CyclicBarrier:如何让多线程步调一致?
20 | 并发容器:都有哪些“坑”需要我们填?
21 | 原子类:无锁工具类的典范
22 | Executor与线程池:如何创建正确的线程池?
23 | Future:如何用多线程实现最优的“烧水泡茶”程序?
24 | CompletableFuture:异步编程没那么难
25 | CompletionService:如何批量执行异步任务?
26 | Fork/Join:单机版的MapReduce
27 | 并发工具类模块热点问题答疑
第三部分:并发设计模式 (10讲)
28 | Immutability模式:如何利用不变性解决并发问题?
29 | Copy-on-Write模式:不是延时策略的COW
30 | 线程本地存储模式:没有共享,就没有伤害
31 | Guarded Suspension模式:等待唤醒机制的规范实现
32 | Balking模式:再谈线程安全的单例模式
33 | Thread-Per-Message模式:最简单实用的分工方法
34 | Worker Thread模式:如何避免重复创建线程?
35 | 两阶段终止模式:如何优雅地终止线程?
36 | 生产者-消费者模式:用流水线思想提高效率
37 | 设计模式模块热点问题答疑
第四部分:案例分析 (4讲)
38 | 案例分析(一):高性能限流器Guava RateLimiter
39 | 案例分析(二):高性能网络应用框架Netty
40 | 案例分析(三):高性能队列Disruptor
41 | 案例分析(四):高性能数据库连接池HiKariCP
第五部分:其他并发模型 (4讲)
42 | Actor模型:面向对象原生的并发模型
43 | 软件事务内存:借鉴数据库的并发经验
44 | 协程:更轻量级的线程
45 | CSP模型:Golang的主力队员
结束语 (1讲)
结束语 | 十年之后,初心依旧
用户故事 (2讲)
用户来信 | 真好,面试考到这些并发编程,我都答对了!
3 个用户来信 | 打开一个新的并发世界
Java并发编程实战
登录|注册

14 | Lock和Condition(上):隐藏在并发包中的管程

王宝令 2019-03-30
Java SDK 并发包内容很丰富,包罗万象,但是我觉得最核心的还是其对管程的实现。因为理论上利用管程,你几乎可以实现并发包里所有的工具类。在前面《08 | 管程:并发编程的万能钥匙》中我们提到过在并发编程领域,有两大核心问题:一个是互斥,即同一时刻只允许一个线程访问共享资源;另一个是同步,即线程之间如何通信、协作。这两大问题,管程都是能够解决的。Java SDK 并发包通过 Lock 和 Condition 两个接口来实现管程,其中 Lock 用于解决互斥问题,Condition 用于解决同步问题
今天我们重点介绍 Lock 的使用,在介绍 Lock 的使用之前,有个问题需要你首先思考一下:Java 语言本身提供的 synchronized 也是管程的一种实现,既然 Java 从语言层面已经实现了管程了,那为什么还要在 SDK 里提供另外一种实现呢?难道 Java 标准委员会还能同意“重复造轮子”的方案?很显然它们之间是有巨大区别的。那区别在哪里呢?如果能深入理解这个问题,对你用好 Lock 帮助很大。下面我们就一起来剖析一下这个问题。

再造管程的理由

你也许曾经听到过很多这方面的传说,例如在 Java 的 1.5 版本中,synchronized 性能不如 SDK 里面的 Lock,但 1.6 版本之后,synchronized 做了很多优化,将性能追了上来,所以 1.6 之后的版本又有人推荐使用 synchronized 了。那性能是否可以成为“重复造轮子”的理由呢?显然不能。因为性能问题优化一下就可以了,完全没必要“重复造轮子”。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Java并发编程实战》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(75)

  •  
    我觉得:不会出现死锁,但会出现活锁

    作者回复: 👍

    2019-03-30
    1
    36
  • xiyi
    存在活锁。这个例子可以稍微改下,成功转账后应该跳出循环。加个随机重试时间避免活锁

    作者回复: 👍👍👍

    2019-03-30
    28
  • bing
    文中说的公平锁和非公平锁,是不按照排队的顺序被唤醒,我记得非公平锁的场景应该是线程释放锁之后,如果来了一个线程获取锁,他不必去排队直接获取到,应该不会入队吧。获取不到才进吧

    作者回复: 是的,高手👍👍👍

    2019-03-30
    28
  • 小华
    有可能活锁,A,B两账户相互转账,各自持有自己lock的锁,都一直在尝试获取对方的锁,形成了活锁

    作者回复: 👍

    2019-03-30
    1
    20
  • 刘晓林
    1.这个是个死循环啊,有锁没群,都出不来。
    2.如果抛开死循环,也会造成活锁,状态不稳定。当然这个也看场景,假如冲突窗口很小,又在单机多核的话,活锁的可能性还是很小的,可以接受

    作者回复: 👍👍👍

    2019-03-30
    11
  • Q宝的宝
    老师,本文在讲述如何保证可见性时,分析示例--“线程 T1 对 value 进行了 +=1 操作后,后续的线程 T2 能否看到 value 的正确结果?“时,提到三条Happen-Before规则,这里在解释第2条和第3条规则时,似乎说反了,正确的应该是,根据volatile变量规则,线程T1的unlock()操作Happen-Before于线程T2的lock()操作,所以,根据传递性规则,线程 T1 的 value+=1操作Happen-Before于线程T2的lock()操作。请老师指正。

    作者回复: 火眼金睛👍👍👍👍,这就改过来

    2019-03-30
    1
    11
  • 姜戈
    我也觉得是存在活锁,而非死锁。存在这种可能性:互相持有各自的锁,发现需要的对方的锁都被对方持有,就会释放当前持有的锁,导致大家都在不停持锁,释放锁,但事情还没做。当然还是会存在转账成功的情景,不过效率低下。我觉得此时需要引入Condition,协调两者同步处理转账!

    作者回复: 用condition会更复杂

    2019-03-30
    8
  • linqw
    class Account {
      private int balance;
      private final Lock lock
              = new ReentrantLock();
      // 转账
      void transfer(Account tar, int amt){
    boolean flag = true;
        while (flag) {
          if(this.lock.tryLock(随机数,NANOSECONDS)) {
            try {
              if (tar.lock.tryLock(随机数,NANOSECONDS)) {
                try {
                  this.balance -= amt;
                  tar.balance += amt;
    flag = false;
                } finally {
                  tar.lock.unlock();
                }
              }//if
            } finally {
              this.lock.unlock();
            }
          }//if
        }//while
      }//transfer
    }
    感觉可以这样操作

    作者回复: 点个大大的赞!不过还可以再优化一下,如果阻塞在tar.lock.tryLock上一段时间,this.lock是不能释放的。

    2019-04-07
    3
    7
  • 羊三@XCoin.AI
    用非阻塞的方式去获取锁,破坏了第五章所说的产生死锁的四个条件之一的“不可抢占”。所以不会产生死锁。

    用锁的最佳实践,第三个“永远不在调用其他对象的方法时加锁”,我理解其实是在工程规范上避免可能出现的锁相关问题。

    作者回复: 是的

    2019-03-30
    6
  • 海鸿
    突然有个问题:
    cpu层面的原子性是单条cpu指令。
    java层面的互斥(管程)保证了原子性。
    这两个原子性意义应该不一样吧?
    我的理解是cpu的原子性是不受线程调度影响,指令要不执行了,要么没执行。而java层面的原子性是在锁的机制下保证只有一个线程执行,其余等待,此时cpu还是可以进行线程调度,使运行中的那个线程让出cpu时间,当然了该线程还是掌握锁。
    我这样理解对吧?

    作者回复: 对

    2019-03-30
    5
  • 朱小豪
    应该是少了个break跳出循环,然后这个例子是会产生死锁的,因为满足了死锁产生的条件。

    作者回复: 加了break,也会有活锁问题,不加的话我觉得也是活锁,因为锁都会释放

    2019-03-30
    5
  • 小予
    1、转账成功跳出循环,避免死循环
    2、锁自己账户时,加一个随机等待时间,避免活锁
    3、锁目标账户时,锁不到则直接解锁
    class Account {
    private int balance;
    private final Lock lock = new ReentrantLock();
        // 转账
        void transfer(Account tar, int amt){
    while (true) {
    if(this.lock.tryLock(随机数,NANOSECONDS)) {
    try {
    if (tar.lock.tryLock()) {
    try {
    this.balance -= amt;
    tar.balance += amt;
    // 转账成功结束循环
    break;
    } finally {
    tar.lock.unlock();
    }
    }//if
    } finally {
    this.lock.unlock();
    }
    }//if
    }//while
        }//transfer
    }
    2019-07-17
    1
    4
  • Liam
    1 不会出现死锁,因为不存在阻塞的情况
    2 线程较多的情况会导致部分线程始终无法获取到锁,导致活锁

    作者回复: 👍

    2019-03-30
    4
  • tdytaylor
    老师,关于这个问题,我思考之后觉得不会出现死锁,但是没看出为什么会出现活锁

    作者回复: 想想对面相遇的两个人互相谦让的例子看看

    2019-05-15
    3
  • 江湖夜雨
    终于理解最后一道题出现活锁问题了,两个线程同事执行,线程A获取了u1.trylock,线程B获取了u2.trylock,线程A尝试获取u2.trylock,不能成功,线程A结束,同时线程B尝试获取u1.trylock,不能成功,线程B结束,又开始新的一轮,这样一直循环下去!
    2019-07-28
    2
  • 朱小豪
    本文最后的例子,不明白为什么要用while true而且没有跳出循环,这不是死循环吗
    2019-03-30
    2
  • 兔2🐰🍃
    volatile 变量规则:【unlock()对state的写操作 happens-before其他线程lock() 对 state的读操作,】所以线程 T1 的 unlock() 操作 Happens-Before 线程 T2 的 lock() 操作;
    这样会不会更容易理解些?
    2019-09-06
    1
  • zyz
    老师,lock是用aqs实现的,aqs是用了volatile+cas操作系统原子操作保证线程安全的,这个也是管程吗?

    作者回复: 是管程的实现

    2019-08-21
    1
  • Fortune
    唉,到实践部分就懵逼了,前面理论还没消化,实践部分理解不是很透,不适合没有用过或者没有对并发包API理解的人,我太难了。。。。
    2019-08-01
    1
  • 倚梦流
    会出现活锁和死循环,老师你好,这是我的优化后的代码,运用了之前学过的知识,优先获取id值比较小的资源,请老师指点是否有不足之处,谢谢!
        void transfer6(Account tar,int amt){
            boolean isOver=false;
            Account left=this;
            Account right=tar;
            if(left.id>right.id){
                left=tar;
                right=this;
            }
            Thread th=Thread.currentThread();
            while (!th.isInterrupted() && !isOver){
                if(left.lock.tryLock()){
                    try{
                        if(right.lock.tryLock()){
                            try{
                                System.out.println("账号:"+this.id+"开始转账啦!,转出金额:"+amt);
                                if(this.balance>=amt){
                                    this.balance-=amt;
                                    tar.balance+=amt;
                                    System.out.println("转账成功!");
                                }else{
                                    System.out.println("转账失败,余额不足!");
                                }
                                isOver=true;
                                System.out.println("转入账号:"+this.id+" 余额:"+this.balance);
                                System.out.println("转出账号:"+tar.id+" 余额:"+tar.balance);
                                System.out.println("转账结束!");
                            }finally {
                                right.lock.unlock();
                            }
                        }
                    }finally {
                        left.lock.unlock();
                    }
                }
            }
        }

    作者回复: 👍加个随机等待就更好了

    2019-07-06
    2
    1
收起评论
75
返回
顶部