什么是线程不安全
对线程安全的理解就是多个线程同时操作一个共享变量时会产生意料之外的情况,这种情况就是线程不安全。注意:只有写操作才可能出现线程不安全,对共享变量只进行读操作线程是绝对安全的。
具体线程不安全的例子有一个很经典的就是两个线程都对一个共享变量 x=0 执行 100 次自增操作,但是 x 的结果并非 200
因此线程不安全的条件是:多线程 + 共享变量 + 写操作
Java的内存模型
你可能会好奇线程是如何获取共享变量的?
Java 线程之间的通信由 Java 内存模型(简称 JMM)控制,从抽象的角度来说,JMM 定义了线程和主内存之间的抽象关系。JMM 的抽象示意图如图所示:
从图中可以看到:
以上只是 Java 内存模型的抽象图,实际上线程的工作模型是这样的,栈内存即是两个缓冲区
接下来看一个线程不安全的例子:
假设线程 A、B 操作同一个共享变量 X,初始两级 Cache 都为空
由于 Java 内存机制就是这样设计的,因此多个线程操作同一个变量会产生不安全的问题,volatitle 关键字这是被设计出来解决这一问题的,它只能用于单个变量。
volatile解决共享变量线程不安全的策略
还是接着上面这个例子,这样定义 X
volatle int X=0
volatile 的内存语义是:
当一个线程对 volatile 修饰的变量进行写操作时,JMM 会立即将该线程对应的栈内存中的副本的值刷新到堆内存中;当一个线程对 volatile 修饰的变量进行读时,JMM 会清空此变量的一二级缓存,直接从堆内存中读取共享变量的值。
volatile 可以当作一个轻量级的锁来使用,但 volatile 仅仅只能保证共享变量内存的可见性,不能保证操作共享变量的原子性,而锁(如 synchronized)能保证整段锁范围内的代码具有原子性。
synchronized 与锁
首先要明确的是 synchronized 不是锁,锁都是基于对象的( Object 的子类),Java 中的每一个对象都可以作为一个锁。
synchronized 是 Java 的一个关键字,保证临界区内的代码同一时刻只能有一个线程执行。
线程的执行代码在进入 synchronized 代码块前会自动获取内部锁,这时候其他线程访问该同步代码块时会被阻塞挂起。拿到内部锁的线程会在正常退出同步代码块或者抛出异常后或者在同步块内调用了该内置锁资源的wait系列方法时释放该内置锁。内置锁是排它锁,也就是当一个线程获取这个锁后,其他线程必须等待该线程释放锁后才能获取该锁。
sysnchronized的内部锁可以是:
wait 和 notify 方法只能用于 synchronized 同步代码块内
synchronized 的内存语义
与 volatile 不同
进入 synchronized 块的内存语义是把再 synchronized 块内使用到的所有共享变量从栈内存中清空,这样就只能从堆内存只读取,保证了内存可见性。退出 synchronized 块的内存语义是把 synchronized 块内对共享变量的修改刷新到堆内存。
仔细想想,这其实也是一个加锁和解锁的过程,保证共享变量修改的可见性。
总结