Java线程安全-锁定整个静态类,但只锁定一个方法

Java线程安全-锁定整个静态类,但只锁定一个方法,java,multithreading,static,static-methods,Java,Multithreading,Static,Static Methods,我实现了一个静态帮助器类,它帮助缓存和检索数据库中的一些只读、不可变、非易失性数据 (b)例如: 按照我对静态类的阅读方式,如果我将synchronized关键字添加到reloadAllCaches()方法中,则在该方法执行时,会对整个类应用锁。这是正确的吗?(编辑:是的,不正确。感谢您的回复。) 注意:我希望对getter方法及其返回的对象的线程安全性保持不可知状态,因为这些数据从未发生变化,并且希望尽快返回 不,这是不对的。仅将synchronized添加到reloadAllCaches方法

我实现了一个静态帮助器类,它帮助缓存和检索数据库中的一些只读、不可变、非易失性数据

(b)例如:

按照我对静态类的阅读方式,如果我将
synchronized
关键字添加到reloadAllCaches()方法中,则在该方法执行时,会对整个类应用锁。这是正确的吗?(编辑:是的,不正确。感谢您的回复。)


注意:我希望对
getter
方法及其返回的对象的线程安全性保持不可知状态,因为这些数据从未发生变化,并且希望尽快返回

不,这是不对的。仅将
synchronized
添加到
reloadAllCaches
方法意味着该方法的调用方必须获取类的锁,但是调用非同步方法的线程仍然可以并发访问该类。为了安全起见,仍然需要在同一个锁上同步访问器,否则读卡器线程可能看不到最新的更改,并将获得过时的数据。或者,您可以使用ConcurrentHashMap。

而不是在
reloadAllCaches()中的
CacheHelper
类对象(CacheHelper.Class)上应用锁
您可以在此方法内对某段代码应用此锁,因为我看到的所有方法都是
静态的
,如果您使它们都
同步
,那么如果任何线程正在访问任何方法,则所有线程都将被阻止。

如果您将
同步
关键字添加到reloadAllCaches()中函数当reloadAllCaches()函数运行时,类中获得
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
        }
    }
}
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();
        }
    }
}
因此,静态和非静态函数具有不同的同步上下文,并且不会使用synchronized关键字阻止彼此的执行

对于您的情况,我建议使用
ReentrantReadWriteLock
。此类将允许任意数量的函数同时获得读锁,但只允许一个函数获得写锁。只有在没有读锁的情况下才获取写锁,并且只要有写锁,就不会获取读锁

您可以使重载函数获取写锁,使所有读取函数获取写锁。您必须使用原因的
ReentrantReadWriteLock
的静态实例

我的建议是这样实施:

public class Bar
{
    public void foo()
    {
        synchronized (this)
        {
            // your code
        }
    }
}
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();
        }
    }
}

简单地说,锁只防止其他线程同时运行同一个方法,它不为类中的任何其他内容(静态或其他)提供任何锁定资源

其他线程将仅在具有控制权的线程退出该方法之前阻止对该方法的访问。所有线程仍然可以自由访问任何其他内容

如果需要对对象本身进行锁定控制,则需要考虑为线程提供安全的访问器或某种继承处理。

我的意思是,如果您在这个方法中构造一个新的缓存,并且一旦构造完毕,就用这些新对象替换cache helper中的引用对象,那么只需同步reloadAllCaches方法就可以了

但是,如果您打算重用/回收现有缓存容器,则必须在容器级别使用锁定,以防止在销毁和重建缓存时进行读取


如果您需要重新加载多个缓存映射(根据您的示例),则您可能会发现有必要将缓存对象抽象到另一层,否则在重新应用缓存时,您可能无法同步访问缓存。

如果您希望能够重新填充集合而不锁定它们,您可以用不可变的集合替换它们

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将看到一个完全一致的副本,而不需要锁,因为映射总是被替换的,从不被修改


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;
     }
}
最终ConcurrentMap映射=
公共费用对象getOrNull(键){
返回map.get(key);
}
public ExpensiveObject获取或创建(密钥){
同步(地图){
ExpensiveObject ret=map.get(键);
if(ret==null)
map.put(key,ret=新的ExpensiveObject(key));
返回ret;
}
}

您不能使用ConcurrentMap吗?您是否需要立即更新整个映射,或者更新一些值而不更新一些值可以吗?它是清除映射,然后(或多或少)在格式化后将表的全部内容转储到映射中。虽然你的建议是可行的,如果我包装在一些逻辑中只更新实体等,但我宁愿每次都是一个干净的表转储。你不需要为此锁定。构建全新的映射,并将它们分配到
volatile
字段。映射本身可以是普通的
HashMap
s,为了安全起见,可能会包装到
immutableMap
s中。我假设只有一个线程将进行重建。这是正确的,一个线程将生成。在重建它们的过程中,我清除了现有的地图并重新填充。。。。但是你的建议也很有趣,如果我把它作为一个新的地图来构建,那么就把它分配给我