我实现了一个静态帮助程序类,可以帮助缓存和检索数据库中的一些只读,不可变,非易失性数据。
(剥离)示例:
public class CacheHelper
{
private static HashMap foos, bars;
public static Foo getFoo(int fooId) { /* etc etc */ }
public static Bar getBar(int barId) { /* etc etc */ }
public static void reloadAllCaches()
{
//This is where I need it to lock access to all the other static methods
}
}
我为静态类读取它的方式,如果我将synchronized
关键字添加到reloadAllCaches()方法,这将在该方法执行时对整个类应用锁。它是否正确? (编辑:是的,不正确。感谢您的回复。)
注意:我希望对getter
方法及其返回的对象的线程安全保持不可知,因为这些数据永远不会发生变化,并且希望尽快返回。
答案 0 :(得分:6)
如果将synchronized
关键字添加到reloadAllCaches()函数,则在reloadAllCaches()函数运行时,无法执行获得synchronized
关键字的类中的所有其他静态函数。
非静态函数如何执行,无论它们是否具有synchronized
关键字都无关紧要。此外,没有synchronized
关键字的所有其他函数都可以执行。
毕竟可以查看synchronized
的函数:
public class Bar
{
public static void foo()
{
synchronized (Bar.class)
{
// your code
}
}
}
具有synchronized
关键字的非静态函数可以这样查看:
public class Bar
{
public void foo()
{
synchronized (this)
{
// your code
}
}
}
因此,静态和非静态函数具有不同的同步上下文,并且不会使用synchronized关键字阻止彼此的执行。
对于您的情况,我建议使用ReentrantReadWriteLock
。这个类允许任意数量的函数同时获得读锁,但只有一个函数可以获得Write-Lock。只有在没有读锁定的情况下才会获取写锁定,并且只要写锁定到位就不会获取读锁定。
你可以让你的重载函数获取一个写锁定,所有的读取函数都可以获取写锁定。您必须使用ReentrantReadWriteLock
原因的静态实例。
我的建议是像这样实施:
public class CacheHelper
{
private static HashMap foos, bars;
private static java.util.concurrent.locks.ReadWriteLock lock = new java.util.concurrent.locks.ReentrantReadWriteLock();
public static Foo getFoo(int fooId)
{
lock.readLock().lock();
try {
/* etc etc */
} finally {
lock.readLock().unlock();
}
}
public static Bar getBar(int barId)
{
lock.readLock().lock();
try {
/* etc etc */
} finally {
lock.readLock().unlock();
}
}
public static void reloadAllCaches()
{
lock.writeLock().lock();
try {
//This is where I need it to lock access to all the other static methods
} finally {
lock.writeLock().unlock();
}
}
}
答案 1 :(得分:2)
不,这不正确。仅将synchronized
添加到reloadAllCaches
方法意味着该方法的调用者必须获取该类的锁,但调用非同步方法的线程仍然可以同时访问该类。您仍然需要在同一个锁上同步访问器才能确保安全,否则阅读器线程可能看不到最新的更改并将获得过时的数据。或者,您可以使用ConcurrentHashMap。
答案 2 :(得分:2)
如果您希望能够在不锁定集合的情况下重新填充集合,则可以使用不可变集合替换它们。
private static volatile Map foos, bars;
public static Foo getFoo(int fooId) { return foos.get(fooId); }
public static Bar getBar(int barId) { /* etc etc */ }
public static void reloadAllCaches()
{
Map newFoo = ...
// populate newFoo
foos = newFoo;
Map newBar = ...
// populate newBar
bars = newBar;
}
getFoo将看到一个完整的一致副本而不需要锁定,因为Map总是被替换,永远不会被修改。
synchronized
锁定对象而非方法,在这种情况下,您锁定CacheHelper.class
对象
为了尽可能快地获取getter,您可以使用ConcurrentHashMap而不是synchronized
仅使用synchronized进行更新的示例。
final ConcurrentMap<Key, ExpensiveObject> map =
public ExpensiveObject getOrNull(Key key) {
return map.get(key);
}
public ExpensiveObject getOrCreate(Key key) {
synchronized(map) {
ExpensiveObject ret = map.get(key);
if (ret == null)
map.put(key, ret = new ExpensiveObject(key));
return ret;
}
}
答案 3 :(得分:0)
不是在CacheHelper
中对reloadAllCaches()
类对象(CacheHelper.class)应用锁定,而是可以在此方法中对某些代码应用此锁定,因为我看到的所有方法都是static
如果你将它们全部synchronized
,那么如果任何线程正在访问任何方法,那么所有线程都将被阻止。
答案 4 :(得分:0)
简单来说,锁只能防止其他线程同时运行同一个方法,它不会提供任何锁定资源而不是类中的任何其他内容,静态或其他。
其他线程将阻止对该方法的访问,直到具有控制权的线程退出该方法。所有线程都可以免费访问其他任何内容。
如果您需要锁定对象本身的控制权,那么您需要考虑为缓存提供线程安全访问器或某种继承处理。
我的意思是,如果你在这个方法中构造一个新的缓存,并且一旦构造用这些新对象替换缓存助手中的引用对象,那么只需同步reloadAllCaches方法就可以了。
但是,如果您的目的是重用/回收现有的缓存容器,那么您将不得不在容器级别使用锁定来防止在缓存被销毁和重建时进行读取。
如果您需要重新加载多个缓存的地图(根据您的示例),那么您可能会发现有必要将缓存的对象抽象出另一层,否则您可能会在重新应用缓存时失去对缓存的同步访问权。