您的位置:首页 > 其它

多线程访问静态方法中的静态变量

2017-09-04 00:39 232 查看
背景:近期,项目中遇到一个场景,多线程访问一个数组,从下标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)。简单的说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替换当前变量的值;如果不等,就重新再取一次:

后台同时指出,还有更简单的方法,把错误示范中的条件重置 的 “==” 改为 “>=”  


相互探讨,如有缪误,还望指正。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐