简体   繁体   中英

Can a class with getters separate from input processing methods be considered “thread-safe”?

I was reading though a book on Java and there was this exercise question where they declared a class with one private variable, one public void method that did some expensive operation to calculate and then set the private variable, and a second public method to return the private variable. The question was "how can you make this thread-safe" and one possible answer was "synchronize each of the two methods" and one other possible answer was "this class can not be made thread-safe".

I figured the class could not be made thread-safe since even if you synchronize both methods, you could have a situation that Thread1 would invoke the setter and before Thread1 could invoke the getter, Thread2 might execute and invoke the setter, so that when Thread1 went and retrieved the result it would get the wrong info. Is this the right way to look at things? The book suggested the correct answer was that the class could be made thread safe by synchronizing the two methods and now I'm confused...

I figured the class could not be made thread-safe since even if you synchronize both methods, you could have a situation that Thread1 would invoke the setter and before Thread1 could invoke the getter, Thread2 might execute and invoke the setter, so that when Thread1 went and retrieved the result it would get the wrong info. Is this the right way to look at things?

You are correct with this. There is no way to guarantee that a thread will not have called either of the methods in between your calls of each of the methods, from within the class .

If you do want to do this, that will require a wrapper class. So if the class with the getter and setter is like so:

class Foo
{
    private static int bar;

    public static synchronized void SetBar(int z) { ... }
    public static synchronized int GetBar() { ... }
}

The wrapper class would look something like this:

class FooWrapper
{

    public synchronized int SetGetBar(int z)
    {
        Foo.SetBar(z);
        return Foo.GetBar();
    }

}

The only way to guarantee this will work is if you can guarantee that all calls will go through your wrapper class rather than directly to class Foo.

When you make those two synchronized, the getter and setter themselves are thread-safe. More specifically:

  • When you call the setter, you are guaranteed that the value of the variable is what you set it to when the method finishes.
  • When you call the getter, you are guaranteed that the return value is the value of the variable when you made the call.

    However, making the getter and setter themselves thread-safe does not mean that the application as a whole (ie whatever is using this class) is thread-safe. If your thread wants to call a setter then get the same value upon invoking the getter, that involves synchronization on a different level.

    As far as thread-safety is concerned, a thread-safe class need not control how its methods are invoked (for example, it need not control which way the threads interleave their calls), but it needs to ensure that when they are, the methods do what they are supposed to.

  • synchronized in Java is an object-wide lock. Only one synchronized method of any given object can be executed on any given thread at a time. Let's have this class:

    class Foo
    {
        private int bar;
    
        public synchronized void SetBar() { ... }
        public synchronized int GetBar() { ... }
    }
    
    • Thread 1 calls SetBar() . Thread 1 acquires the object lock.
    • Thread 2 wants to call SetBar() , but Thread 1 holds the lock. Thread 2 is now queued to acquire the lock when Thread 1 will release it.
    • Thread 1 finishes executing SetBar() and releases the lock.
    • Thread 2 immediately acquires the lock and starts executing SetBar() .
    • Thread 1 calls GetBar() . Thread 1 is now queued to acquire the lock when Thread 2 will release it.
    • Thread 2 finishes executing SetBar() and releases the lock.
    • Thread 1 acquires the lock, executes GetBar() , and is done with it.

    You did the work twice, but you didn't cause any race condition. It may or may not be erroneous to do the work twice, depending on what it is.

    A frequent pattern is to have one thread produce content and one other thread do something useful with it. This is called the producer-consumer pattern. In this case, there is no confusion over who or what tries to SetBar() and what tries to GetBar() .

    The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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