Java多线程-线程安全计数器 [英] Java Multithreading - Threadsafe Counter

查看:854
本文介绍了Java多线程-线程安全计数器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我从一个非常简单的多线程示例开始.我试图做一个线程安全的计数器.我想创建两个线程,使计数器间歇地增加到1000.以下代码:

I'm starting off with a very simple example in multithreading. I'm trying to make a threadsafe counter. I want to create two threads that increment the counter intermittently to reach 1000. Code below:

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.输出:

From what I can tell, the while loop right now means that only the first thread has access to the counter until it reaches 1000. Output:

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

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

How do I fix that? How can I get the threads to share the counter?

推荐答案

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

Both threads have access to your variable.

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

The phenomenon you are seeing is called thread starvation. Upon entering the guarded portion of your code (sorry I missed this earlier), other threads will need to block until the thread holding the monitor is done (i.e. when the monitor is released). Whilst one may expect the current thread pass the monitor to the next thread waiting in line, for synchronized blocks, java does not guarantee any fairness or ordering policy to which thread next recieves the monitor. It is entirely possible (and even likely) for a thread that releases and attempts to reacquire the monitor to get hold of it over another thread that has been waiting for a while.

从Oracle:

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

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.

尽管您的两个线程都是贪婪"线程的示例(因为它们反复释放并重新获取监视器),但从技术上讲,线程0首先启动,因此线程1处于饥饿状态.

Whilst both of your threads are examples of "greedy" threads (since they repeatedly release and reacquire the monitor), thread-0 is technically started first, thus starving thread-1.

解决方案是使用支持公平性的并发同步方法(例如ReentrantLock),如下所示:

The solution is to use a concurrent synchronization method that supports fairness (e.g. ReentrantLock) as shown below:

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.这样的系统具有公平性策略,允许长时间等待的线程有机会执行,从而消除了饥饿.

note the removal of the synchronized keyword in favor of the ReentrantLock within the method. Such a system, with a fairness policy, allows long waiting threads a chance to execute, removing the starvation.

这篇关于Java多线程-线程安全计数器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆