我有一个接受客户端连接的服务器程序。这些客户端连接可以属于许多流。例如,两个或更多客户端可以属于同一个流。在这些流中,我必须传递一条消息,但我必须等到所有流都建立完毕。为此,我保留了以下数据结构。
ConcurrentHashMap<Integer, AtomicLong> conhasmap = new ConcurrentHashMap<Integer, AtomicLong>();
Integer是流ID,Long是客户端编号。为了使给定流的一个线程等到AtomicLong达到特定值,我使用了以下循环。实际上,流的第一个数据包会将流ID和要等待的连接数放入其中。每次连接都会减少连接以等待。
while(conhasmap.get(conectionID) != new AtomicLong(0)){
// Do nothing
}
然而,这个循环会阻塞其他线程。根据这个 answer它会进行不稳定的读取。如何修改代码以等待给定流的正确线程,直到达到特定值?
答案 0 :(得分:3)
如果您使用的是Java 8,CompletableFuture
可能非常合适。这是一个完整的,人为的示例,它等待5个客户端连接并向服务器发送消息(使用带有商品/投票的BlockingQueue
进行模拟)。
在此示例中,当达到预期的客户端连接消息计数时,将完成CompletableFuture
挂钩,然后在您选择的任何线程上运行任意代码。
在此示例中,您没有任何复杂的线程等待/通知设置或忙等待循环。
package so.thread.state;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
public class Main {
public static String CONNETED_MSG = "CONNETED";
public static Long EXPECTED_CONN_COUNT = 5L;
public static ExecutorService executor = Executors.newCachedThreadPool();
public static BlockingQueue<String> queue = new LinkedBlockingQueue<>();
public static AtomicBoolean done = new AtomicBoolean(false);
public static void main(String[] args) throws Exception {
// add a "server" thread
executor.submit(() -> server());
// add 5 "client" threads
for (int i = 0; i < EXPECTED_CONN_COUNT; i++) {
executor.submit(() -> client());
}
// clean shut down
Thread.sleep(TimeUnit.SECONDS.toMillis(1));
done.set(true);
Thread.sleep(TimeUnit.SECONDS.toMillis(1));
executor.shutdown();
executor.awaitTermination(1, TimeUnit.SECONDS);
}
public static void server() {
print("Server started up");
// track # of client connections established
AtomicLong connectionCount = new AtomicLong(0L);
// at startup, create my "hook"
CompletableFuture<Long> hook = new CompletableFuture<>();
hook.thenAcceptAsync(Main::allClientsConnected, executor);
// consume messages
while (!done.get()) {
try {
String msg = queue.poll(5, TimeUnit.MILLISECONDS);
if (null != msg) {
print("Server received client message");
if (CONNETED_MSG.equals(msg)) {
long count = connectionCount.incrementAndGet();
if (count >= EXPECTED_CONN_COUNT) {
hook.complete(count);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
print("Server shut down");
}
public static void client() {
queue.offer(CONNETED_MSG);
print("Client sent message");
}
public static void allClientsConnected(Long count) {
print("All clients connected, count: " + count);
}
public static void print(String msg) {
System.out.println(String.format("[%s] %s", Thread.currentThread().getName(), msg));
}
}
你得到这样的输出
[pool-1-thread-1] Server started up
[pool-1-thread-5] Client sent message
[pool-1-thread-3] Client sent message
[pool-1-thread-2] Client sent message
[pool-1-thread-6] Client sent message
[pool-1-thread-4] Client sent message
[pool-1-thread-1] Server received client message
[pool-1-thread-1] Server received client message
[pool-1-thread-1] Server received client message
[pool-1-thread-1] Server received client message
[pool-1-thread-1] Server received client message
[pool-1-thread-4] All clients connected, count: 5
[pool-1-thread-1] Server shut down
答案 1 :(得分:2)
你的表达:
conhasmap.get(conectionID) != new AtomicLong(0)
将始终为true,因为您正在比较永远不会相等的对象引用,而不是值。更好的表达方式是:
conhasmap.get(conectionID).longValue() != 0L)
,但是在循环中没有等待/通知逻辑的这样循环不是一个好习惯,因为它不断地使用CPU时间。相反,每个线程应该在AtomicLong实例上调用.wait(),当它递减或递增时,你应该在AtomicLong实例上调用.notifyAll()来唤醒每个等待的线程来检查表达式。 AtomicLong类可能已经在修改时调用notifyAll()方法,但我不确定。
AtomicLong al = conhasmap.get(conectionID);
synchronized(al) {
while(al.longValue() != 0L) {
al.wait(100); //wait up to 100 millis to be notified
}
}
在递增/递减的代码中,它将如下所示:
AtomicLong al = conhasmap.get(conectionID);
synchronized(al) {
if(al.decrementAndGet() == 0L) {
al.notifyAll();
}
}
我个人不会使用AtomicLong作为此计数器,因为您没有从AtomicLong的无锁行为中受益。只需使用java.lang.Long,因为无论如何都需要在wait()/ notify()逻辑的计数器对象上进行同步。