Java并发编程实战
王宝令
资深架构师
立即订阅
15140 人已学习
课程目录
已完结 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并发编程实战
登录|注册

12 | 如何用面向对象思想写好并发程序?

王宝令 2019-03-26
在工作中,我发现很多同学在设计之初都是直接按照单线程的思路来写程序的,而忽略了本应该重视的并发问题;等上线后的某天,突然发现诡异的 Bug,再历经千辛万苦终于定位到问题所在,却发现对于如何解决已经没有了思路。
关于这个问题,我觉得咱们今天很有必要好好聊聊“如何用面向对象思想写好并发程序”这个话题。
面向对象思想与并发编程有关系吗?本来是没关系的,它们分属两个不同的领域,但是在 Java 语言里,这两个领域被无情地融合在一起了,好在融合的效果还是不错的:在 Java 语言里,面向对象思想能够让并发编程变得更简单
那如何才能用面向对象思想写好并发程序呢?结合我自己的工作经验来看,我觉得你可以从封装共享变量、识别共享变量间的约束条件和制定并发访问策略这三个方面下手。

一、封装共享变量

并发程序,我们关注的一个核心问题,不过是解决多线程同时访问共享变量的问题。在《03 | 互斥锁(上):解决原子性问题》中,我们类比过球场门票的管理,现实世界里门票管理的一个核心问题是:所有观众只能通过规定的入口进入,否则检票就形同虚设。在编程世界这个问题也很重要,编程领域里面对于共享变量的访问路径就类似于球场的入口,必须严格控制。好在有了面向对象思想,对共享变量的访问路径可以轻松把控。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Java并发编程实战》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(68)

  • magict4
    1. setUpper() 跟 setLower() 都加上 "synchronized" 关键字。不要太在意性能,老师都说了,避免过早优化。
    2. 如果性能有问题,可以把 lower 跟 upper 两个变量封装到一个类中,例如
    ```
    public class Boundary {
        private final lower;
        private final upper;
        
        public Boundary(long lower, long upper) {
            if(lower >= upper) {
                // throw exception
            }
            this.lower = lower;
            this.upper = upper;
        }
    }
    ```
    移除 SafeVM 的 setUpper() 跟 setLower() 方法,并增入 setBoundary(Boundary boundary) 方法。

    作者回复: 👍👍

    2019-03-26
    3
    50
  • 海鸿
    我看有些人评论用volatie,volatie只能保证可见性,但是保证不了原子性,所以得加锁保证互斥。
    老师我这样理解对吗?

    作者回复: 相当正确!

    2019-03-26
    16
  • QQ怪
    必须加锁啊,synchronized (this)就行了,最简单加锁吧,volatile只能保证内存可见性,并不能保证原子性

    作者回复: 👍

    2019-03-27
    9
  • Boomkeeper
    我就不明白了,使用了synchronized,为啥还用voliate,他的确是保证可见性,但是并不能保证原子性,一般他的应用场景应该是不依赖之前的结果而改变数据,累加的场景明显不适合
    2019-04-09
    1
    5
  • Cc
    又想到一种,既然两个变量要同时锁定,那就把两个变量封装成一个,然后使用cas操作。这样行不行,另外老师帮我看看volatile是不是有多余的地方
    ···········
    volatile AtomicReference<Inventory> inventory = new AtomicReference<>();

        static class Inventory {
            private volatile long upper = 0;

            private volatile long lower = 0;
        }

        void setUpper(long v) {
            long low;
            Inventory oldObj;
            Inventory newObj;
            do {
                oldObj = inventory.get();
                if (v >= (low = oldObj.lower)) {
                    throw new IllegalArgumentException();
                }
                newObj = new Inventory();
                newObj.lower = low;
                newObj.upper = v;

            } while (inventory.compareAndSet(oldObj, newObj));
        }

        void setLower(long v) {
            long upp;
            Inventory oldObj;
            Inventory newObj;
            do {
                oldObj = inventory.get();
                if (v <= (upp = oldObj.upper)) {
                    throw new IllegalArgumentException();
                }
                newObj = new Inventory();
                newObj.lower = v;
                newObj.upper = upp;

            } while (inventory.compareAndSet(oldObj, newObj));
        }


    作者回复: 我觉得这个没有问题,volatile 换成 final会更好

    2019-03-29
    4
  • 抽离の❤️
    老师、讲的真好!
    2019-03-26
    3
  • 悟空
    访问时使用syncchronize对类加锁。保证变量访问的互斥

    作者回复: 对象加锁就可以了

    2019-03-26
    2
  • Lemon
    使用 Condition
    public class SafeWM {

        // 库存上限
        private final AtomicLong upper =
                new AtomicLong(10);
        // 库存下限
        private final AtomicLong lower =
                new AtomicLong(2);

        private ReentrantLock lock = new ReentrantLock();
        private Condition c1 = lock.newCondition();
        private Condition c2 = lock.newCondition();
        
        // 设置库存上限
        void setUpper(long v) {
            try {
                lock.lock();
                // 检查参数合法性
                while (v < lower.get()) {
                    c1.await();
                }
                upper.set(v);
                c2.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }

        }

        // 设置库存下限
        void setLower(long v) {

            try {
                lock.lock();
                // 检查参数合法性
                while (v > upper.get()) {
                    c2.await();
                }
                lower.set(v);
                c1.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }

    }

    作者回复: 错误的设置会导致永远等待,太危险了

    2019-03-26
    2
  • Zm
    Volatile修饰变量
    2019-03-26
    2
  • 无庸
    compareAndSet吧

    void setUpper(long v){
      upper.compareAndSet(upper.longValue
    (),v);
    }

    作者回复: 不满足合理库存的约束条件

    2019-03-26
    2
  • 一早起来,就把文章看完了,期待老师后面更精彩的内容
    2019-03-26
    2
  • 逆水行舟
    那本书,有些晦涩,但是是必读的。

    作者回复: 太晦涩了

    2019-03-31
    1
  • 阿琨
    老师,这样写有什么问题吗,总感觉哪里怪怪的。
    public class DBPush {
        private volatile static DBPush dbPush = null;

        private DBPush() {
        }

        public static DBPush getInStance() {
            if (dbPush == null) {
                synchronized (DBPush.class) {
                    if (dbPush == null) {
                        dbPush = new DBPush();
                    }
                }
            }
            return dbPush;
        }
    }

    作者回复: 没问题

    2019-03-26
    1
  • 江南豆沙包
    令哥,一直在追你的专栏,结合公司目前实际情况,有个疑问,如果你专栏中的例子中的共享信息,是整个系统维度的,系统又是多实例集群部署的,我们该怎么办呢,能不能在思想或实现思路上给点建议指导。

    作者回复: 感谢信任!我们这里说的共享是进程级别的,如果是分布式计算只能靠redis,db,zk这些来搞分布式的锁,当然不共享是最好的解决方案。

    2019-03-26
    1
  • undifined
    要保证变量间的约束条件,就必须保证判断和赋值是一个原子操作,可以通过给 upper 和 lower 同时加锁,也可以通过 AtomicLong 提供的方法进行操作

        // 设置库存上限
        void setUpper(long v) {
            // 检查参数合法性
            upper.getAndUpdate(u -> {
                if (v < lower.get()) {
                    throw new IllegalArgumentException();
                } else {
                    return v;
                }
            });
        }

        // 设置库存下限
        void setLower(long v) {
            // 检查参数合法性
            lower.getAndUpdate(u -> {
                if (v > upper.get()) {
                    throw new IllegalArgumentException();
                } else {
                    return v;
                }
            });
        }
     
    老师这样理解对吗

    作者回复: 我感觉不可以,还是有竞态条件,你可以在return前面增加sleep看看

    2019-03-26
    1
  • xlz
    validate我看书中说像a=4这种是有原子性的,对于复合操作是没有原子性,望老师解答

    作者回复: 是这样的,可以使用JDK提供的原子类

    2019-11-30
  • 大大。
    加一个lock,在set里面去获取lock,获取到的才能set
    2019-11-29
  • 朕爱吾妃
    最简单的方法就是加synchronized关键字,不过我不认为这是最佳的解决方案,这个方案违反了三个原则中两个原则,第二个原则和第三个原则,先说第三个原则,其实加上synchronized是属于过早的优化,解决方案就是可以创建一个不变的对象,然后不管是设置上限还是设置下限都需要对这个资源进行竞争,不过也没有晚多少优化,但是这个方案没有解决第二个原则,因为还是会使用到synchronized的关键字... ...,这个我没法解决
    2019-11-11
  • 吃饭饭
    不能锁 synchronized(当前参数值) 吗?
    2019-10-16
  • DFighting
    用前几章的那个转账例子用到的,每次执行set方法时都要在一个Lock方法中持有上限和下限,然后判断是否符合约束,线程间协作可以考虑synchronized+wait()+notifyAll()或Lock+Condition实现;
    2019-09-19
收起评论
68
返回
顶部