文章目录
- 概要
- 线程安全的三个体现
- 一 、原子性
- 二 、可见性
- 三 、有序性
- 小结
概要
什么是线程安全???
当多个线程访问某个类时,不管运行时环境采用 何种调度方式 或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类就是线程安全的。
线程安全的三个体现
- 原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作(Atomic、CAS算法、synchronized、Lock)
- 可见性:一个主内存的线程如果进行了修改,可以及时被其他线程观察到(synchronized、volatile)
- 有序性:如果两个线程不能从 happens-before原则 观察出来,那么就不能观察他们的有序性,虚拟机可以随意的对他们进行重排序,导致其观察观察结果杂乱无序(happens-before原则)
一 、原子性
在Java jdk中里面提供了很多Atomic类
AtomicXXX:CAS、Unsafe.compareAndSwapInt
AtomicLong、LongAdder
AtomicReference、AtomicReferenceFieldUpdater
AtomicStampReference:CAS的ABA问题
由于CAS原语的直接操作与计算机底层的联系很大,CAS原语有三个参数,内存地址、期望值、新值。我们在Java中一般不去直接写CAS相关的代码,JDK为我们封装在AtomicXXX中,因此,我们直接使用就可以了。
CAS并非完美的,它会导致ABA问题,例如:当前内存的值一开始是A,被另外一个线程先改为B然后再改为A,那么当前线程访问的时候发现是A,则认为它没有被其他线程访问过。在某些场景下这样是存在错误风险的。比如在链表中。 如何解决这个ABA问题呢,大多数情况下乐观锁的实现都会通过引入一个版本号标记这个对象,每次修改版本号都会变话,比如使用时间戳作为版本号,这样就可以很好的解决ABA问题。 在JDK中提供了AtomicStampedReference类来解决这个问题,这个类维护了一个int类型的标记stamp,每次更新数据的时候顺带更新一下stamp。
二 、可见性
一个线程对主内存的修改可以及时被其他线程观察到
- syncronized 线程加锁时,将清空工作内存中共享变量的值,从而使得使用共享变量时需要从主内存中重新读取最新的值(注意:加锁与解锁是同一把锁) 由于syncronized可以保证原子性及可见性,变量只要被syncronized修饰,就可以放心的使用
- volatile 通过加入内存屏障和禁止重排序优化来实现可见性。
三 、有序性
允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
通过volatile、synchronized、lock保证有序性
小结
每日一小结,进步一大节
Java八股文