[本文是我对Java Concurrency In Practice第二章的归纳和总结,  转载请注明作者和出处,  如有谬误, 欢迎在评论中指正. ]

多线程环境下,无需调用方进行任何同步处理也能保证正确性的类是线程安全的类

无状态的对象是线程安全的。无状态是指没有成员变量。由于方法的局部变量都是在线程私有的栈中分配的,因此在一个线程中调用无状态对象的方法,不会影响到其他线程。

race condition: 正确性依赖于事件发生的相对时间。

check-and-act是race condition中的一种,指的是基于check的结果进行操作。由于check和act并非是原子的,进行act时check的结果可能已经无效,那么基于check所进行的act就可能带来问题。

见如下的lazy单例类:

public class LazyInitRace {private static ExpensiveObject instance = null;public static ExpensiveObject getInstance() {// if语句是一个check-and-act操作if (instance == null) {instance = new ExpensiveObject();}return instance;}
}

这是一个check-and-act的典型例子:首先判断instance是否为null,如果是就创建ExpensiveObject对象,否则直接返回instance。但是判断和创建并非原子操作,假设线程1判断出instance为null,要是另一个线程紧接着创建了ExpensiveObject对象,那么线程1的判断就失效了,基于判断结果所进行的创建操作就会导致程序中存在多个ExpensiveObject对象--这违背了单例模式的初衷。

Read‐modify‐write也是race condition中的一种,指的是读取某个变量的值,修改后写回。这显然不是一个原子操作,如果B线程在A线程read之后write之前修改了变量的值,那么A线程read的结果就失效了,基于read所做的modify就可能带来问题。

见如下的servlet:

public class CountingFactorizer implements Servlet {private final long count = 0;public void service(ServletRequest req, ServletResponse resp) {BigInteger i = extractFromRequest(req);BigInteger[] factors = factor(i);// // 自增语句是一个Read‐modify‐write操作count++;encodeIntoResponse(resp, factors);}
}

使用java提供的同步机制,将check-and-act或者Read‐modify‐write转换为原子操作,则可以避免race condition造成的错误。

使用同步机制改进LazyInitRace和CountingFactorizer类,使之成为线程安全的类:

public class LazyInitRace {private static ExpensiveObject instance = null;public static ExpensiveObject getInstance() {// 使用synchronized将check-and-act操作转换为原子操作if (instance == null) {synchronized (LazyInitRace.class) {if (instance == null) {instance = new ExpensiveObject();}}}return instance;}
}public class CountingFactorizer implements Servlet {private final AtomicLong count = new AtomicLong(0);public void service(ServletRequest req, ServletResponse resp) {BigInteger i = extractFromRequest(req);BigInteger[] factors = factor(i);// 使用long对应的原子类AtomicLong,将Read‐modify‐write操作转换为原子操作count.incrementAndGet();encodeIntoResponse(resp, factors);}
}

对于只有一个成员变量的对象,成员的状态就是对象的状态。如果涉及到该成员的操作都是原子的,那么该类就是一个线程安全的类。比如以上的LazyInitRace类,其只有一个成员变量instance,且涉及instance的操作是原子的,因此LazyInitRace类是一个线程安全的类。

是否可以类推出,只要类的每一个成员变量的操作都是原子的,类就是线程安全的?不行。

因为每一个成员的操作都是原子的,不能保证所有涉及到成员的操作整体上是原子的。例如:

public class UnsafeCachingFactorizer implements Servlet {private final AtomicReference<BigInteger> lastNumber = new AtomicReference<BigInteger>();private final AtomicReference<BigInteger[]> lastFactors = new AtomicReference<BigInteger[]>();public void service(ServletRequest req, ServletResponse resp) {BigInteger i = extractFromRequest(req);// 优先使用缓存if (i.equals(lastNumber.get()))encodeIntoResponse(resp, lastFactors.get() );else {BigInteger[] factors = factor(i);// 将最后一次的计算结果缓存起来lastNumber.set(i);lastFactors.set(factors);encodeIntoResponse(resp, factors);}}
}

UnsafeCachingFactorizer类的2个成员lastNumber和lastFactors的set()和get()方法是原子的,但是该类不是线程安全的。因为从整体上看,2次写入和2次读取都不是同时进行的,UnsafeCachingFactorizer类仍然存在race condition.

java的synchronized机制使用的锁是可重入锁,即同一个线程可以多次申请持有同一把锁而不会引起死锁。假设A线程持有lock,那么如果B线程申请lock锁,B线程就会被阻塞。但是如果A线程再次申请已经持有的lock锁,该申请将获得通过,这就是所谓的同一线程可多次获取同一把锁。作为锁的对象,不仅需要标识锁的所有者,也需要标识所有者持有的计数。如果所有者线程刚刚申请到锁,则计数器的值为1,每重新获取一次,计数器的值加1,每退出一个同步代码块,计数器的值减1. 当计数器的值减为0时,所有者线程才释放锁。可重入锁的设计是为了防止因申请已持有的锁而造成死锁,例如:

public class Widget {public synchronized void doSomething() {...}
}
public class LoggingWidget extends Widget {public synchronized void doSomething() {System.out.println(toString() + ": calling doSomething");super.doSomething();}
}

如果java中的锁不是可重入的,那么调用LoggingWidget对象的doSomething方法就会导致死锁。

Race condition--Java Concurrency In Practice C02读书笔记相关推荐

  1. 《Java Concurrency in Practice》中文版笔记

    第1章 简介 1.1 并发简史 茶壶和面包机的生产商都很清楚:用户通常会采用异步方式来使用他们的产品,因此当这些机器完成任务时都会发出声音提示. 1.2 线程的优势 线程能够将大部分的异步工作流转换成 ...

  2. 《Java 并发编程实战》--读书笔记

    Java 并发编程实战 注: 极客时间<Java 并发编程实战>–读书笔记 GitHub:https://github.com/ByrsH/Reading-notes/blob/maste ...

  3. 《Java多线程编程核心技术》读书笔记

    为什么80%的码农都做不了架构师?>>>    <Java多线程编程核心技术>读书笔记. ###第一章 Java多线程技能 使用Java多线程两种方式. 继承Thread ...

  4. JAVA WEB整合开发王者归来 -- 读书笔记 by CZF 完整版

    JAVA WEB整合开发王者归来 -- 读书笔记  目录 第1章 概述. 1 第2章 搭建web开发环境. 1 第3章 Servlet技术. 1 第4章 深入JSP技术. 7 第5章 会话跟踪. 12 ...

  5. 单麦降噪经典书籍《Speech enhancement: theory and practice》读书笔记(第5章)

    目录 前言 第5章 谱减算法 5.1 谱减的基本原理 5.2 谱减的几何分析 5.2.1 带噪信号与纯净信号相位差的上限 5.2.2 不同的谱减形式及理论局限 5.3 谱减法的缺点 5.4 谱减法中使 ...

  6. 单麦降噪经典书籍《Speech enhancement: theory and practice》读书笔记(第1章~第4章)

    目录 前言 第1章 引言 1.1 了解噪声 1.2 语音增强算法分类 第3章 语音产生与感知 3.2 语音产生过程 3.2.1 肺 3.2.2 喉与声带 3.2.3 声道 3.3 语音产生的工程模型 ...

  7. Java Concurrency In Practice

    线程安全 定义 A class is thread-safe if it behaves correctly when accessed from multiple threads, regardle ...

  8. 《Java Concurrency in Practice》之可见性(Visibility)

    原文:Visibility is subtle because the things that can go wrong are so counterintuitive.译文:可见性是微妙的,因为可能 ...

  9. 《Java Concurrency in Practice》之原子性(Atomicity)

    假定有两个操作A和B,如果从执行A的线程来看,当另一个线程执行B时,要么将B全部执行完,要么完全不执行B,那么A和B对彼此来说是原子的.原子操作是指,对于访问同一个状态的所有操作(包括该操作本身)来说 ...

最新文章

  1. caffe的python接口学习(6):用训练好的模型(caffemodel)来分类新的图片
  2. 【剑指offer-Java版】33把数组排成最小的数
  3. fetch使用的常见问题及解决办法
  4. Echarts渲染选择SVG /canvas
  5. 在CentOS上安装Python3的三种方法
  6. kafka简介(大数据技术)
  7. jl1.如何设置元素的宽高包含元素的边框和内边距
  8. 我要做 Android 之 Service
  9. 2014牡丹江——Hierarchical Notation
  10. iphone:使用NSFileManager取得目录下所有文件(遍历所有文件)
  11. 分布式架构之缓存系统
  12. golang,break跳出循环的例子以及随机数生成
  13. XML (4) XSL教程
  14. Social Media附加价值开发的四大模式
  15. AMPL-最短路选择问题
  16. VOSviewer 操作指南 简明
  17. matlab 平方根法解方程组,matlab改进平方根法
  18. 网络基础-应用层:E-mail应用:SMTP协议,POP协议,IMAP协议
  19. 工作缺点和不足及措施_【工作中存在的问题和不足及改进措施】_工作中的不足与改进_工作中不足及改进措施...
  20. DSPE-PEG-TAT,磷脂-聚乙二醇-靶向穿膜肽TAT,一种磷脂PEG肽

热门文章

  1. CentOS Rescure救援模式恢复数据记录
  2. Elastic-Job简介和理解
  3. OC 血压计 蓝牙BLE4.0 链接踩坑开发
  4. Java学习-使用文本编辑器编辑Java源代码
  5. 什么是英雄?撒贝宁满分回答我要背下来
  6. 2020年全球及中国动物模型(模式动物)行业发展现状及竞争格局分析,行业增速较快,发展前景良好「图」
  7. 移动网站性能优化:网页加载技术概览
  8. Android Weekly #38 :Android 13 正式发布
  9. oracle忘记某个用户密码,Oracle忘记用户名密码怎样恢复
  10. 基金委发布项目申报指南