使用AtomicBoolean的Java循环调度算法

时间:2015-04-13 18:49:33

标签: java multithreading algorithm atomicity atomicboolean

我想在向外部系统发送请求时实施严格的循环调度。有两个外部系统服务器。第一个请求应该转到System1'第二个请求必须转到System2'和下一个到System1'等等。

由于我只有两个服务器来发送请求,并且因为我想要最大性能而没有任何阻塞和上下文切换,所以我已经使用了AtomicBoolean,因为它使用了CAS操作。

我的实施课程

1。 RoundRobinTest.java

package com.concurrency;

import java.util.Iterator;

public class RoundRobinTest 
{
    public static void main(String[] args) 
    {
        for (int i = 0; i < 500; i++) 
        {
            new Thread(new RoundRobinLogic()).start();
        }
        try 
        {
            // Giving a few seconds for the threads to complete
            Thread.currentThread().sleep(2000);
            Iterator<String> output = RoundRobinLogic.output.iterator();
            int i=0;
            while (output.hasNext()) 
            {
                System.out.println(i+++":"+output.next());
                // Sleeping after each out.print 
                Thread.currentThread().sleep(20);
            }
        } 
        catch (Exception ex) 
        {
            // do nothing
        }
    }

}

2.RoundRobinLogic.java (具有静态AtomicBoolean对象的类)

package com.concurrency;

import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.atomic.AtomicBoolean;

public class RoundRobinLogic implements Runnable 
{
    private static AtomicBoolean bool = new AtomicBoolean(true);

    public static Queue<String> output = new ConcurrentLinkedDeque<>();

    @Override
    public void run() 
    {
        if(bool.getAndSet(false))
        {
            // Sending the request to first system
            output.add("Request to System1");
        }
        else if(!bool.getAndSet(true))
        {
            // Sending the request to first system
            output.add("Request to System2");
        }       
    }

}

输出:


......................
314:Request to System1
315:Request to System2
316:Request to System1
317:Request to System2
318:Request to System1
319:Request to System1
320:Request to System2
321:Request to System2
322:Request to System1
323:Request to System2
324:Request to System1
325:Request to System2
......................

请求318和319已发送到同一服务器,AtomicBoolean在此方案中失败。对于我的应用程序,1000-2000个线程可能一次访问共享对象。 从实践中的Java并发性来看,我已经看到了以下内容。

在高争用级别锁定往往优于原子变量,但更现实 争用级别原子变量优于锁定。这是因为锁通过挂起线程来对争用做出反应, 减少共享内存总线上的CPU使用率和同步流量。 具有低到中等的争用,原子提供更好的可扩展性;锁定提供高争用 更好的争用避免。 (基于CAS的算法在单CPU系统上也优于基于锁定的算法,因为a CAS总是在单个CPU系统上成功,除非线程在中间被抢占的不太可能的情况 读取修改写入操作。)

现在我有以下问题。

  1. 是否有其他有效的非阻塞方式,以实现循环请求发送。
  2. 在激烈争论下,AtomicBoolean可能会失败吗?我的理解是,由于争用很大,性能/吞吐量可能会下降。但在上面的例子中AtomicBoolean失败了。为什么?

3 个答案:

答案 0 :(得分:8)

除了John的答案之外,RoundRobinLogic的更简洁,更有效的实施方法将使用AtomicIntegerAtomicLong。这样就无需将AtomicBoolean的当前值与新值进行比较:

class RoundRobinLogic implements Runnable
{
    private static final AtomicInteger systemIndex = new AtomicInteger(1);

    public static final Queue<String> output = new ConcurrentLinkedDeque<>();

    @Override
    public void run()
    {
        if (systemIndex.incrementAndGet() % 2 == 0) {
            // Sending the request to first system
            output.add("Request to System1");
        } else {
            // Sending the request to second system
            output.add("Request to System2");
        }
    }
}

这将允许您相当容易地将其扩展到其他系统:

class RemoteSystem
{
    private final String name;

    RemoteSystem(String name)
    {
        this.name = name;
    }

    public String name()
    {
        return name;
    }
}

class RoundRobinLogic implements Runnable
{
    private static final AtomicInteger systemIndex = new AtomicInteger(1);

    private static final RemoteSystem[] systems = new RemoteSystem[] {
        new RemoteSystem("System1"),
        new RemoteSystem("System2"),
        new RemoteSystem("System3"),
        new RemoteSystem("System4"),
    };

    public static final Queue<String> output = new ConcurrentLinkedDeque<>();

    @Override
    public void run()
    {
        RemoteSystem system = systems[systemIndex.incrementAndGet() % systems.length];

        // Sending the request to right system
        output.add("Request to " + system.name());
    }
}

答案 1 :(得分:4)

假设您没有使用Queue,而是使用实际系统的api。我看到的问题与:

有关
    if(bool.getAndSet(false))
    {
        // Sending the request to first system
        output.add("Request to System1");
    }
    else if(!bool.getAndSet(true))
    {
        // Sending the request to second system
        output.add("Request to System2");
    }     

如果 两个 条件失败怎么办?怎么可能?想象一下,在输入第一个if时,布尔值为true。然后你尝试将它设置为false,但另一个线程击败你,所以你看到false。然后尝试else if。现在如果你到达那里时else if是假的,但设置为true又买另一个线程怎么办?在这种情况下,两次尝试都将失败。

我会将其重构为:

while(true){
  boolean current = bool.get();
  if(bool.compareAndSet(current, !current){
     if(current){ 
        //send request to first system
     } else {
        //send request to second system
     }
     return;
  }
}

正如Sean Bright所提到的那样,因为你正在添加一个队列,即使你像上面那样实现它,你仍然可能会看到一些无序的值,因为Queue本身不是与AtomicBoolean同步的一部分。 / p>

答案 2 :(得分:1)

由于您的要求基本上是:实现原子操作

  1. 评估并翻转布尔值(或评估模数并在通用n服务器案例中递增计数器)
  2. 根据步骤1的结果将条目插入队列,
  3. 通过使步骤1和2单独进行线程安全,您无法真正实现这一点;你必须同步步骤1和2。

    这是一个应该有效的简单实现:

    import java.util.LinkedList;
    import java.util.Queue;
    
    public class RoundRobinLogic implements Runnable 
    {
        private static boolean bool = true;
        public static final Queue<String> OUTPUT = new LinkedList<String>();
        private static final Object LOCK = new Object();
    
        @Override
        public void run() {
            synchronized (LOCK) {
                OUTPUT.add(bool ? "Request to System1" : "Request to System2");
                bool = !bool;
            }
        }
    }
    

    关于你的问题:

    1. 如果需要同时处理两个高于处理器级别的操作,则无法避免阻塞。 java.util.concurrent.atomic中的类使用机器级原子指令,这就是使用这些类的代码(通常,取决于平台)不需要阻塞的原因。
    2. 在您的实施中,AtomicBoolean没有失败。相反,在读取布尔值和向队列中添加元素之间存在竞争条件。