本篇来谈谈 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 并发编程(四):如何保证对象的线程安全性的更多相关文章

  1. Java并发编程学习笔记(一)——线程安全性

    主要概念:线程安全性.原子性.原子变量.原子操作.竟态条件.复合操作.加锁机制.重入.活跃性与性能. 1.当多个线程访问某个状态变量并且其中有一个线程执行写入操作时,必须采用同步机制来协同这些线程对变 ...

  2. 【Java并发编程四】关卡

    一.什么是关卡? 关卡类似于闭锁,它们都能阻塞一组线程,直到某些事件发生. 关卡和闭锁关键的不同在于,所有线程必须同时到达关卡点,才能继续处理.闭锁等待的是事件,关卡等待的是其他线程. 二.Cycli ...

  3. java并发编程JUC第九篇:CountDownLatch线程同步

    在之前的文章中已经为大家介绍了java并发编程的工具:BlockingQueue接口.ArrayBlockingQueue.DelayQueue.LinkedBlockingQueue.Priorit ...

  4. Java并发编程 (四) 线程安全性

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 一.线程安全性-原子性-atomic-1 1.线程安全性 定义: 当某个线程访问某个类时,不管运行时环境 ...

  5. Java 并发编程(二)对象的不变性和安全的公布对象

    一.不变性 满足同步需求的还有一种方法是使用不可变对象(Immutable Object). 到眼下为止,我们介绍了很多与原子性和可见性相关的问题,比如得到失效数据.丢失更新操作或光查到某个对象处于不 ...

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

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

  7. Java并发编程原理与实战七:线程带来的风险

    在并发中有两种方式,一是多进程,二是多线程,但是线程相比进程花销更小且能共享资源.但使用多线程同时会带来相应的风险,本文将展开讨论. 一.引言 多线程将会带来几个问题: 1.安全性问题 线程安全性可能 ...

  8. Java 并发编程中的 Executor 框架与线程池

    Java 5 开始引入 Conccurent 软件包,提供完备的并发能力,对线程池有了更好的支持.其中,Executor 框架是最值得称道的. Executor框架是指java 5中引入的一系列并发库 ...

  9. 那些年读过的书《Java并发编程实战》一、构建线程安全类和并发应用程序的基础

    1.线程安全的本质和线程安全的定义 (1)线程安全的本质 并发环境中,当多个线程同时操作对象状态时,如果没有统一的状态访问同步或者协同机制,不同的线程调度方式和不同的线程执行次序就会产生不同的不正确的 ...

随机推荐

  1. 利用python爬虫关键词批量下载高清大图

    前言 在上一篇写文章没高质量配图?python爬虫绕过限制一键搜索下载图虫创意图片!中,我们在未登录的情况下实现了图虫创意无水印高清小图的批量下载.虽然小图能够在一些移动端可能展示的还行,但是放到pc ...

  2. RabbitMQ原理介绍

    RabbitMQ历史 RabbitMQ消息系统是一个由erlang开发的AMQP(Advanced Message Queue )的开源实现.在同步消息通讯的世界里有很多公开标准(如COBAR的IIO ...

  3. QQ第三方授权登录OAuth2.0实现(Java)

    准备材料 1.已经备案好的域名 2.服务器(域名和服务器为统一主体或域名已接入服务器) 3.QQ号 4.开发流程:https://wiki.connect.qq.com/%E5%87%86%E5%A4 ...

  4. Ubuntu部署Tomcat Web服务

    在Ubuntu平台中安装TomCat 本文将为大家介绍TomCat在Ubuntu平台中如何进行部署使用,带你快速入门使用TomCat TomCat简介 Tomcat是Apache 软件基金会(Apac ...

  5. Nebula 架构剖析系列(一)图数据库的存储设计

    摘要 在讨论某个数据库时,存储 ( Storage ) 和计算 ( Query Engine ) 通常是讨论的热点,也是爱好者们了解某个数据库不可或缺的部分.每个数据库都有其独有的存储.计算方式,今天 ...

  6. Python 3.8.0 正式版发布,新特性初体验

    北京时间 10 月 15 日,Python 官方发布了 3.8.0 正式版,该版本较 3.7 版本再次带来了多个非常实用的新特性. 赋值表达式 PEP 572: Assignment Expressi ...

  7. numpy.random.rand

    numpy.random.rand(d0,d1,...,dn ) 给定形状中的随机值. 创建一个给定形状的数组,并用统一分布的随机样本填充它.[0, 1) 参数: d0,d1,...,dn:int,可 ...

  8. RF中for循环

    robotframework支持FOR循环语句,语法和Python的语法基本相同,但robotframework中,“FOR”关键字前面需要增加一个“:”,写成“:FOR”,其它与Python的语法相 ...

  9. Ubuntu 19.10 发布 | 云原生生态周报 Vol. 24

    作者 | 木苏.进超.冬岛.元毅.心水.衷源 业界要闻 1.云原生编程语言 Pulumi 1.0 pulumi ,一款中立的开源云开发平台,Pulumi 支持多语言.混合云环境.完全可扩展.初期支持 ...

  10. mysql 二十余种报错注入姿势

    1.floor()  floor 和group by 搭配使用 利用group by 的key唯一性 和mysql 编码执行顺序导致二次执行产生不同key ))) ) 数值型注入时 不用闭合‘  进行 ...