1. 程式人生 > 實用技巧 >將AtomicInteger物件作為方法的區域性變數, 傳遞給其他執行緒, 讀寫操作是否是執行緒安全的?

將AtomicInteger物件作為方法的區域性變數, 傳遞給其他執行緒, 讀寫操作是否是執行緒安全的?

將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在多執行緒間的可見性);

雖然結果是正確的, 但是還是不建議這樣使用方法的區域性變數, 這樣可能會發生執行緒逃逸等問題;

執行緒之間的共享變數, 還是應該作為物件的成員變數

更好, 這才是常見的寫法.