Java 并发编程(四):如何保证对象的线程安全性
本篇来谈谈 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 并发编程(四):如何保证对象的线程安全性相关推荐
- java单线程共享,「Java并发编程实战」之对象的共享
前言 本系列博客是对<Java并发编程实战>的一点总结,本篇主要讲解以下几个内容,内容会比较枯燥.可能大家看标题不能能直观的感受出到底什么意思,这就是专业术语,哈哈,解释下,术语(term ...
- 【Java并发编程】安全发布对象
文章目录 安全发布对象 一.对象发布 二.对象逸出 三.安全发布的方法 安全发布对象 一.对象发布 在介绍安全发布对象之前,应该首先聊一聊什么是发布对象.发布对象是"使一个对象能够被当前范围 ...
- 【Java并发编程 四】Java的进程与线程
什么是进程?进程是程序的⼀次执⾏过程,是系统运⾏程序的基本单位,因此进程是动态的.系统运行和关闭⼀个程序即是⼀个进程从创建,运⾏到消亡的过程.在 Java 中,当我们启动 main 函数时其实就是启动 ...
- 【Java并发编程实战】03对象的共享
上一篇介绍了如何通过同步多个线程避免同一时刻访问相同数据,本篇介绍如何共享和发布对象,使它们被安全地由多个进程访问. 1.可见性 通常,我们无法保证执行读操作的线程能看到其他线程写入的值,因为每个线程 ...
- Java 并发编程(二)对象的发布逸出和线程封闭
对象的发布与逸出 "发布(Publish)"一个对象是指使对象能够在当前作用域之外的代码中使用.可以通过 公有静态变量,非私有方法,构造方法内隐含引用 三种方式. 如果对象构造完成 ...
- Java并发编程实战(chapter_3)(线程池ThreadPoolExecutor源码分析)
为什么80%的码农都做不了架构师?>>> 这个系列一直没再写,很多原因,中间经历了换工作,熟悉项目,熟悉新团队等等一系列的事情.并发课题对于Java来说是一个又重要又难的一大块 ...
- java并发编程(二)多个线程多个锁
多个线程多个锁 多个线程多个锁:多个线程,每个线程都可以拿到自己制定的锁,分别获得锁之后,执行synchronized方法体的内容.就是在上次那个博客上说道的锁竞争的问题,是因为所有的线程过来以后都争 ...
- Java并发编程(4):守护线程与线程阻塞的四种情况
守护线程 Java中有两类线程:User Thread(用户线程).Daemon Thread(守护线程) 用户线程即运行在前台的线程,而守护线程是运行在后台的线程. 守护线程作用是为其他前台线程的运 ...
- 【多线程】java 并发编程中的Condition对象-指定唤醒某个线程
1.概述 转载:java高并发系列 - 第13天:JUC中的Condition对象 synchronized中实现线程等待和唤醒 Condition简介及常用方法介绍及相关示例 使用Condition ...
最新文章
- Android_TextSwitcher和ImageSwitcher
- [转]NS2仿真过程中解决动画仿真节点未定义问题
- 好云推荐官丨飞天加速之星怎样选择云服务器ECS?
- 《JAVA与模式》之观察者模式
- Pycharm - 创建python 文件模版
- MYSQL-主键、外键
- 学习OpenGL-ES: 2 - EGL解析
- make_classification参数
- Zemax操作24--高斯光束的聚焦和传播
- 南大计算机专业课表,南大课程表
- 【中软杯国二开源】基于PaddleOCR和深度学习的企业实体识别
- My97DatePicker时间控件在asp.net的应用
- Java线程Dump分析-工具TDA
- 树及树的算法(4) —— 红黑树
- wincc实现手机APP远程监控
- [异常检测]Learning Regularity in Skeleton Trajectories for Anomaly Detection in Videos
- 力扣(LeetCode)——编译、提交和注释快捷键
- Android软件测试外文文献,软件测试中英文对照外文翻译文献
- day35 数据库的初步认识
- ADS设计不等分功分器