小编典典

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

java

我从一个非常简单的多线程示例开始。我试图做一个线程安全的计数器。我想创建两个线程,使计数器间歇地增加到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

我该如何解决?如何获得共享计数器的线程?


阅读 219

收藏
2020-11-13

共1个答案

小编典典

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

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

从Oracle:

饥饿描述了一种情况,即线程无法获得对共享资源的常规访问并且无法取得进展。当“贪婪”线程使共享资源长时间不可用时,就会发生这种情况。例如,假设一个对象提供了一个同步方法,该方法通常需要很长时间才能返回。如果一个线程频繁调用此方法,则也需要频繁同步访问同一对象的其他线程将经常被阻塞。

虽然您的两个线程都是“贪婪”线程的示例(因为它们反复释放并重新获取监视器),但从技术上讲,线程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
的关键字。这种具有公平性策略的系统允许长时间等待的线程有机会执行,从而消除了饥饿。

2020-11-13