文章目錄
- 執行緒安全的分辨
- 原子性問題的演示
- CAS方式去解決
- 使用atomic工具類
- Jdk新的計數器
- LongAdder的增強版
- ABA問題的案例分析與解決方法
執行緒安全的分辨
如果一段代碼是執行緒安全的,那么它不存在競態條件,只有當多個執行緒更新共享資源時,才會發生競態條件,
原子性問題的演示
// 兩個執行緒,對 i 變數進行遞增操作
public class LockDemo {
//volatile只能保證執行緒的可見性,不能保證執行緒的原子性.
volatile int i = 0;
public void add() {
i++;// 三個步驟
}
public static void main(String[] args) throws InterruptedException {
LockDemo ld = new LockDemo();
for (int i = 0; i < 2; i++) {
new Thread(() -> {
for (int j = 0; j < 10000; j++) {
ld.add();
}
}).start();
}
Thread.sleep(2000L);
System.out.println(ld.i);
}
}
CAS方式去解決
public class LockDemo1 {
volatile int value = 0;
static Unsafe unsafe; // 直接操作記憶體,修改物件,陣列記憶體....強大的API
private static long valueOffset;
static {
try {
// 反射技識訓取unsafe值
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
// 獲取到 value 屬性偏移量(用于定于value屬性在記憶體中的具體地址)
valueOffset = unsafe.objectFieldOffset(LockDemo1.class
.getDeclaredField("value"));
} catch (Exception ex) {
ex.printStackTrace();
}
}
public void add() {
// TODO xx00
// i++;// JAVA 層面三個步驟
// CAS + 回圈 重試
int current;
do {
// 操作耗時的話, 那么 執行緒就會占用大量的CPU執行時間
current = unsafe.getIntVolatile(this, valueOffset);
} while (!unsafe.compareAndSwapInt(this, valueOffset, current, current + 1));
// 可能會失敗
}
public static void main(String[] args) throws InterruptedException {
LockDemo1 ld = new LockDemo1();
for (int i = 0; i < 2; i++) {
new Thread(() -> {
for (int j = 0; j < 10000; j++) {
ld.add();
}
}).start();
}
Thread.sleep(2000L);
System.out.println(ld.value);
}
}
使用atomic工具類
// 兩個執行緒,對 i 變數進行遞增操作
public class LockDemo {
// volatile int i = 0;
AtomicInteger i = new AtomicInteger(0);
public void add() {
// TODO xx00
// i++;// 三個步驟
i.incrementAndGet();
}
public static void main(String[] args) throws InterruptedException {
LockDemo ld = new LockDemo();
for (int i = 0; i < 2; i++) {
new Thread(() -> {
for (int j = 0; j < 10000; j++) {
ld.add();
}
}).start();
}
Thread.sleep(2000L);
System.out.println(ld.i);
}
}
Jdk新的計數器
// 測驗用例: 同時運行2秒,檢查誰的次數最多
public class LongAdderDemo {
private long count = 0;
// 同步代碼塊的方式
public void testSync() throws InterruptedException {
for (int i = 0; i < 3; i++) {
new Thread(() -> {
long starttime = System.currentTimeMillis();
while (System.currentTimeMillis() - starttime < 2000) { // 運行兩秒
synchronized (this) {
++count;
}
}
long endtime = System.currentTimeMillis();
System.out.println("SyncThread spend:" + (endtime - starttime) + "ms" + " v" + count);
}).start();
}
}
// Atomic方式
private AtomicLong acount = new AtomicLong(0L);
public void testAtomic() throws InterruptedException {
for (int i = 0; i < 3; i++) {
new Thread(() -> {
long starttime = System.currentTimeMillis();
while (System.currentTimeMillis() - starttime < 2000) { // 運行兩秒
acount.incrementAndGet(); // acount++;
}
long endtime = System.currentTimeMillis();
System.out.println("AtomicThread spend:" + (endtime - starttime) + "ms" + " v-" + acount.incrementAndGet());
}).start();
}
}
// LongAdder 方式
private LongAdder lacount = new LongAdder();
public void testLongAdder() throws InterruptedException {
for (int i = 0; i < 3; i++) {
new Thread(() -> {
long starttime = System.currentTimeMillis();
while (System.currentTimeMillis() - starttime < 2000) { // 運行兩秒
lacount.increment();
}
long endtime = System.currentTimeMillis();
System.out.println("LongAdderThread spend:" + (endtime - starttime) + "ms" + " v-" + lacount.sum());
}).start();
}
}
public static void main(String[] args) throws InterruptedException {
LongAdderDemo demo = new LongAdderDemo();
demo.testSync();
demo.testAtomic();
demo.testLongAdder();
}
}
- 執行結果
SyncThread spend:2000ms v21103457
SyncThread spend:2000ms v21103457
SyncThread spend:2000ms v21103457
AtomicThread spend:2000ms v-87109275
LongAdderThread spend:2000ms v-194372483
AtomicThread spend:2000ms v-87919296
LongAdderThread spend:2000ms v-194854387
AtomicThread spend:2000ms v-88120307
LongAdderThread spend:2000ms v-197359712
LongAdder的增強版
// LongAdder增強版,處理累加之外,可以自行定義其他計算
public class LongAccumulatorDemo {
public static void main(String[] args) throws InterruptedException {
LongAccumulator accumulator = new LongAccumulator(new LongBinaryOperator() {
@Override
public long applyAsLong(long left, long right) {
// 回傳最大值,這就是自定義的計算
return left < right ? left : right;
}
}, 0);
// 1000個執行緒
for (int i = 0; i < 1000; i++) {
int finalI = i;
new Thread(() -> {
accumulator.accumulate(finalI); // 此處實際就是執行上面定義的操作
}).start();
}
Thread.sleep(2000L);
System.out.println(accumulator.longValue()); // 列印出結果
}
}
ABA問題的案例分析與解決方法
- ABA問題
// aba 問題
// 重復操作 / 過時操作,
public class AbaDemo {
// 模擬充值
// 有3個執行緒在給用戶充值,當用戶余額少于20時,就給用戶充值20元,
// 有100個執行緒在消費,每次消費10元,用戶初始有9元
static AtomicInteger money = new AtomicInteger(19);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 3; i++) {
new Thread(() -> {
int current;
do {
current = money.get();// 獲取當前記憶體中的值
} while (!money.compareAndSet(current, current + 20)); // CAS
}).start();
}
new Thread(() -> {
for (int i = 0; i < 100; i++) {
while (true) {
Integer m = money.get();
if (m > 10) {
if (money.compareAndSet(m, m - 10)) {
System.out.println("消費10元,余額:" + money.get());
break;
}
} else {
break;
}
}
try {
Thread.sleep(100);
} catch (Exception e) {
// TODO: handle exception
}
}
}).start();
}
}
- 展示結果
消費10元,余額:69
消費10元,余額:59
消費10元,余額:49
消費10元,余額:39
消費10元,余額:29
消費10元,余額:19
消費10元,余額:9
- ABA問題的解決
public class AbaDemo1 {
// 模擬充值
// 有3個執行緒在給用戶充值,當用戶余額少于20時,就給用戶充值20元,
// 有100個執行緒在消費,每次消費10元,用戶初始有9元
static AtomicStampedReference<Integer> money = new AtomicStampedReference<Integer>(19, 0);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 3; i++) {
final int timestamp = money.getStamp();
new Thread(() -> {
while (true) {
while (true) {
Integer m = money.getReference();
if (m < 20) {
if (money.compareAndSet(m, m + 20, timestamp,
timestamp + 1)) {
System.out.println("充值成功,余額:"
+ money.getReference());
break;
}
} else {
break;
}
}
}
}).start();
}
new Thread(() -> {
for (int i = 0; i < 100; i++) {
while (true) {
int timestamp = money.getStamp();
Integer m = money.getReference();
if (m > 10) {
if (money.compareAndSet(m, m - 10, timestamp,
timestamp + 1)) {
System.out.println("消費10元,余額:"
+ money.getReference());
break;
}
} else {
break;
}
}
try {
Thread.sleep(100);
} catch (Exception e) {
// TODO: handle exception
}
}
}).start();
}
}
- 案例結果
充值成功,余額:39
消費10元,余額:29
消費10元,余額:19
消費10元,余額:9
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/198666.html
標籤:其他
上一篇:C語言實作RSA的簡單加解密
