繁体   English   中英

如何使线程等待直到变量达到特定值(多线程Java)

[英]How to make a thread wait until a variable reaches a specific value (Multi-threaded Java)

我有一个接受客户端连接的服务器程序。 这些客户端连接可以属于许多流。 例如,两个或更多客户端可以属于同一流。 在这些流中,我必须传递一条消息,但是我必须等待,直到所有流都建立。 为此,我维护以下数据结构。

ConcurrentHashMap<Integer, AtomicLong> conhasmap = new ConcurrentHashMap<Integer, AtomicLong>();

Integer是流ID,Long是客户端号。 为了使给定流的一个线程等待AtomicLong达到特定值,我使用了以下循环。 实际上,流的第一个数据包将其流ID和要等待的连接数放入其中。 对于每个连接,我都会减少等待连接的时间。

while(conhasmap.get(conectionID) != new AtomicLong(0)){
       // Do nothing
}

但是,此循环会阻塞其他线程。 根据此答案,它会进行易失性读取。 如何修改代码以等待给定流的正确线程,直到达到特定值?

如果您使用的是Java 8, CompletableFuture可能是一个不错的选择。 这是一个完整的,人为设计的示例,该示例正在等待5个客户端连接并将消息发送到服务器(使用带有BlockingQueue / Poll的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

您的表情:

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()逻辑的同步。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM