1、实例封闭

class personset{
private final Set<Person> myset = new HashSet<Person>(); public void addPersom(Person p){
myset.add(p);
} public boolean containPerson(Person p){
return myset.contains(p);
}
}

  这个类的状态是由HashSet来进行管理的,这里的myset是私有的且并不会逸出,因此HashSer被封闭在personset中,所以如果不对myset进行访问那这个类就是线程安全的,但是由于HashSet并不是线程安全的,所以其add和contains方法都不是线程安全的,所以需要加上锁。

class personset{
private final Set<Person> myset = new HashSet<Person>(); public synchronized void addPersom(Person p){
myset.add(p);
} public synchronized boolean containPerson(Person p){
return myset.contains(p);
}
}

  这样personset的状态完全由它的内置锁保护着,因而它就是一个线程安全的类。

  这个例子中并未对Person的线程安全性进行假设,如果Person类是可变的,那么从personset中获得Person对象时还需要额外的同步

  示例:车辆追踪

class MutablePoint{
private int x;
private int y;
public MutablePoint(){
this.x = 0;
this.y = 0;
}
public MutablePoint(MutablePoint m){
this.x = m.getX();
this.y = m.getY();
}
public synchronized int getX() {
return x;
} public synchronized void setX(int x) {
this.x = x;
} public synchronized int getY() {
return y;
} public synchronized void setY(int y) {
this.y = y;
}
}  

  MutablePoint是一个记录车辆坐标的类  

public class MonitorVehicleTracker {
private final Map<String, MutablePoint> locations;
public MonitorVehicleTracker(Map<String,MutablePoint> locations){
this.locations = deepcopy(locations);
}
public synchronized Map<String,MutablePoint> getLocations(){
return deepcopy(locations);
}
public synchronized MutablePoint getLocation(String id)
{
//获取要返回的数据
MutablePoint loc = locations.get(id);
//创建一个新的对象返回
return loc == null ? null : new MutablePoint(loc);
} private Map<String, MutablePoint> deepcopy(Map<String, MutablePoint> m ){
Map<String, MutablePoint> result = new HashMap<String, MutablePoint>();
for(String id: m.keySet()){
result.put(id, new MutablePoint(m.get(id)));
}
//返回的是一个不可修改的Map
return Collections.unmodifiableMap(result);
}
}

  MonitorVehicleTracker是一个线程安全的追踪器,它所包含的Map对象和可变的MutablePoint都是对外未曾发布的当需要返回车辆的位置的时候,通过MutablePoint的构造函数来复制新的值。

2、线程安全性的委托

  还可以创建一个不可变的Point类来替换MutablePoint,然后构造一个委托给线程安全的车辆追踪器,我们可以先把数据存储在一个线程安全的ConcurrentHashMap类中。

class Point{
public final int x, y;
public Point(int x,int y){
this.x = x;
this.y = y;
}
}

  这个Point是不可变的,因此它是线程安全,因为不可变的值可以自由的分享和发布,所以在返回location的时候不需要再复制。

public class MonitorVehicleTracker {
private final ConcurrentHashMapConcurrentHashMap<String,Point> locations;
private final Map<String, Point> unmodifiableMap;
public MonitorVehicleTracker(Map<String,Point> points){
locations = new ConcurrentHashMap<String,Point>(points);
unmodifiableMap = Collections.unmodifiableMap(locations);
}
public Map<String,Point> getLocations(){
return unmodifiableMap;
}
public Point getLocation(String id)
{
return unmodifiableMap.get(id);
}
}

  这里没有使用明显的同步,所有对状态的访问都由ConcurrentHashMap来管理,创建对象的构造器中先讲数据存储在ConcurrentHashMap类型的对象中,再调用Collections的unmodifibleMap方法将数据传给unmodifiableMap,这个方法返回指定映射的不可修改视图以保证线程安全,这样想要修改unmodifiableMap中的数据(想要修改这个对象的状态),只能通过构造器新建一个对象了。

3、当委托失效时

class NumberRange{
private final AtomicInteger lower = new AtomicInteger(0);
private final AtomicInteger upper = new AtomicInteger(0);
public void setLower(int i){
     //这里是不安全的
if(i > upper.get()){
throw new IllegalArgumentException(
"can't set lower to " + i + " > upper ");
}
lower.set(i);
}
public void setUpper(int i){
     //这里是不安全的
if(i < lower.get()){
throw new IllegalArgumentException(
"can't set lower to " + i + " > upper ");
}
upper.set(i);
}
public boolean isInrange(int i ){
return (i >= lower.get() && i <= upper.get());
}
}

  这里的NumberRange并不是线程安全的,setLower和setUpper都是先检查后执行的操作。这里会出现的问题就是如果线程A想要修改了upper为4,但是并没有进行到set这一行,线程B想要修改lower为5是可以通过检查的。结果可能会变成(5,4)。

  结论就是如果某个类含有复合操作,就像上面这样,仅靠委托并不足以实现线程安全性,在这种情况下,这个类必须提供自己的加锁机制保证这些复合操作都是原子操作,除非整个复合操作都可以委托给状态变量。

4、 客户端加锁机制

class ListHelps<E>{
public List<E> list = Collections.synchronizedList(new ArrayList<E>());
public synchronized boolean putIfAbsent(E x){
boolean absent = !list.contains(x);
if(absent)
list.add(x);
return absent;
}
}

  这里实现的是如果list中没有x则添加x。这里的锁仅仅只是锁住了“如果没有则添加”这个操作,所以如果线程A进行了这个操作,其他线程并不能执行“如果没有则添加”这个操作。但是如果线程A在“其他线程调用另外的方法添加了x”之前已经执行完验证x的存在。总的来说就是putIfAbsent相对于其他list的操作并不是原子性的。

class ListHelps<E>{
public List<E> list = Collections.synchronizedList(new ArrayList<E>());
public boolean putIfAbsent(E x){
synchronized(list) {
boolean absent = !list.contains(x);
if (absent)
list.add(x);
return absent;
}
}
}

  上面的代码给出了如何使putIfAbsent相对于其他list的操作变得是原子性的。

5、组合

class ImprovedList<T> implements List<T>{
private List<T> list; public ImprovedList(List<T> list){
this.list = list;
}
public synchronized boolean putIfAbsent(T x){
boolean absent = !list.contains(x);
if (absent)
list.add(x);
return absent;
}
}

  这样的组合将存储数据的list,也就是真正代表类的状态的list变量封闭在了ImprovedList中,然后putIfAbsent是一个原子性的操作,这样某线程在执行putIfAbsent的时候其他线程也无法修改list的值。

6、总结

  第一个例子实例封闭,将代表对象状态的变量 myset定义为final类型的,然后把使用这个变量的方法都加上锁,这样就能保证多线程程序使用这个类的时候的原子性。

  第二个记录车辆坐标的类中,记录对象状态的变量的初始化是通过复制得到的,且得到的是一个不可修改的map,每次返回数据的时候是通过新建一个对象返回的,实现了把map变量(代表对象状态的变量)封闭在了对象中。而且在初始化和返回数据操作时加上了内置锁,实现了线程安全

  第三个也是记录车辆坐标的,这里使用了线程安全的ConcurrentHashMap,真正代表对象状态的map变量获取数据也是从这个ConcurrentHashMap中获得,而从外界获取数据则是ConcurrentHashMap,从而实现了线程安全。

  第四个例子中代表对象状态的两个变量使用AtomicInteger,这里只实现了数据读写的线程安全,但是在逻辑上数据的使用,也就是先检查后执行的操作是根据数据的内容进行的,而且这里的setLower和setUpper两个操作的结果还是互相影响的,所以如果想要加锁,也需要把这两个操作加在一块。

  第五个和第六个想要解决的问题是一样的,第五个把关于list的操作加上锁实现线程安全,第六个把list封闭在对象中,实现每次进行putIfAbsent操作的时候其他线程无法改变list的内容

Java并发编程实战笔记—— 并发编程3的更多相关文章

  1. Java并发编程实战笔记—— 并发编程1

    1.如何创建并运行java线程 创建一个线程可以继承java的Thread类,或者实现Runnabe接口. public class thread { static class MyThread1 e ...

  2. Java并发编程实战笔记—— 并发编程2

    1.ThreadLocal Java中的ThreadLocal类可以让你创建的变量只被同一个线程进行读和写操作.因此,尽管有两个线程同时执行一段相同的代码,而且这段代码又有一个指向同一个ThreadL ...

  3. Java并发编程实战笔记—— 并发编程4

    1.同步容器类 同步容器类都是线程安全的,但在某些情况下可能需要额外的客户端加锁保护复合操作. 容器上常见的复合操作包括但不限于:迭代(反复访问数据,直到遍历完容器中所有的元素为止).跳转(根据指定顺 ...

  4. Java并发编程实战.笔记十一(非阻塞同步机制)

    关于非阻塞算法CAS. 比较并交换CAS:CAS包含了3个操作数---需要读写的内存位置V,进行比较的值A和拟写入的新值B.当且仅当V的值等于A时,CAS才会通过原子的方式用新值B来更新V的值,否则不 ...

  5. Java并发编程实战 01并发编程的Bug源头

    摘要 编写正确的并发程序对我来说是一件极其困难的事情,由于知识不足,只知道synchronized这个修饰符进行同步. 本文为学习极客时间:Java并发编程实战 01的总结,文章取图也是来自于该文章 ...

  6. 【ARM-Linux开发】OpenACC并行编程实战笔记

    今年运气比较好,学了cuda之后,了解到了gpu的另两种使用语言opencl和openacc,  opencl(Open Computing Language ,开放计算语言)是面向异构系统的并行编程 ...

  7. 多线程-java并发编程实战笔记

    线程安全性 编写线程安全的代码实质上就是管理对状态的访问,而且通常都是共享的,可变的状态. 一个对象的状态就是他的数据,存储在状态变量中,比如实例域或静态域.所谓共享是指一个对象可以被多个线程访问:所 ...

  8. java并发编程实战笔记---(第四章)对象的组合

    4.1设计线程安全的类 包含三个基本要素: 1.找出构成对象状态的所有变量 2.找出约束状态变量的不变性条件 2.简历对象状态的并发访问管理策略 对象的状态: 域 基本类型所有域, 引用类型包括被引用 ...

  9. java并发编程实战笔记---(第三章)对象的共享

    3.1 可见性 synchronized 不仅实现了原子性操作或者确定了临界区,而且确保内存可见性. *****必须在同步中才能保证:当一个线程修改了对象状态之后,另一个线程可以看到发生的状态变化. ...

随机推荐

  1. Spring Bean 生命周期之destroy——终极信仰

    上一篇文章 Spring Bean 生命周期之我从哪里来 说明了我是谁? 和 我从哪里来? 的两大哲学问题,今天我们要讨论一下终极哲学我要到哪里去? 初始化 Spring Bean 有三种方式: @P ...

  2. redis module 学习—官网文档整理

    前言 redis在4.0版本中,推出了一个非常吸引的特性,可以通过编写插件的模式,来动态扩展redis的能力.在4.0之前,如果用户想拥有一个带TTL的INCRBY 命令,那么用户只能自己去改代码,重 ...

  3. MySQL login-path 本地快捷登陆

    目录 1.什么是 login-path 2. 配置 login-path 2.2.配置: 2.3.显示配置: 2.3.1.显示执行的login-path配置 2.3.2.显示所有的login-path ...

  4. C++模版的用法

    模板是实现代码重用机制的一种工具,实质就是实现类型参数化,即把类型定义为参数. C++提供两种模板:函数模板,类模板 函数模板 template <typename T> T myMax( ...

  5. html解析器:Html Agility Pack

    去掉注释.样式.和js代码: foreach(var script in doc.DocumentNode.Descendants("script").ToArray()) scr ...

  6. spring的jar包的下载、说明

    spring的jar包官方下载地址:完整链接:https://repo.spring.io/webapp/#/artifacts/browse/tree/General/libs-release-lo ...

  7. Lockey的沙雕低错集锦(未完待续~自己提升用)

      (嘿嘿 (^~ ^)) 1. bitset<2100>a[2100]    a[x]|=(1<<x)   (1<=x<=2100)  使用时忘了x的范围,额,应 ...

  8. TLS示例开发-golang版本

    目录 前言 制作自签名证书 CA 服务器证书相关 客户端证书相关 证书如何验证 在浏览器中导入证书 导入证书 修改域名 golang服务端 目录 main.go 测试 参考 前言 在进行项目总结的时候 ...

  9. 【基础算法-模拟-例题-*校长的问题】-C++

    为什么在题目前面打上星号呢? 这道题的正解不是模拟! 正解树状数组! 正解树状数组! 正解树状数组! 重要的事情说够三遍了! 但是,歪解模拟因为数据水都能AC! 因为这道题放在模拟专题中,所以我们就讨 ...

  10. CNN神经网络之卷积操作

    在看这两个函数之前,我们需要先了解一维卷积(conv1d)和二维卷积(conv2d),二维卷积是将一个特征图在width和height两个方向进行滑动窗口操作,对应位置进行相乘求和:而一维卷积则只是在 ...