多线程-volatile关键字和ThreadLocal详解 volatile关键字和ThreadLocal

volatile关键字和ThreadLocal

1、并发编程中的三个概念

原子性:一个或多个操作。要么全部执行完成并且执行过程不会被打断,要么不执行。最常见的例子:i++/i--操作。不是原子性操作,如果不做好同步性就容易造成线程安全问题。

可见性:多个线程访问同一个变量,一个线程改变了这个变量的值,其他线程可以立即看到修改的值。可见性的问题,有两种方式保证。一是volatile关键字,二是通过synchronized和lock。详细在后面。

有序性:程序执行的顺序按照代码的先后顺序执行。

要了解有序性需要了解一下指令重排序。处理器为了提供运行效率,会将代码优化,不保证各个语句的执行顺序,但会保证执行结果跟代码顺序执行一致,其不影响单线程的执行结果,但会影响线程并发执行的正确性。指令重排序会考虑指令之间的数据依赖性,如果一个指令B必须用到指令A的结果,那么处理器会保证A在B之前执行。

要保证并发程序正确的执行,必须要保证原子性、可见性及有序性。只要有一个没有被保证,就可能导致程序运行不正确。

 2、Java内存模型

Java内存模型规定:所有变量存在主内存,每个线程有自己的工作内存。线程对变量的操作必须在工作内存进行,而不能直接对主内存进行操作。并且每个线程不能访问其他线程的工作内存。

JAVA语言本身提供的对原子性、可见性及有序性的保证:

原子性:java中,对于引用变量,和大部分的原始数据类型的读写(除long 和 double外)操作都是原子的。这些操作不可被中断,要么执行,要么不执行。对于所有被声明为volatile的变量的读写,都是原子的(除long和double外)

可见性:java提供了volatile关键字来保证可见性。

有序性:java内存模型中,允许编译器和处理器对指令进行重排序。其会影响多线程并发执行的正确性。在java里可以通过volatile关键字,还有synchronized和lock来保证有序性。synchronized和lock保证每个时刻只有一个线程执行同步代码,使得线程串行化执行同步代码,保证了有序性。volatile如何保证的讲解在后面。

 3、volatile关键字详解

一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰后,就具备了两层语义:保证了不同线程对这个变量进行操作时的可见性和禁止了指令重排序。

java提供了volatile关键字来保证可见性:

当一个共享变量被volatile修饰时,它会保证修改的值立即被更新到主内存。其他线程读取时会从内存中读到新值。普通的共享变量不能保证可见性,其被写入内存的时机不确定。当其他线程去读,可能读到的是旧的值。
另外通过synchronized和lock也可以保证可见性。它们能保证同一时刻只有一个线程获取锁然后执行同步代码。并在释放锁之前对变量的修改刷新到住内存中。以此来保证可见性。
volatile关键字的原理和实现机制:
在加入volatile关键字时,会多出一个lock前缀指令。lock前缀指令相当于一个内存屏障,其提供三个功能。
  1、它会强制将对缓存的修改操作立即写入主内存。
  2、如果是写操作,它会导致其他CPU中对应的缓存行无效
  3、它确保指定重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面。即在执行到内存屏障这句指令时,在它前面的操作已经全部完成。

要注意的是:volatile无法保证对变量的任何操作都是原子性的。

使用volatile关键字时必须具备两个条件:

  1、对变量的写操作不依赖于当前值。

  2、该变量没有包含在具有其他变量的不变式中。

4、ThreadLocal详解

ThreadLocal,线程本地变量也可以叫线程本地存储。其为每一个线程维护了一个独立的变量副本。将对象的可见范围限制在同一个线程内。

ThreadLoca类中提供了几个常用方法:

public T get() { }---获取ThreadLocal在当前线程中保存的变量副本
public void set(T value) { }---设置当前线程中变量的副本
public void remove() { }---移除当前线程中变量的副本
protected T initialValue() { }---protected修饰的方法。ThreadLocal提供的只是一个浅拷贝,如果变量是一个引用类型,那么就要重写该函数来实现深拷贝。建议在使用ThreadLocal一开始时就重写该函数

ThreadLocal的作用:实现线程范围内的局部变量,即ThreadLocal在一个线程中是共享的,在不同线程之间是隔离的。

ThreadLocal的原理:ThreadLocal存入值时使用当前ThreadLocal实例作为key,存入当前线程对象中的Map中去。最开始在看源码之前,我以为是以当前线程对象作为key将对象存入到ThreadLocal中的Map中。

猜你喜欢

转载自www.cnblogs.com/alex-xyl/p/12462381.html