限制对方法的并发访问

我有限制并发访问方法的问题。 我有一个方法MyService ,可以在很多时候从很多地方调用。 此方法必须返回一个String ,应根据某些规则进行更新。 为此,我有一个updatedString类。 在获取String之前,它确保更新String ,如果没有,则更新它。 许multithreading可以同时读取String但是如果它已经过时,只有一个应该同时更新String

 public final class updatedString { private static final String UPstring; private static final Object lock = new Object(); public static String getUpdatedString(){ synchronized(lock){ if(stringNeedRenewal()){ renewString(); } } return getString(); } ... 

这很好用。 如果我有7个线程获取String,它保证,如果需要,只有一个线程正在更新String。

我的问题是,将所有这些都static是一个好主意吗? 为什么不呢? 它快吗? 有一个更好的方法吗?

我读过这样的post: 什么案例需要Java中的同步方法访问? 这表明静态可变变量不是一个好主意,也不是静态类。 但我看不到代码中的任何死锁或更好的有效解决方案。 只有某些线程必须等到String更新(如果需要)或等待其他线程离开同步块(这会导致一个小的延迟)。

如果该方法不是static ,那么我有一个问题,因为这不起作用,因为synchronized方法仅适用于线程正在使用的当前实例。 同步方法也不起作用,似乎是锁具实例特定而不是特定于类。 另一个解决方案可能是让Singleton避免创建多个实例,然后使用单个同步的非静态类,但我不太喜欢这个解决方案。

附加信息:

stringNeedRenewal()虽然必须从数据库中读取,但并不算太贵。 相反, renewString()非常昂贵,并且必须从数据库上的几个表中读取最终得出答案。 String需要任意更新,但这种情况不会经常发生(从每小时一次到每周一次)。

@forsvarir让我想到……我认为他/她是对的。 return getString(); 必须在synchronized方法中。 乍一看,它看起来好像可以在它之外,所以线程可以同时读取它,但是如果一个线程停止运行会发生什么呢?调用getString()和其他线程部分执行renewString() ? 我们可能有这种情况(假设一个处理器):

  1. THREAD 1启动getString() 。 操作系统开始将要返回的字节复制到内存中。
  2. 在完成复制之前,操作系统会停止THREAD 1。

  3. THREAD 2进入synchronized块并启动renewString() ,更改内存中的原始String

  4. THREAD 1获得控制权并使用损坏的String完成getString !! 所以它从旧字符串中复制了一个部分,而从新字符串复制了另一个部分。

在synchronized块中读取内容会使一切变得非常慢,因为线程只能逐个访问。

正如@Jeremy Heiler所指出的,这是一个缓存的抽象问题。 如果缓存已旧,请续订。 如果没有,请使用它。 更清楚地描述像这样的问题而不是单个String (或者想象有2个字符串而不是1个字符串)。 那么,如果有人在修改缓存的同时阅读,会发生什么?

首先,您可以删除锁定和同步块,只需使用:

 public static synchronized String getUpdatedString(){ if(stringNeedRenewal()){ renewString(); } return getString(); } 

这在UpdatedString.class对象上同步。

您可以做的另一件事是使用双重检查锁定以防止不必要的等待。 声明字符串是volatile并且:

 public static String getUpdatedString(){ if(stringNeedRenewal()){ synchronized(lock) { if(stringNeedRenewal()){ renewString(); } } } return getString(); } 

然后,是否使用静态 – 它似乎应该是静态的,因为你想在没有任何特定实例的情况下调用它。

我建议调查一下ReentrantReadWriteLock 。 (是否具有高性能由您决定。)这样您就可以同时进行多次读取操作。

以下是文档中的示例:

  class CachedData { Object data; volatile boolean cacheValid; ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); void processCachedData() { rwl.readLock().lock(); if (!cacheValid) { // Must release read lock before acquiring write lock rwl.readLock().unlock(); rwl.writeLock().lock(); // Recheck state because another thread might have acquired // write lock and changed state before we did. if (!cacheValid) { data = ... cacheValid = true; } // Downgrade by acquiring read lock before releasing write lock rwl.readLock().lock(); rwl.writeLock().unlock(); // Unlock write, still hold read } use(data); rwl.readLock().unlock(); } } 

这不完全是你所追求的,而且我不是Java专家,所以带上一点点盐:)

也许您提供的代码示例是人为的,但如果没有,我不清楚该类的目的是什么。 您只需要一个线程将字符串更新为其新值。 为什么? 是为了省力(因为你宁愿在别的东西上使用处理器周期)? 它是否保持一致性(一旦达到某个点,字符串必须更新)?

所需更新之间的循环有多长?

看着你的代码……

 public final class updatedString { private static final String UPstring; private static final Object lock = new Object(); public static String getUpdatedString(){ synchronized(lock){ // One thread is in this block at a time if(stringNeedRenewal()){ renewString(); // This updates the shared string? } } // At this point, you're calling out to a method. I don't know what the // method does, I'm assuming it just returns UPstring, but at this point, // you're no longer synchronized. The string actually returned may or may // not be the same one that was present when the thread went through the // synchronized section hence the question, what is the purpose of the // synchronization... return getString(); // This returns the shared string? } 

正确的锁定/优化取决于您将它们放置到位的原因,需要写入的可能性以及Paulo所说的所涉及的操作成本。

对于某些写入很少的情况,显然取决于renewString的作用,可能需要使用乐观的写入方法。 如果每个线程检查是否需要刷新,则继续在本地执行更新,然后仅在最后,将值分配给正在读取的字段(如果遵循此方法,则需要跟踪更新的年龄) 。 这对于读取来说会更快,因为可以在同步部分之外执行“确定字符串需要更新”的检查。 可以使用各种其他方法,具体取决于具体情况……

只要你锁定是静态的,其他一切都不是必须的,事情会像现在一样工作