繁体   English   中英

Java线程安全 - 锁定整个静态类,但仅限于一种方法

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM