0

Is is okay to synchronize all methods which mutate the state of an object, but not synchronize anything which is atomic? In this case, just returning a field?

Consider:

public class A
{
   private int a = 5;

   private static final Object lock = new Object();

   public void incrementA()
   {
       synchronized(lock)
       {
           a += 1;
       }
   }

   public int getA()
   {
       return a;
   }
}

I've heard people argue that it's possible for getA() and incrementA() to be called at roughly the same time and have getA() return to wrong thing. However it seems like, in the case that they're called at the same time, even if the getter is synchronized you can get the wrong thing. In fact the "right thing" doesn't even seem defined if these are called concurrently. The big thing for me is that the state remains consistent.

I've also heard talk about JIT optimizations. Given an instance of the above class and the following code(the code would be depending on a to be set in another thread):

while(myA.getA() < 10)
{
    //incrementA is not called here
}

it is apparently a legal JIT optimization to change this to:

int temp = myA.getA();
while(temp < 10)
{
    //incrementA is not called here
}

which can obviously result in an infinite loop. Why is this a legal optimization? Would this be illegal if a was volatile?

Update

I did a little bit of testing into this.

public class Test
{
   private int a = 5;

   private static final Object lock = new Object();

   public void incrementA()
   {
       synchronized(lock)
       {
           a += 1;
       }
   }

   public int getA()
   {
       return a;
   }

   public static void main(String[] args)
   {
       final Test myA = new Test();
       Thread t = new Thread(new Runnable(){
        public void run() {
            while(true)
            {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                myA.incrementA();
            }
        }});
       t.start();
       while(myA.getA() < 15)
       {
           System.out.println(myA.getA());
       }
   }
}

Using several different sleep times, this worked even when a is not volatile. This of course isn't conclusive, it still may be legal. Does anyone have some examples that could trigger such JIT behaviour?

Cruncher
  • 7,641
  • 1
  • 31
  • 65
  • It seems your example is bad. Your setA method does not reference the member variable a. you are synchronizing on a lock and doing math with the argument provided to the setA method. – Brett Okken Apr 03 '14 at 15:03
  • @BrettOkken wrote the example pretty quick. I mean this. Thanks – Cruncher Apr 03 '14 at 15:05
  • That test can be very misleading @cruncher. For one thing, `System.out.println(...)` is a synchronized method so you have artificial synchronization there. Writing a quick test to trigger JIT behavior is notoriously difficult to do. – Gray Apr 03 '14 at 16:27

5 Answers5

8

Is is okay to synchronize all methods which mutate the state of an object, but not synchronize anything which is atomic? In this case, just returning a field?

Depends on the particulars. It is important to realize that synchronization does two important things. It is not just about atomicity but it is also required because of memory synchronization. If one thread updates the a field, then other threads may not see the update because of memory caching on the local processor. Making the int a field be volatile solves this problem. Making both the get and the set method be synchronized will as well but it is more expensive.

If you want to be able to change and read a from multiple threads, the best mechanism is to use an AtomicInteger.

private AtomicInteger a = new AtomicInteger(5);
public void setA(int a) {
   // no need to synchronize because of the magic of the `AtomicInteger` code
   this.a.set(a);
}
public int getA() {
    // AtomicInteger also takes care of the memory synchronization
    return a.get();
}

I've heard people argue that it's possible for getA() and setA() to be called at roughly the same time and have getA() return to wrong thing.

This is true but you can get the wrong value if getA() is called after setA() as well. A bad cache value can stick forever.

which can obviously result in an infinite loop. Why is this a legal optimization?

It is a legal optimization because threads running with their own memory cache asynchronously is one of the important reasons why you see performance improvements with them. If all memory accesses where synchronized with main memory then the per-CPU memory caches would not be used and threaded programs would run a lot slower.

Would this be illegal if a was volatile?

It is not legal if there is some way for a to be altered – by another thread possibly. If a was final then the JIT could make that optimization. If a was volatile or the get method marked as synchronized then it would certainly not be a legal optimization.

QuickSilver
  • 3,915
  • 2
  • 13
  • 29
Gray
  • 115,027
  • 24
  • 293
  • 354
  • `It is not legal if there is some way for a to be altered.` In my example I have stated that `setA()` is not called in the loop. Is this grounds for the JIT determining that a can't be altered? – Cruncher Apr 03 '14 at 15:01
  • But `a` can be altered by another thread @Cruncher. I don't think the JIT is smart enough to know if other threads have or have not altered it. – Gray Apr 03 '14 at 15:03
  • Well that's my question really. Is if the JIT will assume that it's not referenced in another thread BECAUSE it's not synchronized? – Cruncher Apr 03 '14 at 15:06
  • 1
    @Cruncher: Gray is wrong. The JIT may safely assume that a non-volatile variable is not accessed by another thread, so it might indeed move it out of the loop. – gexicide Apr 03 '14 at 15:07
  • Can I have a reference @gexicide? So if it is not marked as `volatile` then the JIT can make that assumption? That certainly makes sense. – Gray Apr 03 '14 at 15:25
  • @gexicide, Gray: I assume it does this if it's synchronized as well? Or not? I mean if the getter is synchronized, but not volatile, are there still problems? – Cruncher Apr 03 '14 at 15:26
  • If it was synchronized then the optimization would also _not_ be legal @Cruncher. – Gray Apr 03 '14 at 15:28
  • @Gray said, "...Making the get method be synchronized will as well, but..." Just to be clear, _Both_ methods must be synchronized. The rule is: If thread A sets a variable and then releases a lock, then thread B is guaranteed to see the new value after it _acquires_ the same lock. – Solomon Slow Apr 03 '14 at 16:33
4

It's not thread safe because that getter does not ensure that a thread will see the latest value, as the value may be stale. Having the getter be synchronized ensures that any thread calling the getter will see the latest value instead of a possible stale one.

NESPowerGlove
  • 5,496
  • 17
  • 28
  • Can you elaborate on what you mean by stale a little bit? Are you saying that in one thread, even after `setA()` has returned, `getA()` can return incorrectly in the other thread? – Cruncher Apr 03 '14 at 14:59
  • 1
    @Cruncher: Think about caches. Your one core X sets A, but the value is not propagated to main memory but only upated in the cache of X. Now, core Y reads A from main memory and does not read the update from X. Making the variable volatile enforces each write to be propagated to main memory immediately – gexicide Apr 03 '14 at 15:00
  • 1
    @gexicide Interesting. And if both threads ran on the same processor? – Cruncher Apr 03 '14 at 15:02
  • 1
    @Cruncher: Then it might work. But you still would have the problems with optimizations that reorder statements. – gexicide Apr 03 '14 at 15:07
  • @Cruncher As you can see concurrency in Java is quite tricky, I'd recommend the book Java Concurrency in Practice for a full dose of knowledge. – NESPowerGlove Apr 03 '14 at 15:11
  • @NESPowerGlove For the most part I have a pretty good handle on it synchronization. I just don't fully understand what can and can't be optimized away, and potentially make code invalid. – Cruncher Apr 03 '14 at 15:14
0

You basically have two options:

1) Make your int volatile 2) Use an atomic type like AtomicInt

using a normal int without synchronization is not thread safe at all.

gexicide
  • 38,535
  • 21
  • 92
  • 152
0

Your best solution is to use an AtomicInteger, they were basically designed for exactly this use case.

If this is more of a theoretical "could this be done question", I think something like the following would be safe (but still not perform as well as an AtomicInteger):

public class A
{
   private volatile int a = 5;

   private static final Object lock = new Object();

   public void incrementA()
   {
       synchronized(lock)
       {
           final int tmp = a + 1;
           a = tmp;
       }
   }

   public int getA()
   {
       return a;
   }
}
Brett Okken
  • 6,210
  • 1
  • 19
  • 25
  • What's so special about AtomicInteger that would make it perform better? I would think using the littlest amount of synchronization possible for a given problem should be faster than something that works for everything. (kind of like how you can shave corners by writing assembly, that a compiler can't do because it's general) – Cruncher Apr 03 '14 at 15:15
  • It uses CAS instructions natively. Check this ibm article for info on how the benefits: http://www.ibm.com/developerworks/library/j-jtp11234/ – Brett Okken Apr 03 '14 at 15:45
0

The short answer is your example will be thread-safe, if

  • the variable is declared as volatile, or
  • the getter is declared as synchronized.

The reason that your example class A is not thread-safe is that one can create a program using it that doesn't have a "well-formed execution" (see JLS 17.4.7).

For instance, consider

  // in thread #1
  int a1 = A.getA();

  Thread.sleep(...);

  int a2 = A.getA();

  if (a1 == a2) {
      System.out.println("no increment");

  // in thread #2
  A.incrementA();

in the scenario that the increment happens during the sleep.

For this execution to be well-formed, there must be a "happens before" (HB) chain between the assignment to a in incrementA called by thread #2, and the subsequent read of a in getA called by thread #1.

  • If the two threads synchronize using the same lock object, then there is a HB between one thread releasing the lock and a second thread acquiring the lock. So we get this:

     thread #2 acquires lock --HB-->
     thread #2 reads a --HB-->
     thread #2 writes a --HB-->
     thread #2 releases lock --HB-->
     thread #1 acquires lock --HB-->
     thread #1 reads a
    
  • If two threads share a a volatile variable, there is a HB between any write and any subsequent read (without an intervening write). So we typically get this:

     thread #2 acquires lock --HB-->
     thread #2 reads a --HB-->
     thread #2 writes a --HB-->
     thread #1 reads a
    

    Note that incrementA needs to be synchronized to avoid race conditions with other threads calling incrementA.

  • If neither of the above is true, we get this:

     thread #2 acquires lock --HB-->
     thread #2 reads a --HB-->
     thread #2 writes a                  // No HB!!
     thread #1 reads a
    

    Since there is no HB between the write by thread #2 and the subsequent read by thread #1, the JLS does not guarantee that the latter will see the value written by the former.

Note that this is a simplified version of the rules. For the complete version, you need to read all of JLS Chapter 17.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216