2.线程的安全性

2.1什么是线程安全

在多个线程访问的时候,程序还能“正确”,那就是线程安全的。

无状态(可以理解为没有字段的类)的对象一定是线程安全的。

2.2 原子性

典型的例子,多线程状态下的i++是不安全的。因为i++其实是分很多步骤实现的,多个线程的执行过程可能会相互混乱。

竞态条件(Race Conditions)

线程与线程之间需要依赖于执行顺序来保证执行结果的正确性。那么就会发生竞态条件。例如在A线程中设置一个值,然后通过另外一信号变量通知给另外一个线程来读。这种协调是非常错误的。

关于重排序,参考这里:http://ifeve.com/java-memory-model-2/

最常见的例子:“先检查后执行”

在非同步的多线程环境下,你所看到的不一定是真实的,因为下一秒就可能已经不是这样的了!

书中举了个例子:

你和朋友约好了在某个地方的星巴克见面,但是去了之后才发现有两家星巴克,A和B,你在星巴克A里没有发现朋友,那么此时你的朋友可能:

1,在星巴克B家

2,在星巴克A与B的路上

如果你离开星巴克A,去找别的地方找,那么:你离开星巴克A的大门的下一秒,你手头的“朋友不在星巴克A”的信息就有可能失效!,因为在你从正门出去的下一秒,你的朋友就可能从后门进来了

2.3 加锁机制

synchronize关键字表示内置锁,它根据当前的使用位置来自动创建一个锁对象,因此在使用的时候不用明确地指出用什么锁。

在普通成员方法中的synchronize表示用this表示锁对象

而在静态方法中的synchronize表示用class来作为锁对象

参考:https://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.4.3.6

重入

同步方法的递归和子类调用父类方法时是可以重入的。不会产生死锁。

重入的实现机制:

在锁的信息中有这样的信息

{thread01,1}

表示thread01获取了1次这个锁。当thread01需要再次获取锁时,记录如下:

{thread01,2}

在方法执行完毕之后,锁数量会往下减,当数量为0时则释放该锁。

3.对象的共享

3.1 可见性

怎样让线程A的行结果对线程B可见? 加锁!

用锁来保证一个写、另外一个读按期望的顺序执行,才能实现写的结果会被读到。仅靠代码执行顺序是不行的!

volatile变量

Java提供的一种同步机制,用来保证volatile修饰的变量,在一个线程中被修改之后,另外的线程也能看到。

内部机制是volatile变量不会进行重排也不会缓存到寄存器。

注意事项:

1.volatile只保证可见性,但不保证原子性,所以不要应用于多线程“写”的场景中

2.多个volatile其实单独的,不构成整体同步

使用volatile时一定要特别小心,一般用于简单的信号传递,稍微复杂一点儿的还是用锁吧:

  1. public class VolatileTest2 {
  2.  
  3. public static void main(String[] args) {
  4. VolatileTest2 test = new VolatileTest2();
  5.  
  6. test.start();
  7. try {
  8. Thread.sleep(5000);
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. }
  12. test.stop();
  13.  
  14. }
  15.  
  16. volatile boolean stop = false;
  17.  
  18. public void start() {
  19. new Thread(new Runnable() {
  20. @Override
  21. public void run() {
  22. while(!stop) {
  23. System.out.println("running...");
  24. try {
  25. Thread.sleep(1000);
  26. } catch (InterruptedException e) {
  27. e.printStackTrace();
  28. }
  29. }
  30. }
  31. }).start();
  32. }
  33.  
  34. public void stop() {
  35. stop = true;
  36. }
  37.  
  38. }

3.2 发布与逸出(Escape)

你需要弄清楚,那些对外公开的数据是否真的需要对外公开,因为一旦公开,外部就可以访问,那么你就不得不考虑多线程的问题了。

另外一些时候,你可能会不小心把数据对外公开了,比如在构造函数中创建匿名内部类,或者启动一个线程。不要这么做的原因是,构造函数函数中会把this暴露给内部类或者线程。而“this”本身其实还可能没ready好呢!

(解决办法:使用工程方法;先定义好线程,另外增加一个方法来start这个线程)

3.3 线程封闭

当线程不需要进行数据共享,那么把数据封闭在线程中,是保证线程安全很好的办法。程序设计中应该避免数据逸出到其他线程中。

比如可以使用局部变量或者ThreadLocal来存储线程数据。

ad-hoc线程封闭

就是指完全由程序来实现线程间数据的封闭,不要这样做。

栈封闭

可以理解为基本类型的局部变量是封装在执行的栈里的,不会破坏栈的封闭性。

ThreadLocal

它是一个实现线程封闭的很好的工具类。可以把ThreadLocal<T>看成Map<Thread,Object>,但是它底层的实现其实不是这样的。ThreadLocal的数据实际存储在了关联的Thread对象中。在Thread结束时,这些数据会被回收掉。

注意:不是所有全局参数都需要存储在ThreadLocal中,有人甚至把函数调用的参数都存在这里,这样做会导致程序可读性降低,增加程序耦合度,不便于维护。

3.4 不变性

不可变的数据一定是线程安全的,所以在避免数据状态出问题时,可以想办法让它的状态不可变(如果能不变)。

使用final+volatile实现线程安全

final的特点

Java并发编程实践读书笔记(1)线程安全性和对象的共享的更多相关文章

  1. Java并发编程实践读书笔记(5) 线程池的使用

    Executor与Task的耦合性 1,除非线程池很非常大,否则一个Task不要依赖同一个线程服务中的另外一个Task,因为这样容易造成死锁: 2,线程的执行是并行的,所以在设计Task的时候要考虑到 ...

  2. Java并发编程实践(读书笔记) 任务执行(未完)

    任务的定义 大多数并发程序都是围绕任务进行管理的.任务就是抽象和离散的工作单元.   任务的执行策略 1.顺序的执行任务 这种策略的特点是一般只有按顺序处理到来的任务.一次只能处理一个任务,后来其它任 ...

  3. Java并发编程实践读书笔记(2)多线程基础组件

    同步容器 同步容器是指那些对所有的操作都进行加锁(synchronize)的容器.比如Vector.HashTable和Collections.synchronizedXXX返回系列对象: 可以看到, ...

  4. Java并发编程实践读书笔记(4)任务取消和关闭

    任务的取消 中断传递原理 Java中没有抢占式中断,就是武力让线程直接中断. Java中的中断可以理解为就是一种简单的消息机制.某个线程可以向其他线程发送消息,告诉你“你应该中断了”.收到这条消息的线 ...

  5. Java并发编程实践读书笔记(3)任务执行

    类似于Web服务器这种多任务情况时,不可能只用一个线程来对外提供服务.这样效率和吞吐量都太低. 但是也不能来一个请求就创建一个线程,因为创建线程的成本很高,系统能创建的线程数量是有限的. 于是Exec ...

  6. [Java 并发] Java并发编程实践 思维导图 - 第四章 对象的组合

    依据<Java并发编程实践>一书整理的思维导图. 第一部分: 第二部分:

  7. Java并发编程实战 读书笔记(二)

    关于发布和逸出 并发编程实践中,this引用逃逸("this"escape)是指对象还没有构造完成,它的this引用就被发布出去了.这是危及到线程安全的,因为其他线程有可能通过这个 ...

  8. 读书笔记-----Java并发编程实战(一)线程安全性

    线程安全类:在线程安全类中封装了必要的同步机制,客户端无须进一步采取同步措施 示例:一个无状态的Servlet @ThreadSafe public class StatelessFactorizer ...

  9. 《Java并发编程实战》第二章 线程安全性 读书笔记

    一.什么是线程安全性 编写线程安全的代码 核心在于要对状态訪问操作进行管理. 共享,可变的状态的訪问 - 前者表示多个线程訪问, 后者声明周期内发生改变. 线程安全性 核心概念是正确性.某个类的行为与 ...

随机推荐

  1. let 和const与var的不同

    1. let的作用域在代码块中仅限在当前的作用于中 { var str1 = 12; console.log(str1); let str2 = 20; console.log(str2); //20 ...

  2. spring @Autowired和jdk的@Resource区别

    当一个接口只有一个实例时,使用这两个注解的效果是一样的. 当含有两个实例时,非得使用 @Autowired 那么定义的引用类型必须和service实现类定义的名字相同,参照下图 定义第一个servic ...

  3. Python之matplotlib学习(三)

    例子11-1:横坐标时间的处理 from matplotlib.dates import datestr2num,DateFormatter import matplotlib.dates as da ...

  4. 前端框架对于未来web移动端的影响

    现在前端框架市场比较乱,各种各样的框架参差不齐,这给我带来了很多困惑,同样是很多朋友的困惑吧!因为前端框架有很多种,对于程序员来说选择学习是非常困难的,不可能有几十上百种都要学习吧,不过最好的办法就是 ...

  5. 1)C语言简介(C自考学习)

    C语言历史由来 世界上第一个高级语言是"ALFOL",而C的前身是ALGOL语言.1970年美国贝尔实验室的肯·汤普逊对BCPL(基本复合程序设计语言)进行了进一步的简化,突出了硬 ...

  6. 老男孩Python全栈开发(92天全)视频教程 自学笔记03

    day3课程目录: pyhton的历史 32bit和64bit系统的区别 Python版本的选择 第一个pyhton程序 文件后缀名及系统环境变量的介绍 pyhton程序的执行和其他编程语言的简单对比 ...

  7. Lucene入门-安装和运行Demo程序

    Lucene版本:7.1 一.下载安装包 https://lucene.apache.org/core/downloads.html 二.安装 把4个必备jar包和路径添加到CLASSPATH \lu ...

  8. RAC(ReactiveCocoa)使用方法(一)

    RAC(ReactiveCocoa)使用方法(一) RAC(ReactiveCocoa)使用方法(二) 什么是RAC? 最近回顾了一下ReactiveCocoa的方法,也看了一些人的文章,现写篇文章总 ...

  9. 从今天起开始记录下在freecodecamp学习的一些tip吧(所有内容都在这个随笔的评论里面记录)

    因为可能东西会很零碎 所以就放在随笔里吧 当需要在字符串中使用一个: " 或者 ' 时 可以通过在引号前面使用 反斜杠 (\) 来转义引号. var sampleStr = "Al ...

  10. thinkhphp 上传文件或者图片