1-3章

作者: guideEmotion | 来源:发表于2019-08-22 12:20 被阅读0次

第二章 线程安全性

value++

看上去是单个操作,但它包含了三个独立的操作

  1. 读取value
  2. 将value加一
  3. 将结果写入value(赋值)

线程的风险

  1. 安全性问题(多个线程共享同一个变量,如果不处理,可能会出现一些不可预知的错误)
  2. 活跃性问题(单线程也有问题,关注的目标:某件正确的事情最终会发生。比如可能会出现无意造成的无线循环等等问题)
  3. 性能问题(线程调度、上下文切换等等)

线程安全类

最核心的概念就是正确性(某个类的行为和其规范完全一致)
当多个线程访问某个类时,这个类始终能表现出正确的行为,那么就称这个类是线程安全的

无状态(无状态的对象一定线程安全)

  1. 不包含任何域
  2. 也不包含任何对其他类中域的引用

竞态条件(Race Condition)

在并发编程中,由于不恰当的执行时序而出现不正确的结果是一种非常重要的情况。当某个计算的正确性取决于多个线程的交替执行时序时,那么就会发生竞态条件
例子:先检查后执行

原子方式(不可分割)

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

  1. 要保持状态的一致性,就需要在单个原子操作中更新所有相关的状态变量
  2. 对于可能被多个线程同时访问的可变状态变量,在访问它时都需要持有同一个锁。在这种情况下,我们称状态变量是由这个锁保护的
  3. 每个共享的和可变的变量都应该只由一个锁来保护

第三章 对象的共享

主要介绍如何共享和发布对象,从而使它们能够安全地由多个线程同时访问

synchronized

  1. 实现原子性和确定临界区
  2. 内存可见性

3.1可见性

static不保证可见性,多线程时可能会出问题

指令重排序

在没有同步的情况,编译器、处理器以及运行时等都可能对操作的执行顺序进行一些意想不到的调整。

非原子性的64位操作

  1. 32位操作系统上对64位变量读写的非原子性,高32位和低32位分开操作
  2. 64位操作系统上读写64位数据是原始操作。

加锁与可见性

加锁的含义不仅仅局限于互斥行为,还包括内存可见性。为了确保所有线程都能看到共享变量的最新值,所有执行读操作或者写操作的线程必须在·同一个锁上同步·

当线程B执行由锁保护的同步代码块时,可以看到线程A之前在同一个同步代码块中的所有操作。

volatile变量

一种稍弱的同步机制,用来确保将变量的更新操作通知到其他线程

  1. 编译器和运行时不会将该变量上的操作和其他内存操作一起重排序
  2. volatile变量不会被缓存寄存器或者对其他处理器不可见(同一个处理就是可见的?)的地方

volatile不保证原子性:比如i++

3.2 发布和逸出

“发布publish”就是对象能够在当前作用域之外的代码中使用

逸出:当某个不应该发布的对象被发布时

常见的不小心逸出的场景

  1. 将引用保存到一个共有的静态变量中。(如果这个变量是个集合或数组,那么里面的元素全都逸出了)
  2. 被类一起发布出去
  3. 内部类发布(因为内部类中会有对外部类的引用)

安全的对象构造过程
不要在构造过程中使用this逸出(因为此时的对象可能还未完成构造,状态时不可预测的)

  1. 不要在构造函数中启动一个线程
  2. 也不要在构造函数中注册监听器等等
    因为如果是内部(局部)类都会逸出未构造完成的this

3.3 线程封闭

不共享数据。仅在单线程内访问数据,不需要同步

ThreadLocal
可以共享变量,减少方法参数传递。但也要小心,因为会降低代码可重用性、增加隐含的耦合性

3.4 不变性

不可变对象一定是线程安全的

很多定义都是从代码上看的,但自己在使用中:很多都是逻辑上的不可变(即没有去改动)。

3.4.1 final域

final修饰的变量是线程安全的。
好的编程习惯:除非某个域是可变的,否则应将其声明为final域

3.4.2 使用volatile来发布不可变对象

不可变对象也能解决竞争条件问题
比如一些操作因为因为线检查精后处理得问题,导致竞争条件出现
现在我们可以我们可以通过final和volatile结合,将操作封装到一个不变的对象中(保证对应关系不会因为并发问题乱掉)也就是一致性,volatile保证数据的实时性。

3.5 安全发布

只要没有使用同步来确保对象对其他线程可见,那么就被称为'未被正确发布'

3.5.1 不正确的发布

比如

public Holder h;

public void init(){
  h = new Holder();
}

这段代码的问题在于:
只要没有同步,那么其他线程不一定能看到构造函数结束后的状态(可能构造了一般,其他线程就调用了该对象)构造函数执行过程中:会调用Object的构造函数,属性初始值默认,然后赋值

3.5.2 不可变对象和初始化安全性

  1. 即使某个对象的引用对其他线程是可见的,但不意味对象状态对于使用该对象的线程一定是可见
  2. java对final域做了处理
    任何线程都可以在不需要额外同步的情况下安全地访问不可变对象.

参考

  1. https://www.jianshu.com/p/d5e858670f23

相关文章

网友评论

      本文标题:1-3章

      本文链接:https://www.haomeiwen.com/subject/cbnckctx.html