Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

>And it's unsafe? For what definition of safety?

Let me try to answer with an example. Let us say, we have original code like this:

  synchronized(this) {
   a();
   b();
 }
 c();
 synchronized(this) {
   d();
   e();
 }
It would be unsafe (in general) to transform the above code to

  synchronized(this) {
    a();
    b();
    c();
    d();
    e();
  }
Simply because Compiler does not know (again, in general) what may happen during the execution of c(). However, the following transformation is safe (the timing behavior changes, but as you point out, that is not a guarantee programmers should expect).

  synchronized(this) {
    p();
    q();
  }
  synchronized(this) {
    r();
    s();
  }
to

  synchronized(this) {
    p();
    q();
    r();
    s();
  }
since there is nothing happening between the two synchronized sections.

For the for loop, an example of code where pulling the synchronized statement out of the loop is problematic:

  for(a = AcquireLock(), c = 0; c < 100; c++) {
    synchronized(this) {
      f();
    }
  }
  ReleaseLock(a);
I don't think it is safe to transform to

  a = AcquireLock();
  synchronized(this) {
  for(c = 0; c < 100; c++) {
      f();
    }
  }
  ReleaseLock(a);
Perhaps you (and the original blog post) assume we are only talking about movement of code after ensuring that such movement is safe, but it was not clear from the document.


Look at it in smaller transformations. Compiler optimizations are kinda like algebra - many small structured steps result in hard, large changes.

  for(a = AcquireLock(), c = 0; c < 100; c++) {
    synchronized(this) {
      f();
    }
  }
  ReleaseLock(a);
Per definition of the for-loop, that's the same as:

  a = AcquireLock();
  c = 0;
  while (c < 100) {
      synchronized(this) { f(); }
      c++;
  }
  ReleaseLock(a);
From there, you can deduce: 'c < 100' and 'c++' don't depend on a and have no side effects besides c. Furthermore, there is no guarantee about two consecutive synchronized blocks actually releasing the lock. Thus, you can safely lift the synchronized block from the loop. However, as AcquireLock and ReleaseLock are function calls with unknown behavior, you can't coarsen the lock further in your example.

Interestingly enough, if you'd talk about 'f(c = 0, g(); c < 100; c++)', the optimizations above might be impossible because you don't know if the call to g modifies c. Unless you can inline g, so you can re-arrange instructions again.


Just looking at your final example, I can't understand how you can say that is unsafe. How would anyone be able to tell the difference between the two? I think anything you tell me I'm just going to be able to answer 'but Java never guaranteed you that in the first place'. If nobody can tell the difference then how can it be unsafe?


>Just looking at your final example, I can't understand how you can say that is unsafe

Let me change the example a bit. Say we have two locks aL and bL, that we must always acquire in the order aL first and then bL.

Following the rule, say we write code like this:

  import java.util.concurrent.locks.ReentrantLock;
  class X {
    private static ReentrantLock aL = new ReentrantLock();
    private static ReentrantLock bL = new ReentrantLock();
    static int x = 0;
    static int c = 0;
    static public void main(String[] args) {
	for(aL.lock(); c < 100; c++) {
	  synchronized(bL) {
		x = x + 0x42;
	  }
	}
	aL.unlock();
    }
  }

If I understood it right, the blog post was asking a question whether JVM can transform this to:

  import java.util.concurrent.locks.ReentrantLock;
  class X {
    private static ReentrantLock aL = new ReentrantLock();
    private static ReentrantLock bL = new ReentrantLock();
    static int x = 0;
    static int c = 0;
    static public void main(String[] args) {
      synchronized(bL) {
        for(aL.lock(); c < 100; c++) {
            x = x + 0x42;
        }
        aL.unlock();
      } // end synnchronized
    }
  }
Since the locks are now acquired in a different order, does that not qualify as observable behavior?


But that's just a different example to the one you gave before. In your previous example acquiring the explicit lock always came before the start of synchronised block, both before and after the rewrite. You've changed it here so it's a different question.


Sorry, I meant to write:

  synchronized(this) {
    a = AcquireLock();
    for(c = 0; c < 100; c++) {
        f();
    }
  }
  ReleaseLock(a);
which is inline with what the blog post was proposing.

To repeat the blog is a question:

  for (...) {
    synchronized (obj) {
      // something
    }
  }
…​could it optimize into this?

  synchronized (this) {
    for (...) {
       // something
    }
  }
My answer to that is in general, no.


You mean because ... could be code that can detect whether or not the monitor is held?

Yes, but I think it's an assumption so obvious as to be not worth stating that the author means as long as ... does not do that.


> My answer to that is in general, no.

Because '...' can include arbitrary side-effect inducing statements that can't be moved around without affecting the behavior. As the poster discovered.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: