Atomic原子类
以AtomicXXX开头的都是用CAS操作来保证线程安全的类。
在开发工作中经常有多个线程共同访问一个值,改变一个值的场景,这时就可以使用原子类如AtomicInteger。这里面包含了int类型,这个int类型的自增count++是线程安全的,取值也是线程安全的。
如下,不用再对m()加synchronized,输出一定为10万
public class AtomicInteger_01 {
AtomicInteger count = new AtomicInteger(0);
void m() {
for (int i=0; i<10000; i++) {
count.incrementAndGet(); // 自增 count++
}
}
public static void main(String[] args) {
AtomicInteger_01 instance = new AtomicInteger_01();
List<Thread> threads = new ArrayList<>();
for (int i=0; i<10; i++) {
threads.add(new Thread(instance::m, "thread-"+i));
}
threads.forEach( thread -> thread.start());
threads.forEach( thread -> {
try {
thread.join(); // 自增线程执行完后才执行主线程
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(instance.count);
}
}
上面count.incrementAndGet()方法会使count自增,不需要再额外加锁,该方法内部使用了cas无锁操作,效率更高。
LongAdder
以上知道递增有两种方式:
- 定义Long类型变量,递增时加锁synchronized
- 定义AtomicLong类型变量,使用incrementAndGet()方法自增
- 除上两种外还可用LongAdder
简单用以下程序对比三者效率:
public class T01_increment {
static Long count1 = 0L;
static AtomicLong count2 = new AtomicLong(0L);
static LongAdder count3 = new LongAdder();
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[1000];
/**
* synchronized
*/
final Object lock = new Object();
// 创建一千个线程,每个线程使count++十万次
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 100000; j++) {
synchronized (lock) {
count1++;
}
}
});
}
// 计时
long start = System.currentTimeMillis();
for (Thread t : threads) {
t.start();
}
for (Thread t : threads) {
t.join();
}
long end = System.currentTimeMillis();
System.out.println("synchronized:" + count1 + " time " + (end - start));
/**
* AtomicLong
*/
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 100000; j++) {
count2.incrementAndGet();
}
});
}
// 计时
start = System.currentTimeMillis();
for (Thread t : threads) {
t.start();
}
for (Thread t : threads) {
t.join();
}
end = System.currentTimeMillis();
System.out.println("AtomicLong:" + count1 + " time " + (end - start));
/**
* LongAdder
*/
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 100000; j++) {
count3.increment();
}
});
}
// 计时
start = System.currentTimeMillis();
for (Thread t : threads) {
t.start();
}
for (Thread t : threads) {
t.join();
}
end = System.currentTimeMillis();
System.out.println("LongAdder:" + count1 + " time " + (end - start));
}
}
执行结果:
synchronized:100000000 time 6978
AtomicLong:100000000 time 2098
LongAdder:100000000 time 965
效率对比很明显
为什么Atomic原子类比加synchronized效率高?
synchronized是要加锁的,可能它要去操作系统申请重量级锁,效率偏低。Atomic原子类内部使用了无锁cas操作,效率高。
为什么LongAdder比Atomic原子类效率高?
高并发下N多线程同时去操作一个变量会造成大量线程CAS失败,然后处于自旋状态,导致严重浪费CPU资源,降低了并发性。既然AtomicLong性能问题是由于过多线程同时去竞争同一个变量的更新而降低的,那么如果把一个变量分解为多个变量,让同样多的线程去竞争多个资源,这就是LongAdder的原理。
LongAdder原理图.png
LongAdder则是内部维护一个Cells数组,每个Cell里面有一个初始值为0的long型变量,在同等并发量的情况下,争夺单个变量的线程会减少,这是变相的减少了争夺共享资源的并发量,另外多个线程在争夺同一个原子变量时候,如果失败并不是自旋CAS重试,而是尝试获取其他原子变量的锁,最后当获取当前值时候是把所有变量的值累加后再加上base的值返回的。
更详细原理参考:LongAdder原理分析
https://blog.csdn.net/jiangtianjiao/article/details/103844801/
网友评论