限制对方法的并发访问

发布于 2024-10-27 10:21:07 字数 2070 浏览 2 评论 0原文

我在限制对方法的并发访问时遇到问题。我有一个方法 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 个线程获取字符串,它保证在必要时只有一个线程正在更新字符串。

我的问题是,让所有这些静态是一个好主意吗?如果没有的话为什么?快吗?有更好的方法吗?

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

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

其他信息:

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

@forsvarir 让我思考......我认为他/她是对的。 return getString(); 必须位于同步方法内。乍一看,它似乎可以脱离它,因此线程将能够同时读取它,但是如果一个线程在调用 getString() 时停止运行并且其他线程部分执行 <代码>renewString()?我们可能会遇到这种情况(假设只有一个处理器):

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

  3. 线程2进入同步 阻止并启动 renewString(), 更改原始 String

  4. 线程 1 重新获得控制权 并使用 a 完成 getString 损坏的字符串!!于是复制了一份 旧绳子的一部分和另一根绳子 从新的。

在同步块内进行读取会使一切变得非常慢,因为线程只能一个接一个地访问它。

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

I have a problem with limiting concurrent access to a method. I have a method MyService that can be called from many places at many times. This method must return a String, that should be updated according to some rules. For this, I have an updatedString class. Before getting the String, it makes sure that the String is updated, if not, it updates it. Many threads could read the String at the same time but ONLY ONE should renew the String at the same time if it is out of date.

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

...

This works fine. If I have 7 threads getting the String, it guarantees that, if necessary, ONLY one thread is updating the String.

My question is, is it a good idea to have all this static? Why if not? Is it fast? Is there a better way to do this?

I have read posts like this:
What Cases Require Synchronized Method Access in Java? which suggests that static mutable variables are not a good idea, and static classes either. But I cannot see any dead-lock in the code or a better valid solution. Only that some threads will have to wait until the String is updated (if necessary) or wait for other thread to leave the synchronized block (which causes a small delay).

If the method is not static, then I have a problem because this will not work since the synchronized method acts only for the current instance that the thread is using. Synchronized the method does not work either, it seems that the lock instance-specific and not class-specific.
The other solution could be to have a Singleton that avoids creating more than one instance and then use a single synchronized not-static class, but I do not like this solution too much.

Additional information:

stringNeedRenewal() is not too expensive although it has to read from a database. renewString() on the contrary is very expensive, and has to read from several tables on the database to finally come to an answer. The String needs arbitrary renewal, but this does not happen very often (from once per hour to once per week).

@forsvarir made me think... and I think he/she was right. return getString(); MUST be inside the synchronized method. At a first sight it looks as if it can be out of it so threads will be able to read it concurrently, but what happens if a thread stops running WHILE calling getString() and other thread partially execute renewString()? We could have this situation (assuming a single processor):

  1. THREAD 1 starts getString(). The OS
    starts copying into memory the bytes
    to be returned.
  2. THREAD 1 is stopped by the OS before finishing the copy.

  3. THREAD 2 enters the synchronized
    block and starts renewString(),
    changing the original String in
    memory.

  4. THREAD 1 gets control back
    and finish getString using a
    corrupted String!! So it copied one
    part from the old string and another
    from the new one.

Having the read inside the synchronized block can make everything very slow, since threads could only access this one by one.

As @Jeremy Heiler pointed out, this is an abstract problem of a cache. If the cache is old, renew it. If not, use it. It is better more clear to picture the problem like this instead of a single String (or imagine that there are 2 strings instead of one). So what happens if someone is reading at the same time as someone is modifying the cache?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

橘和柠 2024-11-03 10:21:07

首先,您可以删除锁和同步块,然后简单地使用:

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

这在 UpdatedString.class 对象上进行同步。

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

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

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

First of all, you can remove the lock and the synchronized block and simply use:

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

this synchronizes on the UpdatedString.class object.

Another thing you can do is used double-checked locking to prevent unnecessary waiting. Declare the string to be volatile and:

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

Then, whether to use static or not - it seems it should be static, since you want to invoke it without any particular instance.

罪#恶を代价 2024-11-03 10:21:07

我建议研究 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();
   }
 }

I would suggest looking into a ReentrantReadWriteLock. (Whether or not it is performant is up to you to decide.) This way you can have many read operations occur simultaneously.

Here is the example from the documentation:

 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();
   }
 }
镜花水月 2024-11-03 10:21:07

这不完全是你想要的,而且我不是 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 的作用,可能需要使用乐观的写入方法。每个线程检查是否需要刷新,然后继续在本地执行更新,然后仅在最后将值分配给正在读取的字段(如果遵循此方法,则需要跟踪更新的时间) 。这对于读取来说会更快,因为可以在同步部分之外执行“字符串是否需要更新”的检查。可以使用各种其他方法,具体取决于具体情况......

This isn't exactly what you're after, and I'm not a Java specialist, so take this with a pinch of salt :)

Perhaps the code sample you've provided is contrived, but if not, I'm unclear what the purpose of the class is. You only want one thread to update the string to it's new value. Why? Is it to save effort (because you'd rather use the processor cycles on something else)? Is it to maintain consistentcy (once a certain point is reached, the string must be updated)?

How long is the cycle between required updates?

Looking at your code...

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?
}

The right locking / optimizations depend upon the reason that you're putting them in place, the likelyhood of a write being required and as Paulo has said, the cost of the operations involved.

For some situations where writes are rare, and obviously depending upon what renewString does, it may be desirable to use an optimistic write approach. Where each thread checks if a refresh is required, proceeds to perform the update on a local and then only at the end, assigns the value across to the field being read (you need to track the age of your updates if you follow this approach). This would be faster for reading, since the check for 'does the string need renewed' can be performed outside of the synchronised section. Various other approaches could be used, depending upon the individual scenario...

滥情稳全场 2024-11-03 10:21:07

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

as long as you lock is static, everything else doesn't have to be, and things will work just as they do now

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文