检查锁是否被持有,但如果锁是自由的则不要加锁。

huangapple go评论93阅读模式
英文:

Check if lock is held but not lock it if it is free

问题

我有一个场景,在这个场景中有多个计数器对象。每个计数器对象可以由多个线程同时递增,因此针对所有对象有一组可重入锁,这很有效 - 每个对象在给定时刻只能由一个线程进行修改。

关键是:有一个进程每隔15分钟运行一次,收集所有计数器对象,进行一些计算并清除计数器。此线程不会对任何内容进行锁定,因此会出现以下情况:
1)递增线程正在获取计数器对象并递增一些计数器
2)清除线程正在获取所有计数器对象,进行一些计算并清除计数器
3)清除线程将计数器对象保存到缓存
4)递增线程将计数器对象保存到缓存

在这种情况下,其中一个计数器对象会出现问题,因为最终清除操作被丢弃,计数器的状态与清除前的状态相同。

我想要的是:

  • 所有的递增线程都在特定的计数器对象上进行锁定,因此每个对象只能由一个线程修改,但同时独立的对象可以被多个线程修改,这已经很好地实现了。
  • 当清除线程启动时,它会设置某种标志,所有递增线程都会读取该标志,并且它们必须等到标志被解除为止。

我有备用方案:

  • 清除线程锁定所有对象,但我不喜欢这个想法,因为它可能会花费太长时间,如果在其中一个对象上被阻塞,可能会潜在地阻塞所有线程。
  • 我可以使用for循环清除每个对象上的计数器,但在清除一个对象时,其他对象可能会被修改,这对我来说不是理想的情况。

正如您所见,我有一些选项,但我想知道是否有更好的方法来实现这一点。

更新
有人要求提供代码,以下是代码示例,其中包含一个方法,用于递增对象上的计数器。

public void sipIncomingCall(String objName) {
    try {
        lock(objName);
        Stats stat = getStatisticsForObj(objName);
        long l = stat.getSipIncomingConnections().incrementAndGet();
        stat.getSipConnectionsSum().incrementAndGet();
        LOGGER.debug("incrementing sip incoming connections to {}, objName {}", l, objName);
        putStatisticsForObj(objName, stat);
    } finally {
        unlock(objName);
    }
}

lock()unlock() 方法

private Map<String, ReentrantLock> locks = new ConcurrentHashMap<>();
protected void lock(String key) {
    ReentrantLock lock = locks.getOrDefault(key, new ReentrantLock());
    lock.lock();
}

protected void unlock(String key) {
    ReentrantLock lock = locks.get(key);
    if (lock != null) {
        lock.unlock();
    }
}

getStatisticsForObj()putStatisticsForObj() 方法

private MgcfStats getStatisticsForObj(String tgName) {
    // 从本地缓存(或hazelcast)获取对象
    return Cluster.getTgStatistics(tgName);
}

private void putStatisticsForObj(String tgName, MgcfStats stats) {
    // 保存到本地缓存和hazelcast
    Cluster.putTgStatistics(tgName, stats);
}

以下是来自清除线程的片段它将所有统计对象复制到本地映射然后在Cluster中清除统计数据

statisticsData.setObjStats(new HashMap<>(Cluster.getTgStatistics()));
Cluster.clearTgStatistics();
英文:

I have scenario where i have multiple counters object. Each counters object can be incremented by multiple threads at the same time so there is a set of ReentrantLock for all objects and this works good - each object can be modified only by one thread at given moment.

Here's the catch: there is a process which runs every 15 minutes and collects all counters object, do some calculations and clears counters. This thread is not locking anything so there are situations as below:

  1. incrementing_thread is fetching counters object and incrementing some counters
  2. clearing_thread is fetching all counter objects, do some calculations and clears counters
  3. clearing_thread saves counters objects to cache
  4. incrementing_thread saves counters object to cache

In such situations one of the counters object is messed up because at the end clearing operation is discarded and the state of counters are same as before clearing.

What i want to:

  • All incrementing_threads are locking on specific counters object so each object can be modified only by one thread but at the same time independent object can be modified by multiple thread and this work already great.
  • When clearing_thread starts it sets some sort of flag which is read by all incrementing_threads and they have to wait until the flag is dismissed.

I have backup plans:

  • clearing_thread lock on all objects but i don't like this idea because it can take too long and if it block on one of the object it could potentially block all threads.
  • I could clear counters in for loop for each object but then while clearing one object the other objects can be modified and this is not ideal for me.

As you can see i have some options but i'm wondering if there is better way to do this.

UPDATE
I was asked for the code so there it is.

Below example of one of the methods that increments counters on object.

public void sipIncomingCall(String objName) {
try {
lock(objName);
Stats stat = getStatisticsForObj(objName);
long l = stat.getSipIncomingConnections().incrementAndGet();
stat.getSipConnectionsSum().incrementAndGet();
LOGGER.debug(&quot;incrementing sip incoming connections to {}, objName {}&quot;, l, objName);
putStatisticsForObj(objName, stat);
}finally {
unlock(objName);
}
}

lock() and unlock() methods:

private Map&lt;String,ReentrantLock&gt; locks = new ConcurrentHashMap&lt;&gt;();
protected void lock(String key) {
ReentrantLock lock = locks.getOrDefault(key, new ReentrantLock());
lock.lock();
}
protected void unlock(String key){
ReentrantLock lock = locks.get(key);
if(lock!=null){
lock.unlock();
}
}

methods getStatisticsForObj() and putStatisticsForObj():

private MgcfStats getStatisticsForObj(String tgName) {
//get object from local cache (or hazelcast)
return Cluster.getTgStatistics(tgName);
}
private void putStatisticsForObj(String tgName,MgcfStats stats){
//saving to local cache and hazelcast
Cluster.putTgStatistics(tgName,stats);
}

Below is fragment from "clearing_thread" which copies all statistics objects to local map and then clearing statistics in Cluster:

    statisticsData.setObjStats(new HashMap&lt;&gt;(Cluster.getTgStatistics()));
Cluster.clearTgStatistics();

答案1

得分: 3

你可以使用 ReadWriteLock

  • 增加数值的线程在增加值之前获取读取锁。
  • 清理线程获取写入锁。

你仍然需要为每个计数器使用单独的锁。

英文:

You may use ReadWriteLock.

  • Incrementing threads acquire read lock before incrementing value.
  • Cleaning thread acquire write lock.

You still need individual locks for each counter.

huangapple
  • 本文由 发表于 2020年9月23日 16:08:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/64023620.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定