欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 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)。简单的说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替换当前变量的值;如果不等,就重新再取一次:

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


后台同时指出,还有更简单的方法,把错误示范中的条件重置 的 “==” 改为 “>=”  多线程访问静态方法中的静态变量


相互探讨,如有缪误,还望指正。