Unsafe
概述
- 在将原子类之前,先介绍下Unsafe类,因为原子类的操作都是基于该类做的
- Unsafe对象提供了非常底层操作内存、线程的方法
- 该类只提供了一个私有的无参构造,且Unsafe的定义是一个的私有成员变量,源码如下
public final class Unsafe {
//私有成员静态变量
private static final Unsafe theUnsafe;
// 构造器
private Unsafe() {
}
- 由上一点可知Unsafe对象只能通过反射获取,不能直接创建,获取方式如下
public class UnsafeUtil {
static Unsafe unsafe;
static {
try {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
unsafe = (Unsafe) theUnsafe.get(null);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException("获取 Unsafe 对象异常");
}
}
static Unsafe getUnsafe() {
return unsafe;
}
}
Unsafe的cas
- Unsafe的cas操作是通过对象属性的偏移量、旧值、新值
- 要操作的属性还需要使用volatile修饰,否则会报非法参数异常
- 示例如下
public class UnsafeCas {
public static void main(String[] args) throws NoSuchFieldException {
Dog dog = new Dog();
Unsafe unsafe = UnsafeUtil.getUnsafe();
// 通过反射获取类中属性的域对象 --> 供Unsafe对象获取域对象偏移量使用
Field id = Dog.class.getDeclaredField("id");
Field age = Dog.class.getDeclaredField("age");
Field name = Dog.class.getDeclaredField("name");
// 根据域对象获取域对象的偏移量 --> 供Unsafe cas操作时使用
long idOffset = unsafe.objectFieldOffset(id);
long ageOffset = unsafe.objectFieldOffset(age);
long nameOffset = unsafe.objectFieldOffset(name);
// Unsafe cas 操作, 参数为要操作对象,属性偏移量,旧值,新值
// 如果要保证并发,在加上while(true)循环判断该cas操作是否成功来控制循环退出条件即可
unsafe.compareAndSwapInt(dog,idOffset,0,1);
unsafe.compareAndSwapInt(dog,ageOffset,0,5);
unsafe.compareAndSwapObject(dog,nameOffset,null,"狗狗");
System.out.println(dog);
}
}
class Dog{
volatile long id;
volatile int age;
volatile String name;
@Override
public String toString() {
return "Dog{" +
"id=" + id +
", age=" + age +
", name='" + name + '\'' +
'}';
}
}
原子类
基本类型包装类-原子类
- AtomicBoolean(原子Boolean类)
- AtomicInteger(原子整形类)
- AtomicLong(原子长整型类)
以AtomicInteger为例介绍下该组相关API
public class AtomicIntegerApi {
public static void main(String[] args) {
AtomicInteger i = new AtomicInteger(0);
// 类似于 i++,获取并自增(i = 0, 结果 i = 1, 返回 0)
System.out.println(i.getAndIncrement());
System.out.println(i.get());
// 类似于 ++i,自增并获取(i = 1, 结果 i = 2, 返回 2)
System.out.println(i.incrementAndGet());
System.out.println(i.get());
// 类似于 --i,自减并获取(i = 2, 结果 i = 1, 返回 1)
System.out.println(i.decrementAndGet());
System.out.println(i.get());
// 类似于 i--,获取并自减(i = 1, 结果 i = 0, 返回 1)
System.out.println(i.getAndDecrement());
System.out.println(i.get());
/*
* 上述加减操作单元都是1,如果想自定义操作单元,可以用下面方法
* */
// 获取并加值(i = 0, 结果 i = 5, 返回 0)
System.out.println(i.getAndAdd(5));
System.out.println(i.get());
// 加值并获取(i = 5, 结果 i = 0, 返回 0)
System.out.println(i.addAndGet(-5));
System.out.println(i.get());
/*
* 上述均为加减操作,若想进行更新操作可以使用如下方法,
* 入参为一元整型操作函数式接口: IntUnaryOperator updateFunction
* */
// 获取并更新(i = 0, p 为 i 的当前值, 结果 i = -2, 返回 0)
// 其中函数中的操作能保证原子,但函数需要无副作用
System.out.println(i.getAndUpdate(p -> p - 2));
System.out.println(i.get());
// 更新并获取(i = -2, p 为 i 的当前值, 结果 i = 0, 返回 0)
// 其中函数中的操作能保证原子,但函数需要无副作用
System.out.println(i.updateAndGet(p -> p + 2));
System.out.println(i.get());
/*
* 若想进行两个参数测操作,可以使用如下方法
* 入参为:参数一;要操作的第二个整除 ,参数二:一元整型操作函数式接口: IntUnaryOperator updateFunction
* */
// 获取并计算(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));
System.out.println(i.get());
// 计算并获取(i = 10, p 为 i 的当前值, x 为参数1, 结果 i = 0, 返回 0)
// 其中函数中的操作能保证原子,但函数需要无副作用
System.out.println(i.accumulateAndGet(-10, (p, x) -> p + x));
System.out.println(i.get());
}
}
引用类-原子类
- AtomicReference(基础原子引用类)
- AtomicMarkableReference(可以判断共享变量是否修改过的原子引用类)
- AtomicStampedReference(带戳的原子引用类)
AtomicReference类的使用及问题
public class AtomicReferenceUser {
static AtomicReference<String> ref = new AtomicReference<>("A");
public static void main(String[] args){
System.out.println("main start...");
// 获取值 A
String prev = ref.get();
// 尝试改为 C
System.out.println("change A->C {}" + ref.compareAndSet(prev, "C"));
}
}
- 使用基本原子引用类,会引发ABA问题:也就是如下场景,如果一个线程t1在执行开始时获取到共享变量为A,t1线程执行,这是t2线程将共享变量由A->B并执行结束,又有一个线程t3将B->A并执行结束,这是线程t1要将共享变量由A变为C,这时是会成功的。代码如下
public class ABAThreadSafeQuestion {
static AtomicReference<String> ref = new AtomicReference<>("A");
public static void main(String[] args) throws InterruptedException {
System.out.println("main start...");
// 获取值 A
// 这个共享变量被它线程修改过?
String prev = ref.get();
other();
TimeUnit.SECONDS.sleep(1);
// 尝试改为 C
System.out.println("change A->C {}" + ref.compareAndSet(prev, "C"));
}
private static void other() {
new Thread(() -> {
System.out.println("change A->B {}" + ref.compareAndSet(ref.get(), "B"));
}, "t1").start();
sleep(0.5);
new Thread(() -> {
System.out.println("change B->A {}" + ref.compareAndSet(ref.get(), "A"));
}, "t2").start();
}
}
AtomicMarkableReference类的使用及问题
- 使用AtomicReference原子类操作是会引发ABA问题,如果我只关心共享变量是否被修改过,则可以使用AtomicMarkableReference原子类来解决ABA问题,其他线程修改时,原子变量的第二个参数
public class AtomicMarkableReferenceSloveABA {
static AtomicMarkableReference<String> ref = new AtomicMarkableReference<>("A",Boolean.TRUE);
public static void main(String[] args) throws InterruptedException {
System.out.println("main start...");
// 获取值 A
String prev = ref.getReference();
other();
TimeUnit.SECONDS.sleep(1);
// 尝试改为 C
System.out.println("change A->C {}" + ref.compareAndSet(prev, "C",Boolean.TRUE,Boolean.FALSE));
}
private static void other() {
new Thread(() -> {
System.out.println("change A->B {}" + ref.compareAndSet(ref.getReference(),"B",Boolean.TRUE,Boolean.FALSE));
}, "t1").start();
sleep(0.5);
new Thread(() -> {
System.out.println("change B->A {}" + ref.compareAndSet(ref.getReference(), "A",Boolean.TRUE,Boolean.FALSE));
}, "t2").start();
}
}
AtomicStampedReference原子类的使用及问题
AtomicMarkableReference原子类虽然可以解决ABA问题,但是如果需要知道具体修改过几次,那么
AtomicMarkableReference原子类就不能满足需求了,若要解决这个问题,则就要使用AtomicStampedReference原子类
public class ThreadSafeImplByAtomicStampedReference{
static AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);
public static void main(String[] args) throws InterruptedException {
System.out.println("main start...");
// 获取值A
String prev = ref.getReference();
// 获取版本号
int stamp = ref.getStamp();
System.out.println("版本 {}" + stamp);
// 如果中间有其它线程干扰,发生了 ABA 现象
other();
sleep(1);
// 尝试改为 C
System.out.println("change A->C {}" + ref.compareAndSet(prev, "C", stamp, stamp + 1));
}
private static void other() {
new Thread(() -> {
System.out.println("change A->B {}" + ref.compareAndSet(ref.getReference(), "B",
ref.getStamp(), ref.getStamp() + 1));
System.out.println("更新版本为 {}" + ref.getStamp());
}, "t1").start();
sleep(0.5);
new Thread(() -> {
System.out.println("change B->A {}" + ref.compareAndSet(ref.getReference(), "A",
ref.getStamp(), ref.getStamp() + 1));
System.out.println("更新版本为 {}" + ref.getStamp());
}, "t2").start();
}
}
原子数组类
- AtomicIntegerArray(整型原子数组类)
- AtomicLongArray(长整型原子数组类)
- AtomicReferenceArray(引用对象原子数组类)
public class AtomicArray {
public static void main(String[] args) {
// 不安全数组
test(
()->new int[10],
(array)->array.length,
(array, index) -> array[index]++,
array-> System.out.println(Arrays.toString(array))
);
// 原子数组
test(
()-> new AtomicIntegerArray(10),
(array) -> array.length(),
(array, index) -> array.getAndIncrement(index),
array -> System.out.println(array)
);
}
/**
参数1,提供数组、可以是线程不安全数组或线程安全数组
参数2,获取数组长度的方法
参数3,自增方法,回传 array, index
参数4,打印数组的方法
*/
private static <T> void test(
Supplier<T> arraySupplier,
Function<T, Integer> lengthFun,
BiConsumer<T, Integer> putConsumer,
Consumer<T> printConsumer ) {
List<Thread> ts = new ArrayList<>();
T array = arraySupplier.get();
int length = lengthFun.apply(array);
for (int i = 0; i < length; i++) {
// 每个线程对数组作 10000 次操作
ts.add(new Thread(() -> {
for (int j = 0; j < 10000; j++) {
putConsumer.accept(array, j%length);
}
}));
}
// 启动所有线程
ts.forEach(t -> t.start());
// 等所有线程结束
ts.forEach(t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
printConsumer.accept(array);
}
}
字段更新器原子类
- AtomicReferenceFieldUpdater
- AtomicIntegerFieldUpdater
- AtomicLongFieldUpdater
使用字段更新器,可以对对象的某个属性(域 Field)进行原子操作,只能配合 volatile 修饰的字段使用,否则会出现异常:java.lang.IllegalArgumentException: Must be volatile type
public class AtomicFieldUpdaterTest {
private volatile int field;
public static void main(String[] args) {
AtomicIntegerFieldUpdater fieldUpdater =AtomicIntegerFieldUpdater.newUpdater(AtomicFieldUpdaterTest.class, "field");
AtomicFieldUpdaterTest test5 = new AtomicFieldUpdaterTest();
fieldUpdater.compareAndSet(test5, 0, 10);
// 修改成功 field = 10
System.out.println(test5.field);
// 修改失败 field = 10
fieldUpdater.compareAndSet(test5, 5, 30);
System.out.println(test5.field);
}
}
原子累加器
- DoubleAccumulator
- DoubleAdder
- LongAccumulator
- LongAdder
虽然基本类型包装类自带原子累加操作方法,但是为了提高性能,JDK8中又提供了原子累加器,下面是效率对比测试类
public class AtomicLongAdderTest {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
demo(() -> new LongAdder(), adder -> adder.increment());
}
for (int i = 0; i < 5; i++) {
demo(() -> new AtomicLong(), adder -> adder.getAndIncrement());
}
}
private static <T> void demo(Supplier<T> adderSupplier, Consumer<T> action) {
T adder = adderSupplier.get();
long start = System.nanoTime();
List<Thread> ts = new ArrayList<>();
// 4 个线程,每人累加 50 万
for (int i = 0; i < 40; i++) {
ts.add(new Thread(() -> {
for (int j = 0; j < 500000; j++) {
action.accept(adder);
}
}));
}
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)/1000000);
}
}
性能提升的原因很简单,就是在有竞争时,设置多个累加单元,Therad-0 累加 Cell[0],而 Thread-1 累加Cell[1]… 最后将结果汇总。这样它们在累加时操作的不同的 Cell 变量,因此减少了 CAS 重试失败,从而提高性能。