将AtomicInteger对象作为方法的局部变量, 传递给其他线程, 读写操作是否是线程安全的?

场景

在main线程中, 有一个方法名为triggerSomeThreadWithMethodLocalVariable, 该方法会启动一些线程并且带着该方法里的一个类型为AtomicInteger的局部变量(一般写代码是把线程共享的变量作为类的成员变量),每个线程对该变量的局部变量atomicInteger进行写操作, atomicInteger变量是否在线程间持续可见并且线程安全?

代码

 /**
 * @author rhyme
 */
@Slf4j
public class MethodLocalVariableMain {
  public static void main(String[] args) {
    final MethodLocalVariableMain methodLocalVariableMain = new MethodLocalVariableMain();
    final AtomicInteger atomicInteger = new AtomicInteger(10);
    methodLocalVariableMain.triggerSomeThreadWithMethodLocalVariable(atomicInteger);
  }

  public void triggerSomeThreadWithMethodLocalVariable(AtomicInteger atomicInteger) {
    final int threadCount = 10;
    CompletableFuture[] completableFutures = new CompletableFuture[threadCount];
    for (int i = 0; i < threadCount; i++) {
      completableFutures[i] =
          CompletableFuture.runAsync(
              () -> {
                try {
                  TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                  log.error("InterruptedException happen when TimeUnit.SECONDS.sleep(3);", e);
                  Thread.currentThread().interrupt();
                }
                log.info(
                    "ThreadName: {}, atomicInteger.decrementAndGet(): {}.",
                    Thread.currentThread().getName(),
                    atomicInteger.decrementAndGet());
              });
    }

    // 等待所有CompletableFuture线程执行完毕
    CompletableFuture.allOf(completableFutures).join();
    log.info(
        "In main thread, after all completableFuture is finished, threadName: {}, atomicInteger.get(): {}.",
        Thread.currentThread().getName(),
        atomicInteger.get());
  }
}

上述代码流程见上面的"场景"描述.

运行结果

在4核8逻辑处理测试结果如下:

ThreadName: ForkJoinPool.commonPool-worker-1, atomicInteger.decrementAndGet(): 3.
ThreadName: ForkJoinPool.commonPool-worker-7, atomicInteger.decrementAndGet(): 5.
ThreadName: ForkJoinPool.commonPool-worker-3, atomicInteger.decrementAndGet(): 6.
ThreadName: ForkJoinPool.commonPool-worker-6, atomicInteger.decrementAndGet(): 4.
ThreadName: ForkJoinPool.commonPool-worker-2, atomicInteger.decrementAndGet(): 9.
ThreadName: ForkJoinPool.commonPool-worker-5, atomicInteger.decrementAndGet(): 7.
ThreadName: ForkJoinPool.commonPool-worker-4, atomicInteger.decrementAndGet(): 8.
ThreadName: ForkJoinPool.commonPool-worker-3, atomicInteger.decrementAndGet(): 0.
ThreadName: ForkJoinPool.commonPool-worker-7, atomicInteger.decrementAndGet(): 2.
ThreadName: ForkJoinPool.commonPool-worker-1, atomicInteger.decrementAndGet(): 1.
In main thread, after all completableFuture is finished, threadName: main, atomicInteger.get(): 0.

总结

根据结果来看, 被main线程方法传递的atomicInteger变量, 它的value属性在新启动的各个线程是线程安全, 并且value持续可见;

原理是, 传递的是atomicInteger变量的引用, 即多个线程持有的是同一份atomicInteger变量的引用, 并且利用了AtomicInteger的特性(CAS修改被volatile修饰的变量value, 对value操作的原子性以及变量value在多线程间的可见性);

虽然结果是正确的, 但是还是不建议这样使用方法的局部变量, 这样可能会发生线程逃逸等问题;

线程之间的共享变量, 还是应该作为对象的成员变量更好, 这才是常见的规范写法.