《Java并发编程实战》读书笔记——第2章 线程安全性

  使用线程和锁终归只是一些机制,要编写线程安全的代码,其核心在于要对状态访问操作进行管理,特别是对共享的和可变的状态的访问。完全由线程安全类构成的程序并不一定就是先安全的,而在线程安全类中也可以包含非线程安全的类。

2.1、什么是线程安全性

  在线程安全性的定义中,最核心的概念就是正确性。正确性的含义是,某个类的行为与其规范完全一致。

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

2.2、原子性

2.2.1、竞态条件

  由于不恰当执行时序而出现不正确的结果的情况,就是竞态条件。

2.2.2、示例:延迟初始化中的竞态条件

  延迟初始化就是先检查后执行的操作,即通过一个可能失效的观测结果来决定下一步的动作。

2.2.3、复合操作

  “读取-修改-写入就是复合操作。

2.3、加锁机制

  当不变性条件涉及多个变量时,各个变量之间并不是彼此独立的,某个变量的值会对其他变量的值产生约束。要保持状态的一致性,就需要在单个原子操作中更新所有相关的变量。

2.3.1、内置锁

  java提供来一种内置的锁机制来支持原子性:同步代码块。同步代码块包含两部分:一个作为锁的对象引用,一个作为由这个锁保护的代码块。

synchronized(lock){
    //访问或修改由锁保护的共享状态
}

  以synchronized来修饰的方法,该同步代码块就是整个方法,该同步代码块的锁就是方法调用所在的对象。静态的synchronized方法以该类的Class对象作为锁。每个java对象都可以用做一个实现同步的锁,这些锁称为内置锁,或监视器锁(Monitor Lock)。线程在进入同步代码块时先获取锁,离开时释放锁。在同一时刻,只有一个线程可以获取该锁,其他线程需要等待。

2.3.2、重入

  内置锁是可重入的,某个线程试图获得一个已经由它自己持有的锁,那么这个请求就会成功。重入实现的方式是:为每个锁关联一个获取计数值和所有者线程,当计数值为0时,这个锁就被认为是没有被任何线程持有。当线程请求一个未被持有的锁时,JVM将记下锁的持有者,并且将获取计数值置为1。如果同个线程再次获取这个锁,计数值递增,而当线程退出代码块时,计数器递减。当计数值为0时,这个锁就被释放。

  线程可以执行这样的方法:子类的同步方法中调用父类中的同步方法,不会产生死锁。因为该线程获取的是子类对象的锁,而调用父类中同步方法,依旧是子类的对象,这里是内置锁重入。

2.4、用锁来保护状态

  对于可能被多个线程同时访问的可变状态变量,在访问它时都需要持有同一个锁,在这种情况下,我们称为状态变量是由这个锁保护的。对于每个包含多个变量的不变性条件,其中涉及的所有变量都需要由同一个锁保护。

2.5、活跃性与性能

  通常,线程安全的简单性和性能之间存在者相互制约因素。当实现某个同步策略时,一定不要盲目地为了性能而牺牲简单性,这可能会破坏安全性。当执行时间较长的计算或者可能无法快速完成的操作时,一定不要持有锁。

猜你喜欢

转载自www.cnblogs.com/kbkb/p/13377002.html