Effective Java 第三版——82. 线程安全文档化

Tips
书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code
注意,书中的有些代码里方法是基于Java 9 API中的,所以JDK 最好下载 JDK 9以上的版本。

Effective Java 第三版——82. 线程安全文档化
  1. 线程安全文档化

当并发使用一个类的方法时,类的行为方式是其与客户端建立约定的重要部分。如果未能文档化记录某个类行为的这一方面,其用户只能做出做出假设。如果这些假设是错误的,则生成的程序可能执行同步不够(条目 78)或过度同步(条目 79)。 无论哪种情况,都可能导致严重错误。

你可能听说过,可以通过在方法的文档中查找synchronized修饰符来判断该方法是否线程安全的。这在几个方面来讲是错误的。在正常操作中,Javadoc的输出中没有包含synchronized修饰符,这是有原因的。 方法声明中synchronized修饰符的存在是实现细节,而不是其API的一部分。它不能可靠地说明方法是是线程安全的。

此外,声称存在synchronized修饰符就足以文档记录线程安全性,这体现了线程安全性是要么全有要么全无属性的误解。实际上,线程安全有几个级别。 要启用安全的并发使用,类必须清楚地文档记录它支持的线程安全级别。下面的列表总结了线程安全级别。它并非详尽无遗,但涵盖以下常见情况:

  • 不可变的(Immutable) —— 该类的实例看起来是不变的(constant)。不需要外部同步。示例包括String、Long和BigInteger(条目 17)。
  • 无条件线程安全(Unconditionally thread-safe) —— 此类的实例是可变的,但该类具有足够的内部同步,以便可以并发使用其实例而无需任何外部同步。 示例包括AtomicLong和ConcurrentHashMap。
  • 有条件线程安全(Conditionally thread-safe) —— 与无条件线程安全一样,但某些方法需要外部同步以便安全并发使用。 示例包括 Collections.synchronized包装器返回的集合,其迭代器需要外部同步。
  • 非线程安全(Not thread-safe) —— 这个类的实例是可变的。 要并发使用它们,客户端必须使用其选择的外部同步来包围每个方法调用(或调用序列)。 示例包括通用集合实现,例如ArrayList和HashMap。
  • 线程对立(Thread-hostile) —— 即使每个方法调用都被外部同步包围,该类对于并发使用也是不安全的。线程对立通常是由于在不同步的情况下修改静态数据而导致的。没有人故意编写线程对立类;此类通常是由于没有考虑并发性而导致的。当发现类或方法与线程不相容时,通常将其修正或弃用。条目 78中的 generateSerialNumber方法在没有内部同步的情况下是线程对立的,如第322页所述。

这些分类(除了线程对立)大致对应于《Java Concurrency in Practice》一书中的线程安全注解,分别是Immutable,ThreadSafe和NotThreadSafe [Goetz06,附录A]。 上述分类中的无条件和条件线程安全类别都包含在ThreadSafe注解中。

在文档记录了一个有条件的线程安全类需要小心。 你必须指明哪些调用序列需要外部同步,以及必须获取哪个锁(或在极少数情况下是几把锁)才能执行这些序列。 通常是实例本身的锁,但也有例外。 例如,Collections.synchronizedMap的文档说明了这一点:

It is imperative that the user manually synchronize on the returned map when iterating over any of its collection views:
当迭代任何Map集合的视图时,用户必须手动同步返回的Map:

Map<k, v> m = Collections.synchronizedMap(new HashMap<>());
Set<k> s = m.keySet();  // Needn't be in synchronized block
    ...

synchronized(m) {  // Synchronizing on m, not s!

    for (K key : s)
        key.f();
}
</k></k,>

不遵循此建议可能会导致不确定性行为。

类的线程安全性的描述通常属于类的文档注释,但具有特殊线程安全属性的方法应在其自己的文档注释中描述这些属性。 没有必要记录枚举类型的不变性。 除非从返回类型中显而易见,否则静态工厂必须在文档中记录返回对象的线程安全性,如Collections.synchronizedMap(上文)所示。

当类承诺使用可公开访问的锁时,它允许客户端以原子方式执行一系列方法调用,但这种灵活性需要付出代价。 它与并发集合(如ConcurrentHashMap)使用的高性能内部并发控制不兼容。 此外,客户端可以通过长时间保持可公开访问的锁来发起拒绝服务攻击。 这可能是偶然也可能是故意的。

要防止此拒绝服务攻击,可以使用私有锁对象而不是使用synchronized方法(这隐含着可公开访问的锁):

// Private lock object idiom - thwarts denial-of-service attack
private final Object lock = new Object();

public void foo() {
    synchronized(lock) {
        ...

    }
}

由于私有锁对象在类外是不可访问的,因此客户端不可能干扰对象的同步。 实际上,我们通过将锁定对象封装在它同步的对象中来应用条目 15的建议。

请注意,锁定属性(lock field)被声明为final。 这可以防止无意中更改其内容,从而导致灾难性的非同步访问(条目 78)。 我们通过最小化锁定属性(lock field)的可变性来应用条目 17的建议。 锁定属性(lock field)应始终声明为final。 无论使用普通的监视器锁(如上所示)还是使用java.util.concurrent.locks包中的锁,都是如此。

私有锁对象习惯用法只能用于无条件线程安全类。 有条件线程安全类不能使用这个习惯用法,因为它们必须文档记录在执行某些方法调用序列时客户端要获取的锁。

私有锁对象习惯用法特别适合用于为继承设计的类(条目 19)。 如果这样的类要使用其实例进行锁定,则子类可能容易且无意地干扰基类的操作,反之亦然。 通过为不同的目的使用相同的锁,子类和基类可能最终”踩到彼此的脚趾。”这不仅仅是一个理论问题;它就发生在Thread类上[Bloch05,Puzzle 77]中。

总之,每个类都应该用措辞严谨的描述或线程安全注解清楚地文档记录其线程安全属性。synchronized修饰符在本文档中没有任何作用。条件线程安全类必须文档记录哪些方法调用序列需要外部同步,以及在执行这些序列时需要获取哪些锁。如果你编写一个无条件线程安全的类,请考虑使用一个私有锁对象来代替同步方法。这将保护免受客户端和子类的同步干扰,并提供更大的灵活性,以便在后续的版本中采用复杂的并发控制方法。

Original: https://www.cnblogs.com/IcanFixIt/p/10643350.html
Author: 林本托
Title: Effective Java 第三版——82. 线程安全文档化

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/537088/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Java序列化与反序列化(实践)

    基本概念:序列化是将对象状态转换为可保持或传输的格式的过程。与序列化相对的是反序列化,它将流转换为对象。这两个过程结合起来,可以轻松地存储和传输数据。 昨天在一本书上看到了,好好实…

    Java 2023年5月29日
    045
  • vue3对比vue2获取通过refs获取组件数据

    vue2 1、组件设置 ref 标识 <van-calendar ref="calendarRef"> </van-calendar> …

    Java 2023年6月8日
    069
  • spring mvc 实战化项目之三板斧

    laravel实战化项目之三板斧 spring mvc 实战化项目之三板斧 asp.net mvc 实战化项目之三板斧 接上文希望从一张表(tb_role_info 用户角色表)的…

    Java 2023年5月30日
    0103
  • 虚拟机vmware实现共享文件夹hgfs

    VMware中的centos共享目录/mnt/hgfs 首先,在vmware上手动配置 执行命令 sudo vmhgfs-fuse .host:/ /mnt/hgfs -o non…

    Java 2023年5月30日
    081
  • [Java编程思想] 第七章 复用类

    第一种方法非常直观:只需在新的类中产生现有类的对象(组合)。第二种方法更细致一些:它按照现有类的类型来创建新类(继承)。 7.1 组合语法 只需将对象引用置于新类中即可。 clas…

    Java 2023年6月5日
    077
  • spring cache之自定义keys的过期时间

    spring @cacheable注解默认不支持方法级别的缓存失效时间,只能通过配置来配置全局的失效时间 如果需要实现对方法级别的缓存支持失效时间机制,有一种比较简单的方法,spr…

    Java 2023年6月16日
    071
  • Spring(二):IoC理论推导

    在Spring的简介中我们知道了Spring的核心是控制反转(IoC:Inverse of Control)和面向切面编程(AOP:Aspect Oriented Programm…

    Java 2023年6月15日
    068
  • Linux常用文件管理命令详解

    cat cat命令用于连接文件并打印到标准输出设备上。 命令语法: cat [&#x53C2;&#x6570;] [&#x6587;&#x4EF6;…

    Java 2023年6月7日
    065
  • Css3入门详解

    一、Css基本语法 1.Html和Css没分开 点击查看代码 <!DOCTYPE html> <html lang="en"> <…

    Java 2023年6月13日
    069
  • sharepoint 2013 “The module … owssvr.dll could not be loaded due to a configuration problem”

    打开sharepoint站点可以看到这个503的错误, 在event viewer中查看如下: The Module DLL ‘C:\Program Files\Com…

    Java 2023年6月7日
    058
  • Golang多线程垂直输出字符串

    [本文出自天外归云的博客园] 三个字符串,abc,def,ghi,请用多线程顺序输出:adg,beh,cfi 抛砖引玉,我的代码如下: go;gutter:true; packag…

    Java 2023年5月29日
    071
  • 【Java】【51】Quartz定时器

    前言: 但是,有的时候我们的任务是动态的。比如,可以在后台添加任意个数任意时间的推送短信任务,任务没有开始之前,可以更改推送时间。这就需要用到Quartz动态添加、修改和删除定时任…

    Java 2023年5月29日
    057
  • 设计模式 16 命令模式

    命令模式(Command Pattern)属于 行为型模式 概述 现在各大电子厂商都在推智能家居,即可以通过手机这一个终端控制多个家用电器,比之前的单个设备智能由对应遥控器控制的方…

    Java 2023年6月6日
    073
  • 用font Awesome 制作简单的博客园icon

    其中就有不少Brand Icon 像微信、微博。但是没有在Brand Icon里面找到博客园的Icon。 倒是在Web Application Icons 里找到了 fa-rss-…

    Java 2023年6月5日
    089
  • CocosCreator中worldMatrix到底是什么(下)

    Cocos Creator 中 _worldMatrix 到底是什么(下) 1. 摘要 上篇介绍了矩阵的基本知识以及对应图形变换矩阵推倒。中篇具体介介绍了对应矩阵转换成cocos …

    Java 2023年6月13日
    077
  • C语言求100以内的和的4种方式

    C语言的一个很经典的例子,帮助熟练运行几个循环的写法 方法一(do—while语句) #include main () { int i,sum=0; do { sum=…

    Java 2023年6月9日
    088
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球