多线程访问静态方法中的静态变量
程序员文章站
2022-03-23 23:21:39
...
背景:近期,项目中遇到一个场景,多线程访问一个数组,从下标0开始一直到最大长度,然后再从下标0开始,如此循环往复(线程0访问数组下标0,线程1访问数组下标1......)。下标的数值由一个静态变量共享。当时是这么写的,没有考虑多线程的问题:
public class AppUtils {
private final static int LIMIT = 10;
private final static int ORIGIN = 0;
// 共享变量
public static int counter = 0;
// 错误示范
public static int getKeyNumByNext() {
// 条件重置
if (counter == LIMIT) {
counter = ORIGIN;
}
System.out.println(Thread.currentThread().getName() +":"+counter);
counter += 1;
return counter;
}
private CountDownLatch cdl;
@Test
public void test() {
// 模拟并发数
int concurrentNum = 100;
cdl = new CountDownLatch(concurrentNum);
for (int i = 0; i < concurrentNum; i++) {
new Thread(new UserRequest()).start();
cdl.countDown();
}
try {
Thread.currentThread().sleep(5000);
System.out.println("======="+counter);
//System.out.println("======="+atomCounter.get());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
class UserRequest implements Runnable {
@Override
public void run() {
try {
cdl.await();
// 非安全
getKeyNumByNext();
// 安全
//getAtomKeyNumByNext();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
显然,静态变量counter可能在同一时间被多个线程修改,导致条件重置失败(数组下标最大为9,已经有线程到了10+)。
后来,把getKeyNumByNext方法改成了synchronized,保证了变量的线程安全。但是这样肯定会影响性能。于是想起了jdk提供的一种原子操作类型AtomicInteger:
// 原子操作
public static AtomicInteger atomCounter = new AtomicInteger();
public static int getAtomKeyNumByNext() {
atomCounter.incrementAndGet();
atomCounter.compareAndSet(LIMIT,ORIGIN);
System.out.println(Thread.currentThread().getName() +":"+atomCounter.get());
return atomCounter.get();
}
不仅保证了性能,也保证了计数变量atomCounter的线程安全。这里运用到了一个并发处理的技术:CAS(Compare and swap)。简单的说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替换当前变量的值;如果不等,就重新再取一次:
后台同时指出,还有更简单的方法,把错误示范中的条件重置 的 “==” 改为 “>=”
相互探讨,如有缪误,还望指正。