原子類
一、什么是原子類&作用
-
不可分割
-
一個操作是
不可中斷的,即便是多執行緒的情況下也可以保證 -
java.util.concurrent.
atomic
-
作用: 類似與鎖,為保證并發情況下
執行緒安全,原子類相比鎖更具有優勢-
粒度更細:
原子變數可以把競爭范圍縮小到變數級別,這是我們可以獲得的最細粒度的情況,通常鎖的粒度都要比原子變數的粒度大
-
效率更高:
通常,使用原子類的效率會比使用鎖的效率更高,除了高度競爭的情況
-
二、原子類縱覽

三、Atomic*基本型別原子類
包含:AtomicInteger、AtomicLong、AtomicBoolean
以AtomicInteger為例子
1、常用方法
-
public final int get() // 獲取當前的值
-
public final int getAndSet(int newValue) // 獲取當前的值,并設定新的值
-
public final int getAndIncrement() //獲取當前的值,并自增
-
public final int getAndDecrement() // 獲取當前的值,并自減
-
public final int getAndAdd(int delta) // 獲取當前的值,并加上預期的值
-
boolean compareAndSet(int expect,int update) // 如果輸入的數字等于預期值,則以原子方式將該值設定為輸入值(update)
-
代碼演示: 原子類和普通類的對比
/******
@author 阿昌
@create 2021-06-12 18:04
*******
* 演示AtomicInteger的基本用法,并對比非原子類的執行緒安全問題
*/
public class AtomicIntegerDemo1 implements Runnable {
private static final AtomicInteger atomicInteger = new AtomicInteger();
//原子型別自增
public void atomicIncrement(){
atomicInteger.getAndIncrement();
}
private static volatile int basicCount = 0;
//普通型別自增
public void basicIncrement(){
basicCount++;
}
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
atomicIncrement();
basicIncrement();
}
}
//主函式
public static void main(String[] args) throws InterruptedException {
AtomicIntegerDemo1 aid = new AtomicIntegerDemo1();
Thread thread1 = new Thread(aid);
Thread thread2 = new Thread(aid);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("原子類的結果:"+atomicInteger.get());
System.out.println("普通變數值:"+basicCount);
}
}

- 給普通型別增加
synchronized修飾


- 代碼演示: getAndAdd()


四、Atomic*Array陣列型別原子類
/******
@author 阿昌
@create 2021-06-12 18:17
*******
* 演示原子陣列的使用方法
*/
public class AtomicArray {
public static void main(String[] args) {
AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(1000);
Incrementer incrementer = new Incrementer(atomicIntegerArray);
Decrementer decrementer = new Decrementer(atomicIntegerArray);
Thread[] threadsIncrementer = new Thread[100];
Thread[] threadsDecrementer = new Thread[100];
for (int i = 0; i < 100; i++) {
threadsDecrementer[i] = new Thread(decrementer);
threadsIncrementer[i] = new Thread(incrementer);
threadsDecrementer[i].start();
threadsIncrementer[i].start();
}
// Thread.sleep(10000);
for (int i = 0; i < 100; i++) {
try {
threadsDecrementer[i].join();
threadsIncrementer[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int i = 0; i <atomicIntegerArray.length() ; i++) {
if (atomicIntegerArray.get(i)!=0){
System.out.println("發現了錯誤: " +i);
}
}
System.out.println("運行結束");
}
}
//自減任務類
class Decrementer implements Runnable{
private AtomicIntegerArray array;
public Decrementer(AtomicIntegerArray array) {
this.array = array;
}
@Override
public void run() {
for (int i = 0; i < array.length(); i++) {
array.getAndDecrement(i);
}
}
}
//自增任務類
class Incrementer implements Runnable{
private AtomicIntegerArray array;
public Incrementer(AtomicIntegerArray array) {
this.array = array;
}
@Override
public void run() {
for (int i = 0; i < array.length(); i++) {
array.getAndIncrement(i);
}
}
}

五、Atomic*Reference參考型別原子類

- 原始碼,
compareAndSet()

- 這里是之前的自旋鎖演示例子
/******
@author 阿昌
@create 2021-06-11 21:10
*******
* 自旋鎖演示
*/
public class SpinLock {
private AtomicReference<Thread> sign = new AtomicReference<>();
//加鎖操作
public void lock(){
Thread current = Thread.currentThread();
//期待是null,如果是期望的,就將其設定為current
while (!sign.compareAndSet(null,current)){
System.out.println(Thread.currentThread().getName()+":自旋獲取失敗,再次嘗試");
}
}
//解鎖操作
public void unlock(){
Thread current = Thread.currentThread();
//期待加鎖的當前執行緒,如果是期望的,就將其設定為為null,也就是沒有持有了,就是解鎖了
sign.compareAndSet(current,null);
}
public static void main(String[] args) {
SpinLock spinLock = new SpinLock();
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":開始嘗試獲取自旋鎖");
spinLock.lock();
System.out.println(Thread.currentThread().getName() + ":獲取到了自旋鎖");
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
spinLock.unlock();
System.out.println(Thread.currentThread().getName() + ":釋放了自旋鎖");
}
}
};
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
thread1.start();
thread2.start();
}
}
六、AtomicIntegerFieldUpdater升級原子操作
-
AtomicIntegerFieldUpdater對普通變數進行升級 -
使用場景
- 偶爾需要一個原子get/set操作(如晚上某個時刻他存在大量并發修改,其他時刻就正常)
- 這個變數我們無法操作,只能對他進行升級
-
代碼演示
/******
@author 阿昌
@create 2021-06-12 19:02
*******
* 演示AtomicIntegerFieildUpdater的用法
*/
public class AtomicIntegerFieildUpdater implements Runnable {
static Candidate tom;
static Candidate jack;
//newUpdater():引數1指定哪個類,引數2哪個屬性
public static AtomicIntegerFieldUpdater<Candidate> scoreUpdater = AtomicIntegerFieldUpdater.newUpdater(Candidate.class,"score");
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
tom.score++;//普通自增
scoreUpdater.getAndIncrement(jack);//通過包裝自增
}
}
//候選人類
public static class Candidate{
//分數
volatile int score;
}
//主函式
public static void main(String[] args) throws InterruptedException {
tom = new Candidate();
jack = new Candidate();
AtomicIntegerFieildUpdater a = new AtomicIntegerFieildUpdater();
Thread thread1 = new Thread(a);
Thread thread2 = new Thread(a);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("普通自增: "+tom.score);
System.out.println("升級自增: "+jack.score);
}
}

升級后的操作,都會直接作用到原來物件的屬性上:所以直接 jack.score就可

他讓我們傳入類,和對應屬性名,這里就可以感覺到他使用的底層原理是反射
- 注意點
- 不支持
被static修飾的變數 - 可見范圍,
由public修飾的變數,private不行
- 不支持
七、Adder累加器
-
Java8引入
-
高并發下LongAdder比AtomicLong
效率高,本質還是空間換時間 -
競爭激烈的情況下,LongAdder會把不同執行緒對應到不同的Cell上進行修改,降低沖突的概率,是
多段鎖的理念,提高了并發性
1、對比AddderLong & AtomicLong的高并發性能
-
AtomicLong,20個執行緒并發,每個執行緒執行10000次
public class AtomicLongDemo { //主函式 public static void main(String[] args) throws InterruptedException { AtomicLong counter = new AtomicLong(0); //新建執行緒池 ExecutorService pool = Executors.newFixedThreadPool(20); long startTime = System.currentTimeMillis(); //任務次數 for (int i = 0; i < 10000; i++) { pool.submit(new Task(counter)); } //關閉執行緒池 pool.shutdown(); while (!pool.isTerminated()){ } long endTime = System.currentTimeMillis(); System.out.println(counter.get()); System.out.println("AtomicLong完成時間:"+(endTime-startTime)+"毫秒"); } //任務內部類 public static class Task implements Runnable{ private AtomicLong count; public Task(AtomicLong count) { this.count = count; } @Override public void run() { for (int i = 0; i < 10000; i++) { count.incrementAndGet();//自增 } } } }
花費:
1.959s
-
LongAdder,20個執行緒并發,每個執行緒執行10000次
public class LongAdderDemo { //主函式 public static void main(String[] args) throws InterruptedException { LongAdder counter = new LongAdder(); //新建執行緒池 ExecutorService pool = Executors.newFixedThreadPool(20); long startTime = System.currentTimeMillis(); //任務次數 for (int i = 0; i < 10000; i++) { pool.submit(new Task(counter)); } //關閉執行緒池 pool.shutdown(); while (!pool.isTerminated()){ } long endTime = System.currentTimeMillis(); System.out.println(counter.sum()); System.out.println("LongAdder完成時間:"+(endTime-startTime)+"毫秒"); } //任務內部類 public static class Task implements Runnable{ private LongAdder count; public Task(LongAdder count) { this.count = count; } @Override public void run() { for (int i = 0; i < 10000; i++) { count.increment();//自增 } } } }
花費:
0.373s
總結:在多執行緒的情況下,LongAdder比AtomicLong的性能更好
2、為什么AdderLong高并發性能好的原因
- AtomicLong,
每次加法,都需要flush和refresh,導致消耗資源更多,

- LongAdder,每個執行緒他自己有
獨立的計數器

那這里我就覺得就會出現最后執行緒不安全的情況,無法保持一致性,那這里就要講一下他最后的Sum匯總階段

3、Sum原始碼分析
他最后會判斷,如果有as變數也就是cell[]陣列,他就跟base一起相加結果回傳最后的值

上面的原始碼看出,這個遍歷相加的內部沒有保證執行緒安全,也就是說如果之前加好的陣列元素發生了變動,他就不會實時最新的反應在最侄訓傳的sum總和中,也就是說
回傳的sum結果可能不是最新的值
4、AtomicLong & LongAdder對比
- LongAdder
- 消耗更多的空間;
- 在高并發的情況下性能更好;
- 適用于統計求和計數場景;
- 類方法相對較少

八、Accumulator累加器
-
類似與LongAdder,功能更強勁
-
代碼演示:基本用法
public class LongAccumulatorDemo {
public static void main(String[] args) {
//引數1:運算式
//引數2:初始值,對X的第一次定義
//最開始會將初始值賦給X ,y就是之前的結果;類似于 數學歸納法
LongAccumulator accumulator = new LongAccumulator((x, y) -> x + y, 100);
accumulator.accumulate(1);//此時,x=1,y=100,結果為101
accumulator.accumulate(2);//此時,x=2,y=101,結果為103
System.out.println(accumulator.getThenReset());
}
}

- 靈活使用,自定義運算式: 求1加到9中最大的數
public class LongAccumulatorDemo {
public static void main(String[] args) {
//求1加到9中最大的數
LongAccumulator accumulator = new LongAccumulator((x, y) -> Math.max(x,y), 0);
ExecutorService pool = Executors.newFixedThreadPool(10);
//從1加到9
IntStream.range(1,10).forEach(i->pool.submit(()->accumulator.accumulate(i)));
pool.shutdown();
while (!pool.isTerminated()){ }
System.out.println(accumulator.getThenReset());
}
}

- 使用場景:
并行計算,且不要求計算有順序
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/287099.html
標籤:其他
