原子操作
定義: 原子操作可以是一個步驟,也可以是多個操作步驟,但是其順序不可以被打亂,也不可以被切割而只執
行其中的一部分
i++便不是原子操作
public class Counter {
volatile int i = 0;
public int getI() {
return i;
}
public void add() {
i++;
}
}
測試代碼
public class Demo1_CounterTest {
public static void main(String[] args) throws InterruptedException {
final Counter ct = new Counter();
for (int i = 0; i < 6; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 10000; j++) {
ct.add();
}
System.out.println("done...");
}
}).start();
}
Thread.sleep(6000L);
System.out.println(ct.getI());
}
}
預計應該是打印i的值爲60000
而實際上
i++ 便存在競態條件
競態條件
當某個計算的正確性取決於多個線程的交替執行時序時,那麼就會發生競態條件.換句話說,就是正確的結果要取決於運氣.
也就是說,如果兩個線程競爭同一資源時,如果對資源的訪問順序敏感, 就稱存在競態條件
解決辦法
1.同步鎖
示例代碼:
public class CounterLock {
volatile int i = 0;
Lock lock = new ReentrantLock();
public void add() {
lock.lock();
i++;
lock.unlock();
}
}
或者
public class CounterSync {
volatile int i = 0;
public synchronized void add() {
i++;
}
}
2.CAS
CAS(Compare and swap) 機制
1.CAS 屬於硬件同步原語,處理器提供的內存操作指令,保證原子性。
2.CAS 操作需要兩個參數,一箇舊值和一個目標值,修改前先比較舊值是否改變,如果沒變,將新值賦給變量,否則不做改變。
3.JAVA中sun.misc.Unsafe類提供了CAS機制
Unsafe使用示例
public class CounterUnsafe {
int i = 0;
public int getI() {
return i;
}
//Unsafe可以修改對象的屬性、可以修改數組等等
private static Unsafe unsafe = null;
//一個偏移量,代表了要修改的字段
private static long valueOffset;
static {
try {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
// 暴力反射
theUnsafe.setAccessible(true);
unsafe = (Unsafe) theUnsafe.get(null);
//指定要修改的字段
Field iFiled = CounterUnsafe.class.getDeclaredField("i");
valueOffset = unsafe.objectFieldOffset(iFiled);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
public void add() {
while (true) {
if (unsafe.compareAndSwapInt(this, valueOffset, i, i + 1)) {
return;
}
}
}
}
CAS存在的問題
1.不能用於多個變量來實現原子操作
2.循環CAS自旋讓所有線程都處於高頻運行,爭搶CPU執行.如果長時間不成功,則會帶來很大的CPU資源消耗
3.ABA問題 (在多線程環境中,使用CAS時,如果一個線程對變量修改2次,第2次修改後的值和第1次修改前的值相同,那麼可能就會出現ABA問題
)
J.U.C包內的原子操作封裝類
常用的數據類型的原子操作封裝類:
AtomicBoolean: 原子更新布爾類型
AtomicInteger:原子更新整型
AtomicLong:原子更新長整型
如果將上面示例代碼的int i = 0 改爲原子操作封裝類
public class CounterAtomic {
AtomicInteger at = new AtomicInteger(0);
public void add(){
// CAS操作進行累加
at.getAndIncrement();
}
public int getValue(){
return at.get();
}
}
則可以打印出和預計值一樣的60000
數組的原子操作封裝類
AtomicIntegerArray:原子更新整型數組裏的元素。
AtomicLongArray:原子更新長整型數組裏的元素。
AtomicReferenceArray:原子更新引用類型數組裏的元素。
更新器
AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
AtomicLongFieldUpdater:原子更新長整型字段的更新器。
AtomicReferenceFieldUpdater:原子更新引用類型裏的字段。
使用示例:
public class Demo_AtomicIntegerFieldUpdater {
// 新建AtomicIntegerFieldUpdater對象,需要指明是哪個類中的哪個字段
private static AtomicIntegerFieldUpdater<User> atom =
AtomicIntegerFieldUpdater.newUpdater(User.class, "id");
public static void main(String[] args) {
User user = new User(100, 100);
atom.addAndGet(user, 50);
System.out.println("atom.addAndGet(user, 50)值變爲:" + user);
}
}
class User {
volatile int id;
volatile int age;
public User(int id, int age) {
this.id = id;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", age=" + age +
'}';
}
}
原子更新引用類型
AtomicReference:原子更新引用類型。
AtomicStampedReference:原子更新帶有版本號的引用類型
AtomicMarkableReference:原子更新帶有標記位的引用類型。
原子計數器
DoubleAdder、 LongAdder:計數器增強版,高併發下性能更好
DoubleAccumulator、 LongAccumulator: 是計數器的增強版,可自定義累加規則
使用示例:
public class Demo_LongAccumulator {
public static void main(String[] args) throws InterruptedException {
LongAccumulator accumulator = new LongAccumulator(
(x,y)->{
// 自定義計數規則
System.out.println("x:" + x);
System.out.println("y:" + y);
return x+y;
},
0L);
for (int i = 0; i < 3; i++) {
accumulator.accumulate(1);
}
System.out.println(accumulator.get());
}
}