gpt4 book ai didi

java - 从单个线程修改 HashMap 并从多个线程读取?

转载 作者:搜寻专家 更新时间:2023-11-01 03:17:52 25 4
gpt4 key购买 nike

我有一个类,其中我每 30 秒从一个后台线程填充一个 map liveSocketsByDatacenter,然后我有一个方法 getNextSocket,它将被多个调用阅读器线程获取可用的实时套接字,该套接字使用相同的映射来获取此信息。

public class SocketManager {
private static final Random random = new Random();
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
private final Map<Datacenters, List<SocketHolder>> liveSocketsByDatacenter = new HashMap<>();
private final ZContext ctx = new ZContext();

// Lazy Loaded Singleton Pattern
private static class Holder {
private static final SocketManager instance = new SocketManager();
}

public static SocketManager getInstance() {
return Holder.instance;
}

private SocketManager() {
connectToZMQSockets();
scheduler.scheduleAtFixedRate(new Runnable() {
public void run() {
updateLiveSockets();
}
}, 30, 30, TimeUnit.SECONDS);
}

private void connectToZMQSockets() {
Map<Datacenters, ImmutableList<String>> socketsByDatacenter = Utils.SERVERS;
for (Map.Entry<Datacenters, ImmutableList<String>> entry : socketsByDatacenter.entrySet()) {
List<SocketHolder> addedColoSockets = connect(entry.getKey(), entry.getValue(), ZMQ.PUSH);
liveSocketsByDatacenter.put(entry.getKey(), addedColoSockets);
}
}

private List<SocketHolder> connect(Datacenters colo, List<String> addresses, int socketType) {
List<SocketHolder> socketList = new ArrayList<>();
for (String address : addresses) {
try {
Socket client = ctx.createSocket(socketType);
// Set random identity to make tracing easier
String identity = String.format("%04X-%04X", random.nextInt(), random.nextInt());
client.setIdentity(identity.getBytes(ZMQ.CHARSET));
client.setTCPKeepAlive(1);
client.setSendTimeOut(7);
client.setLinger(0);
client.connect(address);

SocketHolder zmq = new SocketHolder(client, ctx, address, true);
socketList.add(zmq);
} catch (Exception ex) {
// log error
}
}
return socketList;
}

// this method will be called by multiple threads to get the next live socket
public Optional<SocketHolder> getNextSocket() {
Optional<SocketHolder> liveSocket = Optional.absent();
List<Datacenters> dcs = Datacenters.getOrderedDatacenters();
for (Datacenters dc : dcs) {
liveSocket = getLiveSocket(liveSocketsByDatacenter.get(dc));
if (liveSocket.isPresent()) {
break;
}
}
return liveSocket;
}

private Optional<SocketHolder> getLiveSocket(final List<SocketHolder> listOfEndPoints) {
if (!CollectionUtils.isEmpty(listOfEndPoints)) {
Collections.shuffle(listOfEndPoints);
for (SocketHolder obj : listOfEndPoints) {
if (obj.isLive()) {
return Optional.of(obj);
}
}
}
return Optional.absent();
}

private void updateLiveSockets() {
Map<Datacenters, ImmutableList<String>> socketsByDatacenter = Utils.SERVERS;

for (Entry<Datacenters, ImmutableList<String>> entry : socketsByDatacenter.entrySet()) {
List<SocketHolder> liveSockets = liveSocketsByDatacenter.get(entry.getKey());
List<SocketHolder> liveUpdatedSockets = new ArrayList<>();
for (SocketHolder liveSocket : liveSockets) {
Socket socket = liveSocket.getSocket();
String endpoint = liveSocket.getEndpoint();
Map<byte[], byte[]> holder = populateMap();

boolean status = SendToSocket.getInstance().execute(3, holder, socket);
boolean isLive = (status) ? true : false;
SocketHolder zmq = new SocketHolder(socket, liveSocket.getContext(), endpoint, isLive);
liveUpdatedSockets.add(zmq);
}
liveSocketsByDatacenter.put(entry.getKey(), liveUpdatedSockets);
}
}
}

正如你在我上面的类(class)中看到的那样:

  • 从一个每 30 秒运行一次的后台线程,我用所有实时套接字填充 liveSocketsByDatacenter 映射。
  • 然后从多个线程中,我调用 getNextSocket 方法来为我提供可用的实时套接字,它使用 liveSocketsByDatacenter 映射来获取所需的信息。

我上面的代码线程安全吗?所有的阅读器线程都能准确地看到 liveSocketsByDatacenter 吗?由于我每 30 秒从一个后台线程修改一次 liveSocketsByDatacenter 映射,然后从许多读取器线程修改一次,因此我调用了 getNextSocket 方法,所以我不确定我是否这样做了这里有什么问题。

看起来我的“getLiveSocket”方法中可能存在线程安全问题,因为每次读取都会从 map 中获取一个共享的 ArrayList 并将其洗牌?而且可能还有一些我可能错过的地方。在我的代码中修复这些线程安全问题的最佳方法是什么?

如果有任何更好的方法来重写它,那么我也愿意接受。

最佳答案

为了线程安全,您的代码必须同步对所有共享可变状态的任何访问。

给大家分享liveSocketsByDatacenter , HashMap 的实例Map非线程安全 实现可以同时读取(通过 updateLiveSocketsgetNextSocket )和修改(通过 connectToZMQSocketsupdateLiveSockets )而不同步任何已经足以使您的代码非线程安全的访问。此外,这个 Map 的值是 ArrayList 的实例List非线程安全 实现也可以同时读取(由 getNextSocketupdateLiveSockets )和修改(由 getLiveSocket 更准确地由 Collections.shuffle )。

解决 2 线程安全问题的简单方法可能是:

  1. 使用ConcurrentHashMap而不是 HashMap对于你的变量 liveSocketsByDatacenter因为它是 Map 的 native 线程安全实现.
  2. 将您的ArrayList不可修改 版本放入使用 Collections.unmodifiableList(List<? extends T> list) 作为 map 值的实例,那么您的列表将是不可变的,因此线程安全。

例如:

liveSocketsByDatacenter.put(
entry.getKey(), Collections.unmodifiableList(liveUpdatedSockets)
);`
  1. 重写你的方法getLiveSocket避免调用 Collections.shuffle直接在您的列表上,例如,您可以仅随机播放 Activity 套接字列表而不是所有套接字,或者使用列表的副本(例如 new ArrayList<>(listOfEndPoints) )而不是列表本身。

例如:

private Optional<SocketHolder> getLiveSocket(final List<SocketHolder> listOfEndPoints) {
if (!CollectionUtils.isEmpty(listOfEndPoints)) {
// The list of live sockets
List<SocketHolder> liveOnly = new ArrayList<>(listOfEndPoints.size());
for (SocketHolder obj : listOfEndPoints) {
if (obj.isLive()) {
liveOnly.add(obj);
}
}
if (!liveOnly.isEmpty()) {
// The list is not empty so we shuffle it an return the first element
Collections.shuffle(liveOnly);
return Optional.of(liveOnly.get(0));
}
}
return Optional.absent();
}

对于 #1,因为您似乎经常阅读并且很少(每 30 秒一次)修改您的 map ,您可以考虑重建您的 map ,然后每 30 秒共享其不可变版本(使用 Collections.unmodifiableMap(Map<? extends K,? extends V> m) ),这种方法是在大部分阅读场景中非常高效,因为您不再需要为访问 map 内容而支付任何同步机制的费用。

您的代码将是:

// Your variable is no more final, it is now volatile to ensure that all 
// threads will see the same thing at all time by getting it from
// the main memory instead of the CPU cache
private volatile Map<Datacenters, List<SocketHolder>> liveSocketsByDatacenter
= Collections.unmodifiableMap(new HashMap<>());

private void connectToZMQSockets() {
Map<Datacenters, ImmutableList<String>> socketsByDatacenter = Utils.SERVERS;
// The map in which I put all the live sockets
Map<Datacenters, List<SocketHolder>> liveSockets = new HashMap<>();
for (Map.Entry<Datacenters, ImmutableList<String>> entry :
socketsByDatacenter.entrySet()) {

List<SocketHolder> addedColoSockets = connect(
entry.getKey(), entry.getValue(), ZMQ.PUSH
);
liveSockets.put(entry.getKey(), Collections.unmodifiableList(addedColoSockets));
}
// Set the new content of my map as an unmodifiable map
this.liveSocketsByDatacenter = Collections.unmodifiableMap(liveSockets);
}

public Optional<SocketHolder> getNextSocket() {
// For the sake of consistency make sure to use the same map instance
// in the whole implementation of my method by getting my entries
// from the local variable instead of the member variable
Map<Datacenters, List<SocketHolder>> liveSocketsByDatacenter =
this.liveSocketsByDatacenter;
...
}
...
// Added the modifier synchronized to prevent concurrent modification
// it is needed because to build the new map we first need to get the
// old one so both must be done atomically to prevent concistency issues
private synchronized void updateLiveSockets() {
// Initialize my new map with the current map content
Map<Datacenters, List<SocketHolder>> liveSocketsByDatacenter =
new HashMap<>(this.liveSocketsByDatacenter);
Map<Datacenters, ImmutableList<String>> socketsByDatacenter = Utils.SERVERS;
// The map in which I put all the live sockets
Map<Datacenters, List<SocketHolder>> liveSockets = new HashMap<>();
for (Entry<Datacenters, ImmutableList<String>> entry : socketsByDatacenter.entrySet()) {
...
liveSockets.put(entry.getKey(), Collections.unmodifiableList(liveUpdatedSockets));
}
// Set the new content of my map as an unmodifiable map
this.liveSocketsByDatacenter = Collections.unmodifiableMap(liveSocketsByDatacenter);
}

你的领域liveSocketsByDatacenter也可以是 AtomicReference<Map<Datacenters, List<SocketHolder>>> 类型, 那么它将是 final ,您的 map 仍将存储在 volatile 中变量但在类内 AtomicReference .

之前的代码将是:

private final AtomicReference<Map<Datacenters, List<SocketHolder>>> liveSocketsByDatacenter 
= new AtomicReference<>(Collections.unmodifiableMap(new HashMap<>()));

...

private void connectToZMQSockets() {
...
// Update the map content
this.liveSocketsByDatacenter.set(Collections.unmodifiableMap(liveSockets));
}

public Optional<SocketHolder> getNextSocket() {
// For the sake of consistency make sure to use the same map instance
// in the whole implementation of my method by getting my entries
// from the local variable instead of the member variable
Map<Datacenters, List<SocketHolder>> liveSocketsByDatacenter =
this.liveSocketsByDatacenter.get();
...
}

// Added the modifier synchronized to prevent concurrent modification
// it is needed because to build the new map we first need to get the
// old one so both must be done atomically to prevent concistency issues
private synchronized void updateLiveSockets() {
// Initialize my new map with the current map content
Map<Datacenters, List<SocketHolder>> liveSocketsByDatacenter =
new HashMap<>(this.liveSocketsByDatacenter.get());
...
// Update the map content
this.liveSocketsByDatacenter.set(Collections.unmodifiableMap(liveSocketsByDatacenter));
}

关于java - 从单个线程修改 HashMap 并从多个线程读取?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41952171/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com