本篇来谈谈 Java 并发编程:如何保证对象的线程安全性。

01、前言

先让我吐一句肺腑之言吧,不说出来会憋出内伤的。《Java 并发编程实战》这本书太特么枯燥了,尽管它被奉为并发编程当中的经典之作,但我还是忍不住。因为第四章“对象的组合”我整整啃了两周的时间,才啃出来点肉丝。

读者朋友们见谅啊。要怪只能怪我自己的学习能力有限,真读不了这种生硬无趣的技术书。但是为了学习,为了进步,为了将来(口号喊得有点大了),只能硬着头皮上。

请随我来,我尽量写得有趣点。

02、线程安全类

作者说了啊,设计一个线程安全类需要三个步骤:

1)找出表示对象状态的所有变量
2)对变量进行有效性约束
3)增加类的并发访问策略

我在作者说的基础上做了微调,读起来更加容易理解。怎么和代码对应起来了,先来看一个普通的计数器类 Counter。

public class Counter {private int value = 0;public int getValue() {return value;}public int increment() {return ++value;}
}

1)Counter 的状态变量只有一个,就是 value。

2)value 的有效性是什么呢,它最大不能超过 Integer.MAX_VALUE,最小只能为 0(计数嘛,总不能记成负数)。换句话说就是,value 的有效范围是 0 ~ Integer.MAX_VALUE

public int increment() {if (value == Integer.MAX_VALUE) {throw new IllegalStateException("counter overflow");}return ++value;
}

3)增加类的并发访问策略,直接上 synchronized。

public class Counter {private int value = 0;public synchronized int getValue() {return value;}public synchronized int increment() {if (value == Integer.MAX_VALUE) {throw new IllegalStateException("counter overflow");}return ++value;}
}

03、非线程安全的对象

之前我们谈了如何设计一个线程安全的类。如果类是安全的,那么它作为对象使用的时候就是线程安全的。但如果一个类不是线程安全的,它作为对象使用的时候怎么保证是线程安全的呢?

作者提到了一个名词叫做“封闭机制”:

1)把对象作为类的私有成员变量;
2)把对象作为方法内部的局部变量;
3)线程 A 把对象传递到 B 线程,而不是与线程 B 共享这个对象;

大家来看下面这段代码。

class StringList {private List<String> myList = new ArrayList<>();public synchronized void addString(String s) {myList.add(s);}public synchronized void removeString(String s) {myList.remove(s);}
}

本身 ArrayList 不是线程安全的,但 myList 是私有的,访问它的两个方法 addString()removeString() 都加了关键字 synchronized,因此 myList 在使用的时候就变成了线程安全的对象,StringList 类就变成了一个线程安全的类——这种方式被称作 Java 监视器模式:可变的状态被封装在一个类中,访问它们只能通过加上锁的方法。

查看 Vector 的源码,你会发现,它之所以是线程安全的,就是采用的这种监视器模式

04、在已有的线程安全类上追加功能

假如现在有一个线程安全的类,比如之前提到的 StringList,它包含了大多数我们需要的功能,但还不够,那么怎么确保我们追加的功能不破坏原有的线程安全性呢?

最直接的方法当然是修改源码,假如源码掌握在我们自己手里的话。

class StringList {private List<String> myList = new ArrayList<>();public synchronized void addString(String s) {myList.add(s);}public synchronized void addIfNotExist(String s) {boolean isExist = myList.contains(s);if (!isExist) {myList.add(s);}}
}

我们新增了一个 addIfNotExist() 方法:如果字符串 s 还没有添加到 List 当中,就添加一个。

新增的方法没有破坏 StringList 的线程安全性,因为当两个线程同时执行 addIfNotExist() 方法时,需要经过 synchronized 把守的这道大门。

但很多时候,我们无法直接修改源码,这时候就只好在原来的基础上进行改造。大家听过之前的“红芯”浏览器吗?在谷歌浏览器的内核上裹了一层层皇帝的新衣。

class StringList {protected List<String> myList = new ArrayList<>();public synchronized void addString(String s) {myList.add(s);}
}public class NewStringList extends StringList {public synchronized void addIfNotExist(String s) {boolean isExist = myList.contains(s);if (!isExist) {myList.add(s);}}
}

新建一个类 NewStringList,继承自 StringList,然后在 NewStringList 中新增一个方法 addIfNotExist()。当然了,这样做的前提是父类中的 myList 是 protected 而不是 private 的。因此,这种做法不具有普适性。

05、最后

站在我的角度来看,《Java 并发编程实战》的第四章“对象的组合”写得烂透了。导致我在写这篇文章的时候感觉到万分的痛苦。希望下一章不要写的这么烂。

上一篇:如何保证共享变量的可见性?

上上篇:如何保证共享变量的原子性?

微信搜索「沉默王二」公众号,关注后回复「Java 并发编程实战」即可获取该书的电子版(推荐购买纸质书)。

Java 并发编程(四):如何保证对象的线程安全性相关推荐

  1. java单线程共享,「Java并发编程实战」之对象的共享

    前言 本系列博客是对<Java并发编程实战>的一点总结,本篇主要讲解以下几个内容,内容会比较枯燥.可能大家看标题不能能直观的感受出到底什么意思,这就是专业术语,哈哈,解释下,术语(term ...

  2. 【Java并发编程】安全发布对象

    文章目录 安全发布对象 一.对象发布 二.对象逸出 三.安全发布的方法 安全发布对象 一.对象发布 在介绍安全发布对象之前,应该首先聊一聊什么是发布对象.发布对象是"使一个对象能够被当前范围 ...

  3. 【Java并发编程 四】Java的进程与线程

    什么是进程?进程是程序的⼀次执⾏过程,是系统运⾏程序的基本单位,因此进程是动态的.系统运行和关闭⼀个程序即是⼀个进程从创建,运⾏到消亡的过程.在 Java 中,当我们启动 main 函数时其实就是启动 ...

  4. 【Java并发编程实战】03对象的共享

    上一篇介绍了如何通过同步多个线程避免同一时刻访问相同数据,本篇介绍如何共享和发布对象,使它们被安全地由多个进程访问. 1.可见性 通常,我们无法保证执行读操作的线程能看到其他线程写入的值,因为每个线程 ...

  5. Java 并发编程(二)对象的发布逸出和线程封闭

    对象的发布与逸出 "发布(Publish)"一个对象是指使对象能够在当前作用域之外的代码中使用.可以通过 公有静态变量,非私有方法,构造方法内隐含引用 三种方式. 如果对象构造完成 ...

  6. Java并发编程实战(chapter_3)(线程池ThreadPoolExecutor源码分析)

    为什么80%的码农都做不了架构师?>>>    这个系列一直没再写,很多原因,中间经历了换工作,熟悉项目,熟悉新团队等等一系列的事情.并发课题对于Java来说是一个又重要又难的一大块 ...

  7. java并发编程(二)多个线程多个锁

    多个线程多个锁 多个线程多个锁:多个线程,每个线程都可以拿到自己制定的锁,分别获得锁之后,执行synchronized方法体的内容.就是在上次那个博客上说道的锁竞争的问题,是因为所有的线程过来以后都争 ...

  8. Java并发编程(4):守护线程与线程阻塞的四种情况

    守护线程 Java中有两类线程:User Thread(用户线程).Daemon Thread(守护线程) 用户线程即运行在前台的线程,而守护线程是运行在后台的线程. 守护线程作用是为其他前台线程的运 ...

  9. 【多线程】java 并发编程中的Condition对象-指定唤醒某个线程

    1.概述 转载:java高并发系列 - 第13天:JUC中的Condition对象 synchronized中实现线程等待和唤醒 Condition简介及常用方法介绍及相关示例 使用Condition ...

最新文章

  1. Android_TextSwitcher和ImageSwitcher
  2. [转]NS2仿真过程中解决动画仿真节点未定义问题
  3. 好云推荐官丨飞天加速之星怎样选择云服务器ECS?
  4. 《JAVA与模式》之观察者模式
  5. Pycharm - 创建python 文件模版
  6. MYSQL-主键、外键
  7. 学习OpenGL-ES: 2 - EGL解析
  8. make_classification参数
  9. Zemax操作24--高斯光束的聚焦和传播
  10. 南大计算机专业课表,南大课程表
  11. 【中软杯国二开源】基于PaddleOCR和深度学习的企业实体识别
  12. My97DatePicker时间控件在asp.net的应用
  13. Java线程Dump分析-工具TDA
  14. 树及树的算法(4) —— 红黑树
  15. wincc实现手机APP远程监控
  16. [异常检测]Learning Regularity in Skeleton Trajectories for Anomaly Detection in Videos
  17. 力扣(LeetCode)——编译、提交和注释快捷键
  18. Android软件测试外文文献,软件测试中英文对照外文翻译文献
  19. day35 数据库的初步认识
  20. ADS设计不等分功分器

热门文章

  1. 跨界玩CRM营销 开吃餐饮大数据问题
  2. Dubbo-qibai
  3. uniapp 云打包后IOS白屏,真机调试也是白屏,没有报错!解决办法
  4. windows 截屏快捷键x220_电脑截屏的快捷键是什么?
  5. java解决循环引用_Java 中如何避免循环引用,解决相互依赖的问题
  6. uniapp自动保存代码
  7. 计算机二级第23套word题,计算机等级考试:二级VFP机试第23套
  8. Ubuntu设置鼠标灵敏度
  9. android分屏底层实现,Android 7.0分屏原理及生命周期
  10. 安卓Unity分屏设置