簡體   English   中英

java中的意外多線程輸出

[英]unexpected multi thread output in java

public class ConTest {

    @Test
    void name2() {
        final MyCounter myCounter = new MyCounter();
        final Thread t1 = new Thread(() ->
            myCounter.increment()
        );
        final Thread t2 = new Thread(() ->
            myCounter.increment()
        );
        t1.start();
        t2.start();
        System.out.println(myCounter.count);
    }

    @Test
    void name3() {
        final MyCounter myCounter = new MyCounter();
        final ExecutorService service = Executors.newFixedThreadPool(2);

        for (int i = 0; i < 2; i++) {
            service.execute(() -> {
                myCounter.increment();
            });
        }
        System.out.println(myCounter.count);
    }

    static class MyCounter {
        private AtomicLong count = new AtomicLong();

        public void increment() {
            count.incrementAndGet();
        }
    }
}

AtomicLong在多線程時是安全的。

即上面的例子中,是用2個線程執行的,所以不管執行多少次,結果都應該是2。

但是,兩次測試都嘗試了幾次后,結果有時是 1。為什么會發生這種情況?

在打印計數器的值之前,您無需等待任何后台線程或任務結束。 要等待任務退出,您需要為線程添加以下內容:

t1.join();
t2.join();

為服務添加這個,這會阻止添加新任務並等待一段合理的時間讓它們結束:

service.shutdown();
boolean done = awaitTermination(pickSuitablyLongPeriod, TimeUnit.MILLISECONDS);

確保后台任務完成后,運行時應打印正確的結果:

System.out.println(myCounter.count);

不要忘記對 Executors 使用 shutdown()

請參閱此處的評論:

   // Here you start the 2  threads 
    for (int i = 0; i < 2; i++) {
        service.execute(() -> {
            myCounter.increment();
        });
    }

    // we are not sure here that your 2 threads terminate their tasks or not !!

    // the print will be executed by the Main Thread and maybe before the 2 threads terminate their 
    // job ,
    // maybe just one terminate , maybe no one from your 2 threads increment the count .
      System.out.println(myCounter.count);

您可以使用 Future 類,而不是執行您可以使用 submit() ,重運行類型將是類型 Futre<?> (accept void ) ,之后返回 Future 對象,方法 get() 將阻止執行,直到從服務返回的結果:

示例方法 name3() :將始終返回 2

      void name3() {
        final MyCounter myCounter = new MyCounter();
        final ExecutorService service = Executors.newFixedThreadPool(2);
        Future<?> f = null;
        for (int i = 0; i < 2; i++) {
            f =service.submit(() -> {
                myCounter.increment();
            });
            try {
                f.get();
            } catch (InterruptedException | ExecutionException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

         System.out.println(myCounter.count);
         service.shutdown();
    }

這是因為當您調用System.out.println時線程仍在處理。 在這種情況下,您需要在打印出計數器之前阻塞主線程。

Executor的示例中,您可以等待終止:

final ExecutorService service = Executors.newFixedThreadPool(2);
final MyCounter myCounter = new MyCounter();

for (int i = 0; i < 100; i++) {
    service.submit(myCounter::increment);
}
service.shutdown();
while (!service.awaitTermination(100, TimeUnit.MILLISECONDS)) {
    System.out.println("waiting");
}
System.out.println(myCounter.count);

你應該避免阻塞生產代碼,看看發布/訂閱設計模式

除了上述答案之外,您還可以添加一些打印件以更好地了解正在發生的事情。

總之。 您需要等待線程完成執行才能獲得結果,因此這不是AtomicLong的問題。

我修改了代碼,添加了一些打印,這是執行的結果。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;

import org.junit.jupiter.api.Test;

public class ConTest {

  @Test
  void name2() {
    final MyCounter myCounter = new MyCounter();
    final Thread t1 = new Thread(() -> {
      myCounter.increment();
      System.out.println("Counter increment t1 completed and the value is " + myCounter.getCount());
    });
    final Thread t2 = new Thread(() -> {
      myCounter.increment();
      System.out.println("Counter increment t2 completed and the value is " + myCounter.getCount());

    });

    t1.start();
    t2.start();

    System.out.println(myCounter.count.get());
  }

  @Test
  void name3() {
    final MyCounter myCounter = new MyCounter();
    final ExecutorService service = Executors.newFixedThreadPool(2);

    for (int i = 0; i < 2; i++) {
      service.execute(() -> {
        myCounter.increment();
        System.out.println("incrementing for count and the value is " + myCounter.getCount());
      });
    }

    System.out.println(myCounter.count.get());
  }

  class MyCounter {

    private AtomicLong count = new AtomicLong();

    public void increment() {
      count.incrementAndGet();
    }

    public long getCount(){
      return count.get();
    }
  }
}

結果(名稱 2)

1
Counter increment t1 completed and the value is 1
Counter increment t2 completed and the value is 2

結果(名稱 3)

incrementing for count and the value is 1
1
incrementing for count and the value is 2

您還可以使用調試器來更好地理解。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM