Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java Thread Safety - Lock an entire static class but only for one method

I have a Static helper class implemented that helps cache and retreive some read-only, non-mutable, non-volatile data from the database.

(Stripped) Example:

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
    }
}

The way I've read it for static classes, If I add the synchronized keyword to the reloadAllCaches() method, this will apply a lock on the entire class while that method executes. Is this correct? (Edit: Yep, not correct. Thanks for the responses. )

Note: I would like to remain agnostic to the thread safety of the getter methods and the objects they return as this data is never mutated and would like it to be returned as fast as possible.

like image 342
Eric Avatar asked Jul 18 '12 13:07

Eric


1 Answers

If you add the synchronized keyword to the reloadAllCaches() function all other static functions in the class that got the synchronized keyword can't execute while the reloadAllCaches() function is running.

How ever non-static functions can execute, not matter if they got the synchronized keyword or not. Also all other functions without the synchronized keyword can execute.

After all a function with the synchronized can be looked at like:

public class Bar
{
    public static void foo()
    {
        synchronized (Bar.class)
        {
            // your code
        }
    }
}

A non-static function with the synchronized keyword can be looked at like this:

public class Bar
{
    public void foo()
    {
        synchronized (this)
        {
            // your code
        }
    }
}

So static and non-static functions have a different synchronization context and do not block the execution of each other with the synchronized keyword.

For your case I suggest the usage of a ReentrantReadWriteLock. This class will allow any number of functions to get a read-lock at the same time but only one function to get a Write-Lock. The write lock is only acquired when there is no read-lock in place and no read-lock is acquired as long as a write-lock is in place.

You can make your reload function fetching a write-lock and all your reading function fetching a write-lock. You have to use a static instance of the ReentrantReadWriteLock of cause.

My proposal is to implement it like this:

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();
        }
    }
}
like image 92
Nitram Avatar answered Sep 19 '22 12:09

Nitram