我想在向外部系统发送请求时实施严格的循环调度。有两个外部系统服务器。第一个请求应该转到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系统上成功,除非线程在中间被抢占的不太可能的情况 读取修改写入操作。)
现在我有以下问题。
答案 0 :(得分:8)
除了John的答案之外,RoundRobinLogic
的更简洁,更有效的实施方法将使用AtomicInteger
或AtomicLong
。这样就无需将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)
由于您的要求基本上是:实现原子操作
n
服务器案例中递增计数器)和 通过使步骤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;
}
}
}
关于你的问题:
java.util.concurrent.atomic
中的类使用机器级原子指令,这就是使用这些类的代码(通常,取决于平台)不需要阻塞的原因。AtomicBoolean
没有失败。相反,在读取布尔值和向队列中添加元素之间存在竞争条件。