《Java并发编程实战》读书笔记(一)

最近在读《Java并发编程实战》这本书,感觉写的很详细,易懂,可以比较全面地了解Java并发编程相关内容,为了以后方便查找,专门做个读书笔记,这篇只总结这本书的第一部分——基础知识

Java并发编程实战

线程

  • 线程会共享进程范围内的资源(例如:内存句柄和文件句柄),但每个线程都有各自的程序计数器、栈、局部变量

  • 线程也被称为轻量级进程,很多操作系统都以线程为基本的调度单位

  • 同一个进程中的所有线程,共享进程的内存地址空间,可以访问相同的变量,在同一个堆上分配对象(所以多个线程同时访问时,必须使用同步机制来协同访问)

  • 线程的优势:发挥多处理器的能力、建模简单、简化异步事件处理、GUI响应灵敏

  • 线程带来的风险:安全性、活跃性、性能

  • 安全性:存在竞态条件(同时访问,某个方法不一定返回的是唯一值),使用同步来解决

  • 活跃性:死锁、饥饿、活锁

  • 性能:线程调度器临时挂起活跃线程,并转而运行另一个线程,造成上下文(Context)频繁切换

线程安全

  • 多线程读写变量时,必须使用同步机制来协同可变状态的访问,关键字:synchronized,同步还包括:volatile、显式锁、原子变量

  • 如果不使用同步机制,保证程序正确性:不在线程间共享变量、修改为不可变变量

  • 良好的面向对象封装设计,有助于保护线程安全

  • 线程安全的核心:正确性,即某个类的行为与其规范完全一致

  • 线程安全的定义:当多个线程访问某个类时,不管运行时环境采用何种调度方式,或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,0这个类始终都能表现出正确的行为,那么就称这个类是线程安全的

  • 线程安全类:在并发环境和单线程环境中都不会被破坏的类

  • 无状态、不可变的对象,一定是线程安全的

  • 最常见的竞态条件:先检查后执行

  • 原子操作:A和B两个操作,如果从执行A的线程来看,另外一个线程需要执行B,要么将B完全执行完,要么完全不执行B,那么A和B对彼此是原子的

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

  • Java的内置锁机制——同步代码块,静态的synchronized方法以Class对象作为锁

1
2
3
synchronized (lock) {

}
  • Java的内置锁synchronized,是一种互斥锁,可重入

  • 可重入:某个线程可以获得一个已经由它自己持有的锁,如:父类方法已加锁,子类覆盖时再次加锁,如不可重用,将导致死锁

  • 不可盲目地为了性能而牺牲简单性

  • 当执行时间较长的计算或者可能无法快速完成的操作时(例如,网络I/O或控制台I/O),一定不要持有锁

对象的共享

关键词:volatile、ThreadLocal、final

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

  • 可见性的保证:加锁、volatile,加锁机制既可以保证可见性又可以保证原子性,volatile变量只能保证可见性

  • long和double是64位变量,其读写操作,会被JVM拆分为两个32位操作,多线程环境中不使用volatile,可能会读到两个不匹配的32位值

  • volatile用来确保将变量的更新通知到其他线程,编译器和运行时不会将该变量上的操作与其他内存操作一起重排序

  • volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时,总会返回最新写入的值

  • 访问volatile变量不会执行加锁操作,因此不会执行线程阻塞,是比synchronized更为轻量的同步机制

  • 当且仅当满足以下条件时,才应该使用volatile变量:

    1. 对变量的写入操作不依赖变量的当前值,或者能够确保只有单个线程更新变量的值

    2. 该变量不会与其他状态变量一起纳入不变性条件中

    3. 在访问变量是不需要加锁