【问题标题】:One ReentrantReadWriteLock instance per Variable to be synchronized?每个要同步的变量一个 ReentrantReadWriteLock 实例?
【发布时间】:2015-10-21 12:10:06
【问题描述】:

我打算将一些代码与一些全局变量并行化。 我将使用 ReentrantReadWriteLock。 我是否理解正确,我需要为每个变量创建一个自己的 ReentrantReadWriteLock 实例以确保线程安全?

我的意思是,当我有两个列表时,每个线程都可以附加一个项目,并且所有线程有时都从该列表中读取项目。 在那种情况下,我会实现类似的东西:

private static String[] globalVariables = null;
private static String[] processedItems = null;

private final ReentrantReadWriteLock globalVariablesLock = new ReentrantReadWriteLock();
private final Lock globalVariablesEeadLock  = globalVariablesLock .readLock();
private final Lock globalVariablesWriteLock = globalVariablesLock .writeLock();
private final ReentrantReadWriteLock processedItemsLock = new ReentrantReadWriteLock();
private final Lock processedItemsLockReadLock  = processedItemsLock .readLock();
private final Lock processedItemsLockWriteLock = processedItemsLock .writeLock();

如果我有更多变量,例如数据库连接(池)、记录器、更多列表等,该怎么办?

我需要创建一个新的 ReentrantReadWriteLock 还是我遗漏了什么? 网上的样本只处理一个变量。

提前致谢。

【问题讨论】:

  • 提问时请添加语言标签。不是每个人都来自这里的 Java 世界。
  • 哦,对不起,忘了,谢谢

标签: java multithreading reentrantreadwritelock


【解决方案1】:

你想保护什么?

不要想锁定变量。锁的目的是保护不变量。不变量是您可以对程序的状态做出的一些断言,该断言必须始终为真。例如,“变量 A、B 和 C 的总和始终为零。”

在这种情况下,为 A、B 和 C 设置单独的锁对您没有任何好处。您需要 一个 锁来保护该特定不变量。任何想要更改 A、B 或 C 的线程都必须锁定该锁,并且任何依赖于它们的总和为零的线程都必须锁定同一个锁。

如果不暂时破坏一些不变量,线程通常是不可能取得进展的。例如,

A += 1;    //breaks the invariant
B -= 1;    //fixes it again.

如果没有同步,其他线程可能会检查这两个语句之间的 A、B 和 C,并发现不变量被破坏。

同步:

private final Object zeroSumLock = new Object();

void bumpA() {
    synchronized(zeroSumLock) {
        A += 1;
        B -= 1;
    }
}

boolean verifySum() {
    synchronized(zeroSumLock) {
        return (A+B+C) == 0;
    }
}

【讨论】:

  • 一种用途是预先编译的 sql 语句列表,其中列表用作缓存。该列表被实现为一个项目特定的对象,因此它不仅仅是一个我可以变成并发等价物的 hasmap。线程应访问此列表,获取特定的 sql,准备它,在其上设置一些变量并在 db 上运行它。还有一些线程访问它并且找不到他们需要的 sql。所以他们在运行时编译它(从另一个项目特定的,类似 sql 的语言),并将其添加到缓存中。为此,我认为 reentrantreadwritelock 是一种很好的技术。还有一些像这样的对象。
  • @Kaspatoo 您刚刚描述的内容绝对是不变的:通过锁定您试图保持数据完整性,因此当项目具有部分填充的属性等时,没有线程可以看到处于状态的列表。跨度>
  • @Kaspatoo 如果您正在创建自己的缓存类,为什么不在这些类中封装锁?此类任务的常见做法是使用从调用者的角度以原子方式执行的方法(例如 ConcurrentMap#putIfAbsent())组合 API,保持锁定机制的强封装。
  • 我无法更改这些类,因为我不是它们的所有者,他们不会更改它们,因为他们需要更改与其他团队的任何现有接口协议
  • 所以你对我的问题的回答是对我的对象使用 getter 并对其进行同步,但使用 reentrantreadwritelock 的优点是可以并发读取,但只有一次写入,这就是我不打算同步的原因
【解决方案2】:

是的,每个线程安全变量(在您的情况下为数组)应该有一个 Lock。但是,请考虑使用

ArrayList<String> syncList = Collections.synchronizedList(new ArrayList<String>());

而不是数组。委托给库通常会更好(在这种情况下,不仅是同步,还有数组的调整大小)。当然,在执行此操作之前,请检查该库是否完全符合您的预期(在这种情况下,正如@SashaSalauyou 指出的那样,您将失去并发阅读的能力)。

【讨论】:

  • ReadWriteLock的主要目的是允许并发执行读操作,这比序列化读操作和写操作更有效。使用synchronizedList,您将失去此功能。
  • @SashaSalauyou 嘿,我没想到!谢谢!
  • 感谢您直接回答我最初的问题
【解决方案3】:

其中一个解决方案是创建一个不可变的Map,您可以在其中为您需要的所有项目加锁:

final static Map<String, ReadWriteLock> locks = Collections.unmodifiableMap(
     new HashMap<String, ReadWriteLock>() {{
         put("globalVariables", new ReentrantReadWriteLock());
         put("processedItems",  new ReentrantReadWriteLock());
         // rest items
     }}
);

由于 HashMap 被Collections.unmodifiableMap() 包裹,因此无法修改,因此成为线程安全的。

然后,在代码中:

Lock lo = locks.get("globalVariables").readLock();
lo.acquire();
try {
    // ...
} catch (Exception e) {
    // ...
} finally {
    lo.release();
}

【讨论】:

  • 我不认为这会减少任何事情,另外我需要使常量没有硬编码文本才能获得正确的锁定我的问题更倾向于询问我是否真的需要制作自己的锁,或者如果我误解了这个概念
猜你喜欢
  • 2017-05-18
  • 1970-01-01
  • 2023-03-08
  • 2019-07-07
  • 2015-06-09
  • 2014-06-08
  • 1970-01-01
  • 2023-03-25
  • 2020-07-27
相关资源
最近更新 更多