6

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

(剥离)示例:

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方法的线程安全性和它们返回的对象保持不可知论,因为这些数据永远不会发生变异,并且希望它尽快返回。

4

5 回答 5

7

如果将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. 此类将允许任意数量的函数同时获得读锁,但只有一个函数获得写锁。只有在没有读锁到位时才会获取写锁,只要有写锁就不会获取读锁。

你可以让你的重载函数获取一个写锁,而你的所有读取函数都获取一个写锁。您必须使用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();
        }
    }
}
于 2012-07-18T13:26:29.620 回答
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


仅将同步用于更新的示例。

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;
     }
}
于 2012-07-18T13:35:43.257 回答
2

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

于 2012-07-18T13:19:17.557 回答
0

除了在CacheHelper类对象(CacheHelper.class)上应用锁之外,reloadAllCaches()您还可以在此方法中在某些代码上应用此锁,因为我看到的所有方法都是static,如果您全部制作它们,synchronized那么如果任何线程正在访问任何线程,所有线程都将被阻塞方法。

于 2012-07-18T13:24:32.023 回答
0

简单来说,锁只是防止其他线程同时运行相同的方法,它不会为类中的任何其他内容提供任何锁定资源,无论是静态的还是其他的。

只有在拥有控制权的线程退出该方法之前,其他线程才会阻止对该方法的访问。所有线程仍然可以免费访问任何其他内容。

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

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

但是,如果您的意图是重用/回收现有的缓存容器,那么您将不得不在容器级别使用锁定来防止在缓存被破坏和重建时读取。

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

于 2012-07-18T13:33:16.270 回答