Java 限制对方法的并发访问

Java 限制对方法的并发访问,java,concurrency,static,synchronized,Java,Concurrency,Static,Synchronized,我对限制对方法的并发访问有问题。我有一个方法MyService,可以从很多地方多次调用。此方法必须返回一个字符串,该字符串应根据某些规则进行更新。为此,我有一个updatedString类。在获取字符串之前,它会确保字符串已更新,如果未更新,则会对其进行更新。许多线程可以同时读取字符串,但如果字符串过期,则只有一个线程可以同时续订该字符串 public final class updatedString { private static final String UPstring; priva

我对限制对方法的并发访问有问题。我有一个方法
MyService
,可以从很多地方多次调用。此方法必须返回一个
字符串
,该字符串应根据某些规则进行更新。为此,我有一个
updatedString
类。在获取
字符串
之前,它会确保
字符串
已更新,如果未更新,则会对其进行更新。许多线程可以同时读取
字符串
,但如果
字符串
过期,则只有一个线程可以同时续订该字符串

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个线程获取字符串,它保证在必要时只有一个线程更新字符串

我的问题是,让所有这些
都是静态的
好主意吗?为什么不呢?它快吗?有更好的方法吗?

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

如果该方法不是静态的,那么我就有一个问题,因为同步方法只对线程正在使用的当前实例起作用,所以这将不起作用。同步的方法也不起作用,看起来锁是特定于实例的,而不是特定于类的。 另一个解决方案是使用一个避免创建多个实例的单例,然后使用一个同步的非静态类,但我不太喜欢这个解决方案

其他信息:

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

@forsvarir让我想。。。我认为他/她是对的<代码>返回getString()必须位于同步方法内。乍一看,它似乎可以脱离它,因此线程将能够同时读取它,但如果一个线程在调用
getString()
时停止运行,而另一个线程部分执行
renewString()
,会发生什么情况?我们可能会遇到这种情况(假设使用单处理器):

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

  • 线程2进入同步状态 阻止并启动
    renewString()
    , 更改中的原始
    字符串
    记忆

  • 线程1获得了控制权 然后使用 损坏的
    字符串
    !!所以它复制了一个 从旧弦和另一根弦分离 从新的开始
  • 在同步块中进行读取可能会使一切变得非常缓慢,因为线程只能逐个访问


    正如@Jeremy Heiler指出的,这是缓存的抽象问题。如果缓存已旧,请续订它。如果没有,请使用它。更好的办法是将问题描述成这样,而不是一个
    字符串(或者设想有两个字符串而不是一个)。那么,如果有人在修改缓存的同时进行读取,会发生什么情况呢?

    只要您的锁是静态的,其他一切都不必是静态的,事情就会像现在一样正常运行

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

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

    然后,不管是否使用static,它似乎应该是静态的,因为您希望在没有任何特定实例的情况下调用它。

    我建议您研究一个静态的。(是否执行由您决定。)这样您可以同时执行多个读取操作

    以下是文档中的示例:

     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的功能,可能需要使用乐观的写方法。其中,每个线程检查是否需要刷新,继续在本地执行更新,然后仅在最后将值分配给正在读取的字段(如果采用这种方法,则需要跟踪更新的时间)。这将加快读取速度,因为“字符串是否需要更新”的检查可以在同步部分之外执行。根据具体情况,可以使用各种其他方法…

    我建议您使用私有静态最终对象锁=新对象();保持静态,这样您就可以锁定相同的对象。其他一切都不必是静态的,如果不是