JAVA并发编程实战——共享对象
目录
- 思维导图
- 1. 可见性
- 1. 1 过期数据
- 1.2 锁和可见性
- 1.3 Volatile变量
- 2. 发布和逸出
- 2.1 安全构建实践
- 3. 线程封闭
- 3.1 栈限制
- 3.2 ThreadLocal
- 4. 不可变性
- 5 安全发布的模式
- 参考文献
思维导图
1. 可见性
内存可见性:当一个线程修改使用的共享对象,其它线程能够看到改变
以下面demo为例:
public class NoVisibility {private static boolean ready;private static int number;private static class ReadyThread extends Thread {@Overridepublic void run() {while (!ready) {//线程从执行态转为就绪态,放弃cpuThread.yield();}System.out.println(number);}}public static void main(String[] args) {new ReadyThread().start();number = 10;ready = true;}
}
上述不是线程安全的。
- 正常输出10,主线程修改后,对ready线程可见。
- 可能输出0,如果进行了重排序可能先修改ready,而number还未修改就已经被打印出来。
- 处于一直循环状态,ready的值可能一直不可见。
因为涉及到数据的线程共享,我们应该使用下面的简单方法,避免上述各种结果:
只要数据被跨线程共享,就应该进行恰当的同步。
1. 1 过期数据
如下代码:
public class MutableInteger {private int value;public int getValue() {return value;}public void setValue(int value) {this.value = value;}
}
如果多个线程都访问value,可能出现一个线程调用getValue,另一个调用setValue,可能导致看不到最新的值。
为了避免看到过期值,可以采取对getValue和setValue进行加锁synchronized。
对于long和double类型,可能出现另一种现象:得到一个凭空而来的值。
jvm允许将64位读写分为两个32位操作,如果此时读写不发生在同一线程,将会导致出现一个数的高32和另一个数低32位。
1.2 锁和可见性
如下图:
线程A和B都访问了共享变量x,如果不加锁可能B会读到过期的x。但是内部锁保障了可见性。
当线程B执行到同一个代码块,线程A对x的操作加锁对B来说都是可见的。
锁不仅可以同步和互斥,也是内存可见的,当然读取和修改线程是公共的相同的锁。
1.3 Volatile变量
Java提供了一种同步的弱形式:volatile变量,来保证可见性。
- volatile修饰的变量,不会与其它操作进行重排序。
- volatile修饰的变量,不会被缓存在寄存器或者在多处理器系统中对其它处理器隐藏的地方。因此每次读取都是从内存中读取,保障了读取的都是最新的值。
volatile通常用于循环
java并发包中的原子类基本都是使用volatile修饰变量,改变状态使用下面的while进行:
通过while不停判断我们修改时,使用的是最新的可见的值。
加锁可以保障可见性和原子性,volatile变量只能保障可见性。
2. 发布和逸出
发布:发布一个对象使其可以在当前作用域之外的代码使用。
逸出:一个对象尚未准备好,就将它发布出去。
如下是一个逸出情况:
public class UnsafeStatus {private String[] states = new String[]{"new", "going", "end"};public String[] getStates() {return states;}
}
上述代码将导致任何线程都可以修改states变量。
2.1 安全构建实践
不要让this引用在构造期间逸出。
如下不安全示例,构造期间导致this逸出
public class ThisEscapeExample {public ThisEscapeExample( EventSource eventSource) {eventSource.registerListener(new EventListener() {public void onEvent(Event event) {doSomething();}});}public void doSomething() {System.out.println("do something");}}class EventSource {void registerListener(EventListener eventListener) {System.out.println("do something");}
}
上述由于还没构建完对象就使this逸出到了eventsource中。出现安全隐患
安全代码如下:
public class SafeExample {private final EventListener listener;private SafeExample() {this.listener = new EventListener() {public void onEvent(Event event) {doSomething();}};}public static SafeExample newInstance(EventSourceO eventSourceO) {SafeExample safeExample = new SafeExample();eventSourceO.registerListener(safeExample.listener);return safeExample;}private void doSomething() {System.out.println("do something");}
}class EventSourceO {void registerListener(EventListener eventListener) {System.out.println("do something");}
}
通过工厂方法和私有构造确保了对象构造完成后,发布到eventsource中,保障了并发安全性。
3. 线程封闭
一个避免同步的方式就是不共享数据,比如将数据限制在本线程中。
3.1 栈限制
对于栈是java线程私有的,当本地变量没有发布,其它线程就不能访问这个变量。
示例代码:
public class StackLimit {public void loadObject(Collection<Object> objects) {SortedSet<Object> innerVar;innerVar = new TreeSet<>();innerVar.addAll(objects) ;for (Object o : innerVar) {System.out.println("do something" + o.toString());}}
}
innerVar只有在执行线程本地变量表中存在引用,没有发布出去。从而保障了对象安全性。
3.2 ThreadLocal
ThreadLocal提供get和set访问器,为每个访问它的线程提供一个ThreadLocalMap的备份,这个map存储在线程Thread中,只有线程本身可以访问,故get返回的当先线程set的值。
比如我们可以使用下面demo获取一个与线程绑定的数据库连接:
public class ConnThreadLocal {private static ThreadLocal<Connection> connHolder = new ThreadLocal<Connection>(){@SneakyThrows@Overrideprotected Connection initialValue() {return DriverManager.getConnection("DB_URL");}};public static Connection getConnection() {return connHolder.get();}
}
4. 不可变性
不可变对象永远是线程安全的。
如下demo:
public class FinalObjectExample {private final Set<String> places = new HashSet<>();public FinalObjectExample() {places.add("Beijing");places.add("Shanghai");places.add("Xian");}public boolean isPlaces(String targetPlace) {return places.contains(targetPlace);}
}
该对象是一个不可变类。
只有满足以下状态, 一个对象才是不可变的:
1. 它的状态在创建后不能更改。
2. 所有域都是final;并且创建期间没有发生this逸出。
5 安全发布的模式
为了安全发布对象,对象的引用和对象的状态必须同时对其它线程可见,一个正确创建对象应该通过下列条件进行安全发布:
1. 通过静态初始化器初始化对象的引用。
2. 将对象引用存储到volatile或者AtomicReference。
3. 将它的引用存储到正确创建对象的final域中。
4. 将它的引用存储到由锁保护的域中。
参考文献
[1]. 《JAVA并发编程实战》.
JAVA并发编程实战——共享对象相关推荐
- [Java并发编程实战] 共享对象之可见性
2019独角兽企业重金招聘Python工程师标准>>> 「 ***盛年不重来,一日难再晨,及时当勉励,岁月不待人.***」 陶渊明 PS: 如果觉得本文有用的话,请帮忙点赞,留言评论 ...
- java单线程共享,「Java并发编程实战」之对象的共享
前言 本系列博客是对<Java并发编程实战>的一点总结,本篇主要讲解以下几个内容,内容会比较枯燥.可能大家看标题不能能直观的感受出到底什么意思,这就是专业术语,哈哈,解释下,术语(term ...
- Java并发编程实战笔记2:对象的组合
设计线程安全的类 在设计现车让安全类的过程之中,需要包含以下三步: 找出构成对象状态的所有变量 找出约束状态变量的不变性条件 建立对象状态的并发访问策略 实例封闭 通过封闭机制与合适的加锁策略结合起来 ...
- java并发编程实战学习(3)--基础构建模块
转自:java并发编程实战 5.3阻塞队列和生产者-消费者模式 BlockingQueue阻塞队列提供可阻塞的put和take方法,以及支持定时的offer和poll方法.如果队列已经满了,那么put ...
- 《java并发编程实战》- 关于this引用溢出
书中3.2中关于this引用溢出例子: 隐式地使this引用逸出(不要这么做): public class ThisEscape {public ThisEscape(EventSource sour ...
- 【极客时间】《Java并发编程实战》学习笔记
目录: 开篇词 | 你为什么需要学习并发编程? 内容来源:开篇词 | 你为什么需要学习并发编程?-极客时间 例如,Java 里 synchronized.wait()/notify() 相关的知识很琐 ...
- Java并发编程实战之互斥锁
文章目录 Java并发编程实战之互斥锁 如何解决原子性问题? 锁模型 Java synchronized 关键字 Java synchronized 关键字 只能解决原子性问题? 如何正确使用Java ...
- 《Java 并发编程实战》--读书笔记
Java 并发编程实战 注: 极客时间<Java 并发编程实战>–读书笔记 GitHub:https://github.com/ByrsH/Reading-notes/blob/maste ...
- Java并发编程实战————Executor框架与任务执行
引言 本篇博客介绍通过"执行任务"的机制来设计应用程序时需要掌握的一些知识.所有的内容均提炼自<Java并发编程实战>中第六章的内容. 大多数并发应用程序都是围绕&qu ...
最新文章
- Python的零基础超详细讲解(第十天)-Python的input()函数
- 了解大脑的“小情绪”,轻松成为“效率达人”
- 浮点数内存表示---记录一道题目
- MVC中实现订单表和订单详细表联动新增的一种方法
- 2020-11-19(栈帧)
- 如何在ubuntu下安装detectron2_Ubuntu下detectron2 的安装使用笔记
- VHDL简单微处理器的设计
- Java中的equals和==的差别 以及Java中等价性和同一性的讨论
- ROS2——Win10上的rqt_graph无法正常运行
- SQLServer IP不能登录问题解决
- 网络协议:关于TCP/IP,必须知道的十个知识点
- VMP学习笔记之壳基础(一)
- UE编辑器中的快捷键(一)
- google离线地图制作
- 谷歌google搜索打不开、谷歌gmail邮箱及相关服务无法登录的解决的方法
- Mac 判断终端是否走了代理服务器的方法
- 初识HTML(四)进阶:CSS基础、常用属性
- 如何用Navicat连接mongoDB
- 来电显示软件测试自学,用MSComm控件实现来电显示
- 背锅侠?程序出现bug是测试/开发工程师水平有限导致的?我只是个搬砖的......