文章目录
1 原子整数
JUC并发包提供了:
- AtomicBoolean
- AtomicInteger
- AtomicLong
以AtomicInteger为例
AtomicInteger i = new AtomicInteger(0);
// 获取并自增(i = 0, 结果 i = 1, 返回 0),类似于 i++
System.out.println(i.getAndIncrement());
// 自增并获取(i = 1, 结果 i = 2, 返回 2),类似于 ++i
System.out.println(i.incrementAndGet());
// 自减并获取(i = 2, 结果 i = 1, 返回 1),类似于 --i
System.out.println(i.decrementAndGet());
// 获取并自减(i = 1, 结果 i = 0, 返回 1),类似于 i--
System.out.println(i.getAndDecrement());
// 获取并加值(i = 0, 结果 i = 5, 返回 0)
System.out.println(i.getAndAdd(5));
// 加值并获取(i = 5, 结果 i = 0, 返回 0)
System.out.println(i.addAndGet(-5));
// 获取并更新(i = 0, p 为 i 的当前值, 结果 i = -2, 返回 0)
// 其中函数中的操作能保证原子,但函数需要无副作用
System.out.println(i.getAndUpdate(p -> p - 2));
// 更新并获取(i = -2, p 为 i 的当前值, 结果 i = 0, 返回 0)
// 其中函数中的操作能保证原子,但函数需要无副作用
System.out.println(i.updateAndGet(p -> p + 2));
// 获取并计算(i = 0, p 为 i 的当前值, x 为参数1, 结果 i = 10, 返回 0)
// 其中函数中的操作能保证原子,但函数需要无副作用
// getAndUpdate 如果在 lambda 中引用了外部的局部变量,要保证该局部变量是 final 的
// getAndAccumulate 可以通过 参数1 来引用外部的局部变量,但因为其不在 lambda 中因此不必是 final
System.out.println(i.getAndAccumulate(10, (p, x) -> p + x));
// 计算并获取(i = 10, p 为 i 的当前值, x 为参数1, 结果 i = 0, 返回 0)
// 其中函数中的操作能保证原子,但函数需要无副作用
System.out.println(i.accumulateAndGet(-10, (p, x) -> p + x));
2 原子引用
为什么需要原子引用类型?
- AtomicReference
- AtomicMarkableReference
- AtomicStampedReference
2.1 AtomicReference
原子更新基本类型的AtomicInteger,只能更新一个变量,如果要原子更新多个变量,就需要使用这个原子更新引用类型提供的类。
public class AtomicReference_P186 {
public static AtomicReference<User> atomicUserReference = new AtomicReference<User>();
public static void main(String[] args) {
User user = new User("cocan",15);
atomicUserReference.set(user);
User updateUser = new User("shinichi",17);
atomicUserReference.compareAndSet(user,updateUser);
System.out.println(atomicUserReference.get().getName());
System.out.println(atomicUserReference.get().getOld());
}
static class User{
private String name;
private int old;
public User(String name, int old) {
this.name = name;
this.old = old;
}
public String getName() {
return name;
}
public int getOld() {
return old;
}
}
}
主线程仅能判断出共享变量的值与最初值 A 是否相同,不能感知到这种从 A 改为 B 又 改回 A 的情况,如果主线程
希望:只要有其它线程【动过了】共享变量,那么自己的 cas 就算失败,这时,仅比较值是不够的,需要再加一个版本号
这时候就需要使用AtomicStampedReference
2.2 AtomicStampedReference
AtomicStampedReference 可以给原子引用加上版本号,追踪原子引用整个的变化过程,如: A -> B -> A -> C ,通过AtomicStampedReference,我们可以知道,引用变量中途被更改了几次。
public class atomicStampedReference {
static AtomicStampedReference<String> ref = new AtomicStampedReference<String>("A", 0);
public static void main(String[] args) throws Exception {
System.out.println("main start...");
//获取A
//这个共享变量是否被其他线程修改过
String prev = ref.getReference();
//版本号
int version = ref.getStamp();
System.out.println("["+Thread.currentThread().getName()+"]"+version);
other();
Thread.sleep(100);
//尝试改为C
System.out.println("["+Thread.currentThread().getName()+"]"+"change A -> C " + ref.compareAndSet(prev, "C", version, version + 1));
}
private static void other() throws Exception {
new Thread(() -> {
int stamp = ref.getStamp();
System.out.println("["+Thread.currentThread().getName()+"]"+stamp);
System.out.println("["+Thread.currentThread().getName()+"]"+"change A -> B "+ ref.compareAndSet(ref.getReference(),"B",stamp,stamp+1));
},"t1").start();
Thread.sleep(1);
new Thread(() -> {
int stamp = ref.getStamp();
System.out.println("["+Thread.currentThread().getName()+"]"+stamp);
System.out.println("["+Thread.currentThread().getName()+"]"+"change A -> B "+ ref.compareAndSet(ref.getReference(),"A",stamp,stamp+1));
},"t2").start();
}
}
2.3 AtomicMarkableReference
但是有时候,并不关心引用变量更改了几次,只是单纯的关心是否更改过,所以就有了AtomicMarkableReference
public class atomicMarkableReference {
public static void main(String[] args) throws Exception{
GarbageBag bag = new GarbageBag("装满了垃圾");
//参数2 mark可以看做一个标记,表示垃圾袋满了
AtomicMarkableReference<GarbageBag> ref = new AtomicMarkableReference<>(bag,true);
System.out.println("start...");
GarbageBag prev = ref.getReference();
System.out.println(prev.toString());
new Thread( () -> {
System.out.println("start...");
bag.setDesc("空垃圾袋");
ref.compareAndSet(bag,bag,true,false);
System.out.println(bag.toString());
},"保洁阿姨").start();
Thread.sleep(5000);
System.out.println("想换一只新垃圾袋?");
boolean success = ref.compareAndSet(prev,new GarbageBag("空垃圾袋"),true,false);
System.out.println("换了吗?"+success);
System.out.println(ref.getReference().toString());
}
}
class GarbageBag {
String desc;
public GarbageBag(String desc) {
this.desc = desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
@Override
public String toString() {
return super.toString() + " " + desc;
}
}
3 原子数组
- AtomicIntegerArray
- AtomicLongArray
- AtomicReferenceArray
public class atomicIntegerArray {
static int[] value = new int[]{1, 2};
static AtomicIntegerArray ai = new AtomicIntegerArray(value);
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
int prev = ai.get(0);
while(true){
if (ai.compareAndSet(0, prev,i)){
break;
}
}
}
// for (int i = 0; i < 100; i++) {
// new Thread(() -> {
// value[0]--;
// }).start();
// }
System.out.println(ai.get(0));
// System.out.println(value[0]);
// ai.getAndSet(0,3);
// System.out.println(ai.get(0));
// System.out.println(value[0]);
}
}
4 字段更新器
- AtomicReferenceFieldUpdater // 域 字段
- AtomicIntegerFieldUpdater
- AtomicLongFieldUpdater
利用字段更新器,可以针对对象的某个域(Field)进行原子操作,只能配合 volatile 修饰的字段使用,否则会出现异常
public class atomicFieldUpdater {
public static void main(String[] args) {
Student stu = new Student();
AtomicReferenceFieldUpdater updater =
AtomicReferenceFieldUpdater.newUpdater(Student.class,String.class,"name");
System.out.println(updater.compareAndSet(stu, null, "张三"));
System.out.println(stu);
}
}
class Student{
volatile String name;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
}
5 原子累加器
5.1 累加器性能比较
public class atomicAccumulator {
public static void main(String[] args) {
demo(
() -> new AtomicLong(0),
(adder) -> adder.getAndIncrement()
);
demo(
() -> new LongAdder(),
(adder) -> adder.increment()
);
}
/**
* () -> 结果 提供累加器对象
* (参数) -> 执行累加操作
*/
private static <T> void demo(Supplier<T> adderSupplier, Consumer<T> action){
T adder = adderSupplier.get();
List<Thread> ts = new ArrayList<>();
//4个线程,每人累加50万
for (int i = 0; i < 4; i++) {
ts.add(new Thread(() -> {
for (int j = 0; j < 500000; j++) {
action. accept(adder);
}
}));
}
long start = System.nanoTime();
ts.forEach(t -> t.start());
ts.forEach(t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long end = System.nanoTime();
System.out.println(adder+"cost:"+(end - start)/1000_000);
}
}
1000000 cost:43
1000000 cost:9
1000000 cost:7
1000000 cost:7
1000000 cost:7
1000000 cost:31
1000000 cost:27
1000000 cost:28
1000000 cost:24
1000000 cost:22
性能提升的原因很简单,就是在有竞争时,设置多个累加单元,Therad-0 累加 Cell[0],而 Thread-1 累加Cell[1]… 最后将结果汇总。这样它们在累加时操作的不同的 Cell 变量,因此减少了 CAS 重试失败,从而提高性能。
5.2 源码之LongAdder
LongAdder类有几个关键域
//累加单元数组,惰性初始化
transient volatile Cell[] cells;
//基础值,如果没有竞争,则用cas累加这个域
transient volatile long base;
//在cells创建或扩容时,置为1,表示加锁
transient volatile int cellsBusy;
5.3 cas锁
不能用于实践
public class lockCas {
//0 没加锁
//1 加锁
private AtomicInteger state = new AtomicInteger(0);
public void lock() {
while (true) {
if (state.compareAndSet(0, 1)) {
break;
}
}
}
public void unlock() {
System.out.println("["+Thread.currentThread()+"]"+"unlock...");
state.set(0);
}
public static void main(String[] args) {
lockCas lock = new lockCas();
new Thread(() -> {
System.out.println("["+Thread.currentThread()+"]"+"begin...");
lock.lock();
try {
System.out.println("["+Thread.currentThread()+"]"+"lock");
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}).start();
new Thread(() -> {
System.out.println("["+Thread.currentThread()+"]"+"begin...");
lock.lock();
try {
System.out.println("["+Thread.currentThread()+"]"+"lock...");
} finally {
lock.unlock();
}
}).start();
}
}
5.4 原理之伪共享
其中 Cell 即为累加单元
// 防止缓存行伪共享
@sun.misc.Contended
static final class Cell {
volatile long value;
Cell(long x) { value = x; }
// 最重要的方法, 用来 cas 方式进行累加, prev 表示旧值, next 表示新值
final boolean cas(long prev, long next) {
return UNSAFE.compareAndSwapLong(this, valueOffset, prev, next);
}
// 省略不重要代码
}
缓存与内存速度比较
从cpu到 | 大约需要的时钟周期 |
---|---|
寄存器 | 1 cycle(4Hz的CPU约为0.25ms) |
L1 | 3-4cycle |
L2 | 10-20cycle |
L3 | 40-45cycle |
内存 | 120-240cycle |
因为 CPU 与 内存的速度差异很大,需要靠预读数据至缓存来提升效率。
而缓存
以缓存行为单位,每个缓存行对应着一块内存,一般是 64 byte(8 个 long)
缓存的加入会造成数据副本的产生,即同一份数据会缓存在不同核心的缓存行中
CPU 要保证数据的一致性,如果某个 CPU 核心更改了数据,其它 CPU 核心对应的整个缓存行必须失效
因为 Cell 是数组形式,在内存中是连续存储的,一个 Cell 为 24 字节(16 字节的对象头和 8 字节的 value),因此缓存行可以存下 2 个的 Cell 对象。这样问题来了:
Core-0 要修改 Cell[0]
Core-1 要修改 Cell[1]
无论谁修改成功,都会导致对方 Core 的缓存行失效,比如 Core-0 中 Cell[0]=6000, Cell[1]=8000 要累加
Cell[0]=6001, Cell[1]=8000 ,这时会让 Core-1 的缓存行失效
@sun.misc.Contended 用来解决这个问题,它的原理是在使用此注解的对象或字段的前后各增加 128 字节大小的空白,从而让 CPU 将对象预读至缓存时占用不同的缓存行,这样,不会造成对方缓存行的失效
5.5 LongAdder源码-add
public void add(long x) {
// as 为累加单元数组
// b 为基础值
// x 为累加值
Cell[] as; long b, v; int m; Cell a;
// 进入 if 的两个条件
// 1. as 有值, 表示已经发生过竞争, 进入 if
// 2. cas 给 base 累加时失败了, 表示 base 发生了竞争, 进入 if
if ((as = cells) != null || !casBase(b = base, b + x)) {
// uncontended 表示 cell 没有竞争
boolean uncontended = true;
if (
// as 还没有创建
as == null || (m = as.length - 1) < 0 ||
// 当前线程对应的 cell 还没有
(a = as[getProbe() & m]) == null ||
// cas 给当前线程的 cell 累加失败 uncontended=false ( a 为当前线程的 cell )
!(uncontended = a.cas(v = a.value, v + x))
) {
// 进入 cell 数组创建、cell 创建的流程
longAccumulate(x, null, uncontended);
}
}
}
add 流程图
6 Unsafe
6.1 概述
Unsafe 对象提供了非常底层的,操作内存、线程的方法,Unsafe 对象不能直接调用,只能通过反射获得
public class unsafe {
public static void main(String[] args) throws Exception {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
Unsafe unsafe = (Unsafe) theUnsafe.get(null);
}
}
6.2 Unsafe CAS操作
public class unsafe {
public static void main(String[] args) throws Exception {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafe.get(null);
System.out.println(unsafe);
//1.获取域的偏移地址
long idOffset = unsafe.objectFieldOffset(Teacher.class.getDeclaredField("id"));
long nameOffset = unsafe.objectFieldOffset(Teacher.class.getDeclaredField("name"));
Teacher t = new Teacher();
//2.执行cas操作
unsafe.compareAndSwapInt(t,idOffset,0,1);
unsafe.compareAndSwapObject(t,nameOffset,null,"张三");
//3.验证结果
System.out.println(t);
}
}
class Teacher{
volatile int id;
volatile String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Teacher{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
//输出
sun.misc.Unsafe@4554617c
Teacher{id=1, name='张三'}