限制对方法的并发访问

时间:2011-03-29 17:57:34

标签: java concurrency static synchronized

我遇到限制对方法的并发访问的问题。我有一个方法MyService,可以在很多时候从很多地方调用。此方法必须返回String,应根据某些规则进行更新。为此,我有一个updatedString课程。在获得String之前,它会确保更新String,如果没有,则会更新它。许多帖子可以同时阅读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是个好主意吗?为什么不呢?它快吗?有更好的方法吗?

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

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

其他信息:

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

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

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

  3. THREAD 2进入同步 阻止并启动renewString(), 更改原始String 存储器中。

  4. THREAD 1获得控制权 并使用a完成getString 腐败String !!所以它复制了一个 部分来自旧的字符串和另一个 来自新的。
  5. 在synchronized块中读取内容会使一切变得非常慢,因为线程只能逐个访问它。

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

4 个答案:

答案 0 :(得分:2)

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

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();
}

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

答案 1 :(得分:1)

我建议调查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();
   }
 }

答案 2 :(得分:0)

这不完全是你所追求的,而且我不是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?
}

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

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

答案 3 :(得分:-1)

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