什么是线程安全性:

要编写线程安全的代码,其核心在于要对状态访问操作进行管理,特别是对共享的和可变的状态的访问。“共享”意味着变量可以由多个线程同时访问,而“可变”则意味着变量的值在其生命周期内可以发生变化。

原文出处:http://liuxp0827.blog.51cto.com/5013343/1412874

一个对象是否需要线程安全的,取决于他是否被多个线程访问。这指的是在程序中访问对象的方式,而不是对象要实现的功能。要使得对象时线程安全的,需要采用同步机制来协同对对象可变状态的访问。如果无法实现协同,那么可能导致数据破坏以及其他不该出现的结果。

如果当多个线程访问同一个可变的状态变量时没有使用合适的同步,那么程序就会出现错误。有三种方式可以修复这个问题:1.不在线程之间共享该状态变量;2.将状态变量修改为不可变的变量;3.在访问状态变量时使用同步。

在线程安全性的定义中,最核心的概念就是正确性。正确性的含义是,某个类的行为与其规范完全一致。当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么称这个类是线程安全的。

来看一个基于Servlet的因数分解服务,并逐渐扩展它的功能,同时确保它的线程安全性。{}

//原文出处:http://liuxp0827.blog.51cto.com/5013343/1412874
@ThreadSafe
public class StatelessFactorizer implements Servlet {public void service (ServletRequest req,ServletResponse resp) {BigInteger i = extractFromRequest(req);BigInteger[] factors = factor(i);encodeIntoResponse(resp,factors);}
}

与大多数Servlet一样,StatelessFactorizer是无状态的,它不包含任何域,也不包含任何对其他类中域的引用。计算过程中的临时状态仅存在于线程栈上的局部变量中,并只能有正在执行的线程访问。因此无状态对象一定是线程安全的。

原子性


假设我们希望增加一个“命中计数器”来统计所处理得请求数量。一种直观的方法是在Servlet中增加一个long类型的域,并且每处理一个请求就将这个值加1:

//原文出处:http://liuxp0827.blog.51cto.com/5013343/1412874
@NotThreadSafe                  //不好的代码
public class UnsafeCountingFactorizer implements Servlet {private long counts = 0;                                                           public long getCounts(){return counts;}  public void service (ServletRequest req,ServletResponse resp) {BigInteger i = extractFromRequest(req);BigInteger[] factors = factor(i);++counts;encodeIntoResponse(resp,factors);}
}

UnsafeCountingFactorizer是非线程安全的。虽然递增造作++counts是一种紧凑的语法,使其看上去只是一个操作,但这个操作并非原子的,因而他并不会作为一个不可分割的操作来执行。它包含了三个独立的操作“读取counts ——> 修改counts+1 ——> 写入counts”。 假设计数器的初始值为9,那么在某些情况下,每个线程督导的值都是9,接着执行递增操作,并且都将计数器的值设为10,那么命中计数器的值将会偏差1.这种由不恰当的执行时序而出现的不正确的结果是一种竞态条件。

当某个计算的正确性取决于多个线程的交替执行时序时,那么就会发生竞态条件。最常见的竞态条件类型就是“先检查后执行”操作:通过一个可能失效的观测结果决定下一步的动作。

“先检查后执行”的一种常见情况就是延迟初始化,即将对象的初始化操作推迟到实际被使用时才进行,同时要确保只备初始化一次。

//原文出处:http://liuxp0827.blog.51cto.com/5013343/1412874
@NotThreadSafe
public class LazyInitRace {private ExpensiveObject instance = null;public ExpensiveObject getInstance() {if(instance == null)instance = new ExpensiveObject();return instance;}
}

在LazyInitRace中包含了一个竞态条件,它可能会破坏这个类的正确性。

LazyInitRace和UnsafeCountingFactorizer都包含一组需要以原子方式执行的操作。要避免竞态条件问题,就必须在某个线程修改该变量时,通过某种方式防止其他线程使用这个变量,从而确保其他线程只能在修改操作完成之前或之后读取和修改状态,而不是在修改状态的过程中。

如果UnsafeCountingFactorizer的递增操作是原子操作,那么竞态条件就不会发生。为了确保线程安全性,“先检查后执行”和“读取-修改-写入”等操作必须是原子的。我们把“先检查后执行”和“读取-修改-写入”等操作统称为复合操作:包含了一组必须以原子方式执行的操作以确保线程安全性。

//原文出处:http://liuxp0827.blog.51cto.com/5013343/1412874
@ThreadSafe
public class CountingFactorizer implements Servlet {private final AtomicLong counts = new AtomicLong(0);public long getCounts(){return counts.get();}public void service (ServletRequest req,ServletResponse resp) {BigInteger i = extractFromRequest(req);BigInteger[] factors = factor(i);counts.incrementAndGet();encodeIntoResponse(resp,factors);}
}

在实际情况下,应尽可能地使用现有的线程安全对象来管理类的状态。与非线程安全的对象相比,判断线程安全对象的可能状态及其状态转换情况要更为容易,从而也更容易维护和验证线程安全性。

加锁机制

当在Servlet中添加状态变量时,可以通过线程安全的对象来管理Servlet的状态以维护Servlet的线程安全性。但如果想在Servlet中添加更多的状态,只添加更多线程安全状态变量是不够的。我们希望提升Servlet的性能:将最近的计算结果缓存起来,dangliangge 连续的请求对相同的数值进行因数分解时,可以直接使用上一次的结果,而无需重新计算。这时,需要保存两个状态:最近执行因数分解的数值,以及分解结果。前面通过AtomicLong以线程安全的方式来管理计数器状态,是否可以使用类似的AtomicReference来管理最近执行因数分解的数值以及分解结果?

//原文出处:http://liuxp0827.blog.51cto.com/5013343/1412874
@NotThreadSafe
public class UnsafeFactorizer 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,lastNumber.get());else{BigInteger[] factors = factor(i);lastNumber.set(i);lastFactors.set(factors);encodeIntoResponse(resp,factors);}}

尽管这些原子引用本身都是线程安全的,但在UnsafeCachingFactorizer中存在着竞态条件,这可能产生错误的结果。

在线程安全性的定义中要求,多个线程之间的操作无论采用何种执行时序或交替方式,都要保证不变性不被破坏。UnsafeCachingFactorizer的不变性条件之一是:在lastFactors中的缓存的因数之积应该等于在lastNumber中的缓存值。当在不变性条件中涉及多个变量时,各个变量之间并不是彼此独立的,而是某个变量的值会对其他变量的值产生约束。因此,当更新某一个变量时,需要在同一个原子操作中对其他变量同时进行更新。

在上述代码中,尽管set方法的每次调用都是原子的,但仍然无法同时更新lastNumber和lastFactors。如果只修改了其中一个变量,那么在这两次修改操作之间,其他线程将发现不变性条件被破坏了。

所以,要保持状态的一致性,就需要在单原子操作中更新所有相关的状态变量。

未完待续... Java并发编程学习笔记(二)线程安全性 2

转载于:https://blog.51cto.com/liuxp0827/1412874

Java并发编程学习笔记(一)线程安全性 1相关推荐

  1. Java并发编程开发笔记——2线程安全性

    在构建稳健的并发程序时,必须正确地使用线程和锁.但这些终归只是一些机制.要编写线程安全的代码,其核心在于要对状态访问操作进行管理,特别是对共享的(Shared)和可变的(Mutable)状态的访问. ...

  2. 【并发入门】Java 并发编程学习笔记

    注:该笔记主要记录自 B站 up主 遇见狂神说的个人空间_哔哩哔哩_bilibili 1.什么是 JUC Java 工具类中的 并发编程包 学习:源码 + 官方文档 业务:普通的线程代码 Thread ...

  3. JAVA并发编程学习笔记之CAS操作

    http://blog.csdn.net/aesop_wubo/article/details/7537960 CAS操作 CAS是单词compare and set的缩写,意思是指在set之前先比较 ...

  4. Java并发编程学习笔记(二)多线程的理解及多线程的优点

    多线程的优点 原文:http://tutorials.jenkov.com/java-concurrency/benefits.html 作者:Jakob Jenkov        翻译:古圣昌   ...

  5. Java并发编程学习笔记——volatile与synchronized关键字原理及使用

    Java代码在编译后会变成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节码,最终需要转化为汇编指令在CPU上执行,Java中所使用的并发机制依赖于JVM的实现和CPU的指令. 一.vo ...

  6. JAVA并发编程学习笔记------FutureTask

    FutureTask是Future和Callable的结合体.传统的代码是这样写的 Future f = executor.submit(new Callable()); 然后通过Future来取得计 ...

  7. JAVA并发编程实践笔记

    2019独角兽企业重金招聘Python工程师标准>>> JAVA并发编程实践笔记 博客分类: java JAVA并发编程实践笔记 1, 保证线程安全的三种方法:     a, 不要跨 ...

  8. 《Java并发编程的艺术》——线程(笔记)

    文章目录 四.Java并发编程基础 4.1 线程简介 4.1.1 什么是线程 4.1.2 为什么要使用多线程 4.1.3 线程优先级 4.1.4 线程的状态 4.1.5 Daemon线程 4.2 启动 ...

  9. Java 并发编程——Executor框架和线程池原理

    Java 并发编程系列文章 Java 并发基础--线程安全性 Java 并发编程--Callable+Future+FutureTask java 并发编程--Thread 源码重新学习 java并发 ...

  10. java 网络编程学习笔记

    java 网络编程学习笔记 C/S模式:客户端和服务器 客户端创建流程 1 1.建立Socket端点 2 3 Socket s = new Socket(绑定地址, 绑定端口); 2.确认源数据方式和 ...

最新文章

  1. 二维码识别器PC版(电脑版)
  2. python编程入门p-读书笔记 - 《Python编程:从入门到实践》
  3. 【NOIP2015】【Luogu2661】信息传递(有向图最小环)
  4. CEO 赠书 | 甲之蜜糖乙之砒霜,创新者也将成为守旧者
  5. PHP ERROR_php中的异常和错误浅析
  6. AliOS Things 组件系统(uCube)
  7. cmd中输入net start mysql 提示:服务名无效
  8. Linux安装与基本操作命令与JDK的安装,Mysql的安装,Tomcat的安装
  9. EMNLP 2020 可解释性推理
  10. 桌面壁纸被计算机管理员禁用,Win7更改桌面壁纸时出现“此功能已被禁用”如何解决...
  11. SQL 列转行和动态用时间生数据列
  12. 圈粉无数!被称为B站“新垣结衣”的UP主,如何收获Z世代年轻人的喜爱?
  13. 目前文字识别技术,主要应用在哪些场景?
  14. 树莓派开发板Android Things镜像烧录
  15. Android 点击按钮切换图片
  16. (仿微信Android)IM聊天+抢红包+直播+朋友圈源码发布了
  17. 阅读《人类简史:从动物到上帝》笔记
  18. 前台页面与后台管理系统自动生成工具
  19. asp毕业设计——基于asp+sqlserver的选题管理系统设计与实现(毕业论文+程序源码)——选题管理系统
  20. Linux服务器安装git

热门文章

  1. 命令(Command)模式
  2. Windows Phone 7将加入复制粘贴功能
  3. 一个伤心人的帖和一群搞笑的跟帖
  4. [uva11174]村民排队 递推+组合数+线性求逆元
  5. 命令解析optparse
  6. 韩顺平的java入门到精通中serversql笔记(包括emp表和dept表,linux的mysql版)
  7. servlet跳转问题
  8. mysql常用sql命令
  9. php url地址栏传中文乱码解决方法集合
  10. oracle判断数字为复数,oracle学习笔记(十二) 查询练习(二) 高级查询