并发编程-原子类
AtomicInteger
private volatile int value;
public AtomicInteger(int initialValue) {
value = initialValue;
}
public AtomicInteger() {
}
构造方法
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
可以看到最上面是和AQS采用了类似的机制,因为要使用CAS算法更新value的值,所以得先计算出value字段在对象中的偏移地址,CAS直接修改对应位置的内存即可(可见Unsafe类的作用巨大,很多的底层操作都要靠它来完成)
自增操作是怎么在运行的:
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
调用了unsafe.getAndAddInt()
,我们接着看看Unsafe里面写了什么:
public final int getAndAddInt(Object o, long offset, int delta) { //delta就是变化的值,++操作就是自增1
int v;
do {
//volatile版本的getInt()
//能够保证可见性
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, v + delta)); //这里是开始cas替换int的值,每次都去拿最新的值去进行替换,如果成功则离开循环,不成功说明这个时候其他线程先修改了值,就进下一次循环再获取最新的值然后再cas一次,直到成功为止
return v;
}
这是一个do-while
循环,采用自旋形式,来不断进行CAS操作,直到成功。
可见,原子类底层也是采用了CAS算法来保证的原子性,包括getAndSet
、getAndAdd
等方法都是这样。原子类也直接提供了CAS操作方法,我们可以直接使用:
public static void main(String[] args) throws InterruptedException {
AtomicInteger integer = new AtomicInteger(10);
System.out.println(integer.compareAndSet(30, 20));
System.out.println(integer.compareAndSet(10, 20));
System.out.println(integer);
}
如果想以普通变量的方式来设定值,那么可以使用lazySet()
方法,这样就不采用volatile
的立即可见机制了
AtomicInteger integer = new AtomicInteger(1);
integer.lazySet(2);
AtomicIntegerArray
public static void main(String[] args) throws InterruptedException {
AtomicIntegerArray array = new AtomicIntegerArray(new int[]{0, 4, 1, 3, 5});
Runnable r = () -> {
for (int i = 0; i < 100000; i++)
array.getAndAdd(0, 1);
};
new Thread(r).start();
new Thread(r).start();
TimeUnit.SECONDS.sleep(1);
System.out.println(array.get(0));
}
LongAdder
在JDK8之后,新增了DoubleAdder
和LongAdder
,在高并发情况下,LongAdder
的性能比AtomicLong
的性能更好,主要体现在自增上,它的大致原理如下:在低并发情况下,和AtomicLong
是一样的,对value值进行CAS操作,但是出现高并发的情况时,AtomicLong
会进行大量的循环操作来保证同步,而LongAdder
会将对value值的CAS操作分散为对数组cells
中多个元素的CAS操作(内部维护一个Cell[] as数组,每个Cell里面有一个初始值为0的long型变量,在高并发时会进行分散CAS,就是不同的线程可以对数组中不同的元素进行CAS自增,这样就避免了所有线程都对同一个值进行CAS),只需要最后再将结果加起来即可。
使用如下:
public static void main(String[] args) throws InterruptedException {
LongAdder adder = new LongAdder();
Runnable r = () -> {
for (int i = 0; i < 100000; i++)
adder.add(1);
};
for (int i = 0; i < 100; i++)
new Thread(r).start(); //100个线程
TimeUnit.SECONDS.sleep(1);
System.out.println(adder.sum()); //最后求和即可
}
LongAdder
&AtomicLong
性能对比:
public class Main {
public static void main(String[] args) throws InterruptedException {
System.out.println("使用AtomicLong的时间消耗:"+test2()+"ms");
System.out.println("使用LongAdder的时间消耗:"+test1()+"ms");
}
private static long test1() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(100);
LongAdder adder = new LongAdder();
long timeStart = System.currentTimeMillis();
Runnable r = () -> {
for (int i = 0; i < 100000; i++)
adder.add(1);
latch.countDown();
};
for (int i = 0; i < 100; i++)
new Thread(r).start();
latch.await();
return System.currentTimeMillis() - timeStart;
}
private static long test2() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(100);
AtomicLong atomicLong = new AtomicLong();
long timeStart = System.currentTimeMillis();
Runnable r = () -> {
for (int i = 0; i < 100000; i++)
atomicLong.incrementAndGet();
latch.countDown();
};
for (int i = 0; i < 100; i++)
new Thread(r).start();
latch.await();
return System.currentTimeMillis() - timeStart;
}
}
AtomicReference
public static void main(String[] args) throws InterruptedException {
String a = "Hello";
String b = "World";
AtomicReference<String> reference = new AtomicReference<>(a);
reference.compareAndSet(a, b);
System.out.println(reference.get());
}
AtomicIntegerFieldUpdater
JUC还提供了字段原子更新器,可以对类中的某个指定字段进行原子操作(注意字段必须添加volatile关键字):
public class Main {
public static void main(String[] args) throws InterruptedException {
Student student = new Student();
AtomicIntegerFieldUpdater<Student> fieldUpdater =
AtomicIntegerFieldUpdater.newUpdater(Student.class, "age");
System.out.println(fieldUpdater.incrementAndGet(student));
}
public static class Student{
volatile int age;
}
}