本章内容:
原子性:AtomicXXX、CAS原理、Unsafe、AtomicLong&LongAddr、AtomicReference&AtomicReferenceFieldUpdater、AtomicStampReference
锁:synchronized(修饰代码块、方法、静态方法、类)、Lock
可见性:synchronized、volatile(读、写、使用)
有序性:happens-before原则
【线程安全性】当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程如何进行交替执行,并且在代码运行中不需要额外的同步或协同,这个类都行为正常,那么称这个类是线程安全的。
【可见性】一个线程对主内存的修改可被其他线程看到。
【有序性】一个线程观察其他线程的指令执行顺序,由于指令重排序的存在,该观察结果一般无序。
一、原子性——java.util.concurrent.atomic包/CAS
【原子性】提供了互斥访问,同一时刻只有一个线程对它操作。
1.Atomic包
①原子更新基本数字类型:AtomicInteger、AtomicLong、AtomicBoolean;
②原子更新数组:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray;
③原子更新引用:AtomicReference、AtomicMarkableReference、AtomicStampedReference;
④原子更新属性:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater;
1 public static AtomicInteger count = new AtomicInteger(1); 2 3 4 public static Student[] value = new Student[]{new Student(1),new Student2,…}; 5 public static AtomicReferenceArray arr = new AtomicReferenceArray(value); 6 7 private static AtomicIntegerFieldUpdater<Student> updater = AtomicIntegerFieldUpdater.newUpdater(Student.class, "age"); 8 public volatile int age= 20; 9 //student1是Student一个实例 10 updater.compareAndSet(studen1, 100, 120)
2. CAS/Unsafe
【Unsafe】Java无法直接访问底层操作系统,而是通过本地方法(native)来访问的。JVM开启一个后门,JDK有一个类Unsafe,它提供了硬件级别的原子操作。这个类中的方法都是public的,但是只有授信的代码才能获得该类的实例。
【CAS】Compare and Swap:比较并交换。设计并发算法时常用的一门技术,JUC完全建立在CAS上,当前处理器都支持CAS。
CAS方法有三个操作数:内存值V、旧的预期值A、要修改的值B,当且仅当预期值与内存值相同时,将内存值修改为B并返回true。
举例:
AtomicInteger.getAndIncrement():
Unsafe.getAndAddInt():var1是当前AtomicInteger对象,var2是内存值V,var5是旧的预期值A,var5+var4是要修改的值,此处var4=1.只有当var2=var5时,才会赋予新值。
Unsafa.compareAndSwapInt():
【ABA问题】如果一个变量V初次读取的时候是A值,并且在准备复制的时候检查它任然是A值,但是处理过程中被其他线程修改过A-B-A,CAS操作仍然认为他没有修改过=》ABA问题。
解决方案:带有标记的原子引用类AtomicStampedReference,通过控制变量的版本来保证原子性,但是比较鸡肋,如果使用传统的互斥同步可能会使原子类更加高效。
3.AtomicLong && LongAddr
Java8推荐LongAddr替代AtomicInteger。AtomicInteger在高并发场景下compareAndSwapInt会不断试错,有性能问题。
LongAdder在AtomicLong的基础上将单点的更新压力分散到各个节点,在低并发的时候通过对base的直接更新可以很好的保障和AtomicLong的性能基本保持一致,而在高并发的时候通过分散提高了性能。将待处理的数据,通过hash计算分散到Cell数组中分别处理,最后在汇总计算。注意casBase ==> !casBase(b = base, b + x);表示CAS更新操作;如果一个线程去CAS失败,那么表示正在有一个线程正在CAS操作,表示竞争激烈;
LongAddr在高并发的时候,Cell数组的汇总计算会出错。在高并发获取全局唯一ID时,使用AtomicLong而不是LongAddr。
4. AtomicBoolean
使用场景——使某段代码只执行一次。首先我们要知道compareAndSet的作用,判断对象当时内部值是否为第一个参数,如果是则更新为第二个参数,且返回ture,否则返回false。那么默认初始化为false,则一个线程把他变为ture,compareAndSet返回ture,进入方法体执行逻辑,那么其他的任何线程进入该方法执行compareAndSet时第一个参数为false,而对象的内部值已经被修改为true,则永远过不了if。
private static AtomicBoolean isHappened = new AtomicBoolean(false); private static void test() { if (isHappened.compareAndSet(false, true)) {//只执行一次 log.info("execute"); } }
5. 锁
【synchronized关键字】synchronized修饰的变量是不能被继承的,父类是synchronized,子类必须显式写出来,否则不是同步方法。
【作用范围】修饰代码块、修饰静态方法、修饰非静态方法、修饰一个类。
【原理】基于Monitor实现的。synchronized获取对象锁保证在执行共享数据的线程是互斥的,可以使用wait、notify、notifyAll进行线程协同工作。Class和Object都关联了一个Monitor。提供了两个高效的字节码指令monitorenter和monitorexit实现同步代码块。同步加锁的是对象而不是代码。当一个线程访问Object的一个synchronized(this)同步代码块时,其他线程对Objecet中其他synchronized(this)同步代码块的访问将会被阻塞,只能访问非synchronized代码块。
【锁-Lock】锁标记存放在对象头中的Mark Word中。
【synchronized和Lock的区别】
①Lock是一个接口,synchronized是一个关键字,由Java内置语言实现的。
②synchronized发生异常时,会自动释放线程占有的锁(独占锁,也是悲观锁),因此不会有死锁现象发生。Lock发生异常时,必须通过unLock()去释放锁(乐观锁),否则会产生死锁现象。通常将unLock放在finally中去释放锁。
③Lock会让等待的锁中断,而synchronized不会,因此会产生阻塞现象。
④通过Lock可以知道有么有成功获取锁,而synchronized不行。
⑤Lock可提高多线程进行读操作的效率。
二、可见性
【可见性】一个线程对主存的修改可被其他线程看到、
1.导致共享变量不可见的原因
①线程交叉执行;
②重排序结合线程交叉执行;
③共享变量更新后没有及时地在工作内存和主内存之间更新;
2.可见性-synchronized
JMM关于synchronized的规定:
①线程解锁前,必须将共享变量的最新值刷回主存;(解锁-刷)
②线程加锁时,将清空工作内存中共享变量值,从而使用共享变量时需要从主内存重新读取最新的值。(加锁-取)
3.可见性-volatile
通过加入内存屏障和禁止重排序优化来实现。
①对volatile变量写操作时,会在写操作后加入一条store屏障指令,将本地内存中的共享变量刷回主存中。
②对volatile变量读操作时,会在读操作前加入一条load屏障指令,从主存中读取共享变量。
volatile不能保证原子性:
/* 1.count 线程1,2读取最新值 count=1 2.count++ 线程1,2同时增加1 count=2 3.count 线程1,2将count写回主存,少加一次1 count=1 正确结果应该是2;根本原因:count++不是一个原子性操作
可以通过Atomic包实现原子性操作 */ volatile int count = 1; count++;
③应用场景:适合用于修饰状态标记量
使用条件:对变量的写操作不依赖于变量的当前值,或者确保只有单个线程更新变量值;该变量没有包含在具有其他变量的不变式中。
④volatile与synchronized区别
volatile | synchronized | |
作用范围 | 变量 | 变量、方法、类 |
保证可见性 | √ | √ |
保证原子性 | × | √ |
是否会造成阻塞 | × | √ |
是都可被编译器优化 | × | √ |
三、有序性
【有序性】程序执行的顺序按照代码的顺序执行
【重排序】见上章
【happens-before规则】见上章