Java 多线程 - 线程安全计数器

Java Multithreading - Threadsafe Counter

我从一个非常简单的多线程示例开始。我正在尝试制作一个线程安全的计数器。我想创建两个线程来间歇性地增加计数器以达到 1000。代码如下:

public class ThreadsExample implements Runnable {
     static int counter = 1; // a global counter

     public ThreadsExample() {
     }

     static synchronized void incrementCounter() {
          System.out.println(Thread.currentThread().getName() + ": " + counter);
          counter++;
     }

     @Override
     public void run() {
          while(counter<1000){
               incrementCounter();
          }
     }

     public static void main(String[] args) {
          ThreadsExample te = new ThreadsExample();
          Thread thread1 = new Thread(te);
          Thread thread2 = new Thread(te);

          thread1.start();
          thread2.start();          
     }
}

据我所知,while 循环现在意味着只有第一个线程可以访问计数器,直到它达到 1000。输出:

Thread-0: 1
.
.
.
Thread-0: 999
Thread-1: 1000

我该如何解决?如何让线程共享计数器?

您可以使用 AtomicInteger。它是一个可以原子递增的class,因此调用其递增方法的两个独立线程不会交错。

public class ThreadsExample implements Runnable {
     static AtomicInteger counter = new AtomicInteger(1); // a global counter

     public ThreadsExample() {
     }

     static void incrementCounter() {
          System.out.println(Thread.currentThread().getName() + ": " + counter.getAndIncrement());
     }

     @Override
     public void run() {
          while(counter.get() < 1000){
               incrementCounter();
          }
     }

     public static void main(String[] args) {
          ThreadsExample te = new ThreadsExample();
          Thread thread1 = new Thread(te);
          Thread thread2 = new Thread(te);

          thread1.start();
          thread2.start();          
     }
}

两个线程都可以访问您的变量。

您看到的现象称为线程饥饿。进入代码的受保护部分后(抱歉,我之前错过了这个),其他线程将需要阻塞,直到持有监视器的线程完成(即当监视器 释放 时)。虽然人们可能期望当前线程将监视器传递给排队等待的下一个线程,但对于同步块,java 不保证任何公平性或顺序策略,即线程下一个接收监视器。 释放并尝试重新获取监视器的线程完全有可能(甚至可能)通过另一个已经等待一段时间的线程来获取它。

来自甲骨文:

Starvation describes a situation where a thread is unable to gain regular access to shared resources and is unable to make progress. This happens when shared resources are made unavailable for long periods by "greedy" threads. For example, suppose an object provides a synchronized method that often takes a long time to return. If one thread invokes this method frequently, other threads that also need frequent synchronized access to the same object will often be blocked.

虽然您的两个线程都是 "greedy" 线程的示例(因为它们反复释放并重新获取监视器),但线程 0 在技术上首先启动,因此使线程 1 处于饥饿状态。

解决方案是使用支持公平的并发同步方式(如ReentrantLock)如下图:

public class ThreadsExample implements Runnable {
    static int counter = 1; // a global counter

    static ReentrantLock counterLock = new ReentrantLock(true); // enable fairness policy

    static void incrementCounter(){
        counterLock.lock();

        // Always good practice to enclose locks in a try-finally block
        try{
            System.out.println(Thread.currentThread().getName() + ": " + counter);
            counter++;
        }finally{
             counterLock.unlock();
        }
     }

    @Override
    public void run() {
        while(counter<1000){
            incrementCounter();
        }
    }

    public static void main(String[] args) {
        ThreadsExample te = new ThreadsExample();
        Thread thread1 = new Thread(te);
        Thread thread2 = new Thread(te);

        thread1.start();
        thread2.start();          
    }
}

请注意删除了 synchronized 关键字以支持方法中的 ReentrantLock。这样一个具有公平策略的系统允许长时间等待的线程有机会执行,从而消除饥饿。

好吧,使用你的代码我不知道如何间歇性地获取 "exactly",但是如果你在调用 incrementCounter() 之后使用 Thread.yield() 你将会有更好的分布。

public void run() {
         while(counter<1000){
              incrementCounter();
              Thread.yield();

         }
    }

否则,要获得您的建议,您可以创建两个不同的线程 class(如果需要,ThreadsExample1 和 ThreadsExample2),另一个 class 作为共享变量。

public class SharedVariable {
    private int value;
    private boolean turn; //false = ThreadsExample1 --> true = ThreadsExample2

    public SharedVariable (){
        this.value = 0;
        this.turn = false;
    }

    public void set (int v){
        this.value = v;
    }

    public int get (){
        return this.value;
    }

    public void inc (){
        this.value++;
    }

    public void shiftTurn(){
        if (this.turn){
            this.turn=false;
        }else{
            this.turn=true;
        }
    }

    public boolean getTurn(){
        return this.turn;
    }

}

现在主要可以是:

public static void main(String[] args) {
        SharedVariable vCom = new SharedVariable();
        ThreadsExample1 hThread1 = new ThreadsExample1 (vCom);
        ThreadsExample2 hThread2 = new ThreadsExample2 (vCom);

        hThread1.start();
        hThread2.start();

        try {
            hThread1.join();
            hThread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

而且你必须换行 static int counter = 1; // a global counter private SharedVariable counter;

而新的 运行 是:

public void run() {
    for (int i = 0; i < 20; i++) {
        while (!counter.getTurno()){
            Thread.yield();
        }
        System.out.println(this.counter.get());
        this.counter.cambioTurno();
    }
}

}

是的,这是另一个代码,但我认为它可以帮助你。