我遇到限制对方法的并发访问的问题。我有一个方法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
需要任意更新,但这种情况不会经常发生(从每小时一次到每周一次)。
return getString();
必须在synchronized方法中。乍一看,它看起来好像可以在它之外,所以线程可以同时读取它,但是如果一个线程停止运行WHILE调用getString()
而其他线程部分执行renewString()
会发生什么?我们可能有这种情况(假设一个处理器):
getString()
。操作系统
开始将字节复制到内存中
将被退回。在完成复制之前,操作系统会停止THREAD 1。
THREAD 2进入同步
阻止并启动renewString()
,
更改原始String
存储器中。
getString
腐败String
!!所以它复制了一个
部分来自旧的字符串和另一个
来自新的。在synchronized块中读取内容会使一切变得非常慢,因为线程只能逐个访问它。
正如@Jeremy Heiler所指出的,这是一个缓存的抽象问题。如果缓存已旧,请续订。如果没有,请使用它。更清楚地描述这样的问题而不是单个String
(或者想象有2个字符串而不是1个字符串)。那么如果有人在修改缓存的同时阅读会发生什么呢?
答案 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)
只要你锁定是静态的,其他一切都不是必须的,事情会像现在一样工作