第2章 线程安全性

线程安全性

同步: synchronized,volatile, 显示锁(Explicit Lock),原子变量

面向对象助于编写线程安全的类

线程类安全 != 程序安全

2.1 什么是线程安全性

  • 正确性:当多个线程访问某个类时,这个类始终表现出正确行为
  • 无状态对象一定是线程安全的

2.2 原子性

2.2.1 竞态条件

正确性依赖时序,常见:先检查后执行(Check-Then-Act),计数器递增(读取-修改-写入)

2.2.2 延迟初始化中的竞态条件

@NotThreadSafe 
public class Lazy nitRace { 
	private ExpensiveObject instance = null; 
    
	public ExpensiveObject getInstance () { 
		if (instance = null) 
			instance = new Expensiveobject (); 
        return instance;
    }
}

两次调用getlnstance可能得到不同的结果

2.2.3 复合操作

线程安全对象(如AtomicLong 读取-修改-写入等操作是原子的)更易维护和验证安全性

2.3 加锁机制

2.3.1 内置锁

同步代码块(Synchronized Block)

  • 作为锁的对象引用
  • 作为由这个锁保护的代码块

性能问题:并发性非常糟糕

2.3.2 重入

线程试图获取一个已经由它自己持有的锁,这个请求会成功。

一种实现:为每个锁关联一个获取计数值和一个所有者线程。线程请求锁时,JVM记录锁的持有者,同一个线程再次获取锁时,递增计数值,当线程退出同步代码块时,计数器响应递减,当为0时锁将释放

/**
 * 如果内置锁不是可重入的,这段代码将发生死锁
 */
public class Widget {
    public synchronized void doSomething() {
        System.out.println("Widget doSomething");
    }

    public static class LoggingWidget extends Widget {
        public synchronized void doSomething() {
            System.out.println("LoggingWidget doSomething");
            // 无法获取Wdiget上的锁
            super.doSomething();
        }
    }
}

重入获取锁的粒度时“线程” POSIX pthread互斥体的获取操作以“调用”为粒度

2.4 用锁来保护状态

锁能保护代码串行访问,因此可用锁来构造协议以实现对共享状态的独占访问,确保状态的一致性

常见加锁约定:将所有可变状态都封装太对象内部,并通过对象的内置锁对所有访问可变状态的路径进行同步,使得在该对象上不会发生并发访问。

复合操作需要额外的加锁机制

2.5 活跃性与性能

缩写同步代码块的作用范围,确保并发性,同时又维护线程安全