[英]Java Thread Safety - Lock an entire static class but only for one method
我实现了一个静态帮助程序类,可以帮助缓存和检索数据库中的一些只读,不可变,非易失性数据。
(剥离)示例:
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
方法的线程安全性以及它们返回的对象保持不可知,因为这些数据永远不会发生变化,并且希望尽快返回它。
如果将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();
}
}
}
不,这不正确。 仅对reloadAllCaches
方法添加synchronized
意味着该方法的调用者必须获取该类的锁,但调用非同步方法的线程仍然可以同时访问该类。 您仍然需要在同一个锁上同步访问器才能确保安全,否则阅读器线程可能看不到最新的更改并将获得过时的数据。 或者,您可以使用ConcurrentHashMap。
如果您希望能够在不锁定集合的情况下重新填充集合,则可以使用不可变集合替换它们。
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
仅用于更新的同步的示例。
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;
}
}
您可以在reloadAllCaches()
对CacheHelper
类对象(CacheHelper.class)应用锁定,而不是在某个代码段中将此锁定应用于此方法,因为我看到的所有方法都是static
,如果使它们全部synchronized
则所有线程都将被阻塞如果任何线程正在访问任何方法。
简单来说,锁只能防止其他线程同时运行相同的方法,它不会提供任何锁定资源而不是类中的任何其他内容,静态或其他。
其他线程将阻止访问该方法,直到具有控件的线程退出该方法。 所有线程仍可免费访问其他任何内容。
如果您需要锁定对象本身的控制权,那么您需要考虑为缓存提供线程安全访问器或某种继承处理。
我的意思是,如果你在这个方法中构造一个新的缓存,并且一旦构造用这些新对象替换缓存助手中的引用对象,那么只需要同步reloadAllCaches方法就可以了。
但是,如果您的目的是重用/回收现有缓存容器,则必须在容器级别使用锁定以防止在缓存被销毁和重建时进行读取。
如果您需要重新加载多个缓存的映射(根据您的示例),那么您可能会发现有必要将缓存的对象抽象出另一层,否则您可能会在重新应用缓存时失去对缓存的同步访问权限。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.