views:

257

answers:

5

I am new to multithreading, and I wrote this code which prints the numbers 1-10000 by having concurrently running threads increment and print a variable.

Here's the code I'm using:

package threadtest;

public class Main{

    static int i=0;
    static Object lock=new Object();

    private static class Incrementer extends Thread{

        @Override
        public void run(){
            while (true){
                synchronized(lock){
                        if (i>=10000)
                            break;
                        i++;
                        System.out.println(i);
                }
            }               
        }
    }


    public static void main(String[] args) {
        new Incrementer().start();
        new Incrementer().start();
        new Incrementer().start();
        new Incrementer().start();
        new Incrementer().start();
        new Incrementer().start();
    }
}

This works - I wrote up a test program to check the output, and the numbers printed are exactly 1-10000 in order.

My question is this: I've heard that synchronized is only syntactic sugar. But I can't seem to achieve a successful result without using it. What am I missing?

+10  A: 

synchronized is by no means syntactic sugar for anything. There is no way to work locks in Java without using the synchronized keyword.

Where there is "syntactic sugar" of a sort in locks in Java is that synchronized can apply both to blocks (as you've done it above) and to whole methods. The following two methods are roughly equivalent in semantics:

synchronized void method1() {
  // ... do stuff ...
}

void method2() {
  synchronized(this) {
    // ... do stuff ...
  }
}

So why would you want to do the second version instead of the first?

  • Synchronized method invocations are far slower than plain old method invocations, like by about an order of magnitude. If your synchronized code isn't guaranteed to always execute (say it's in a conditional), then you probably don't want to synchronize the whole method.
  • Synchronized methods hold locks for longer than synchronized blocks (because of all the method setup/tear down code). The second method above will hold the lock for less time because the time spent setting up and tearing down the stack frame won't be locked.
  • You can have much finer control over exactly what you're locking if you go with the synchronized blocks.
  • (Courtesy of starblue) Synchronized blocks can use objects other than this for locking which gives you more flexible locking semantics.
JUST MY correct OPINION
Thanks, that makes sense. Glad to have that cleared up :)
Cam
With a block you can use some private object as the lock, which prevents interference from external code.
starblue
Good point. I'll add that to the answer, starblue.
JUST MY correct OPINION
since java 1.5 you can java.util.concurrent.Lock to guard access to critical regions. This is actually faster than using synchronized in 1.5 and 1.6. So your opening statement is wrong.
Tim Bender
Do you have a reference for those first two bullet points?
Kevin Bourrillion
+1  A: 

It sounds like your sources are just wrong. The syncrhonized keyword is important to use - and use properly - when writing thread-safe code. And it sounds like your own experiments bear this out.

For more on synchronization in Java:

Java Synchronized Methods

Java Locks and Synchronized Statements

Isaac Truett
A: 

Synchronization is one of the most important concepts while programming in multi thread environment. While using synchronization one has to consider the object over which synchronization takes place. For example if a static method is to be synchronized then the synchronization must be on the class level using

synchronized(MyClass.class){
 //code to be executed in the static context
}

if the block in instance method is to be synchronized then the synchronization must be using an instance of an object which is shared between all the threads. Most poeple go wrong with the second point as it appears in your code where the synchronization appears to be on different objects rather than a single object.

frictionlesspulley
+1  A: 

Actually as of Java 5 you (formally) have an alternative set of tools in java.util.concurrent. See here for more details. As detailed in the article the monitor locking model provided at Java's language level has a number of significant limitations and can be difficult to reason about when there are a complex set of interdependent objects and locking relationships making live-lock a real possibility. The java.util.concurrent library offers locking semantics which might be more familiar to programmers who've had experience in POSIX-like systems

bjg
+1  A: 

Of course, "synchronized" is just syntactic sugar - extremley useful syntactic sugar.

If you want sugar-free java programs, you should be writing directly in java byte code the monitorenter, monitorexit, lock, and unlock operations referenced in VM Specifications 8.13 Locks and Synchronization

There is a lock associated with every object. The Java programming language does not provide a way to perform separate lock and unlock operations; instead, they are implicitly performed by high-level constructs that always arrange to pair such operations correctly. (The Java virtual machine, however, provides separate monitorenter and monitorexit instructions that implement the lock and unlock operations.)

The synchronized statement computes a reference to an object; it then attempts to perform a lock operation on that object and does not proceed further until the lock operation has successfully completed. (A lock operation may be delayed because the rules about locks can prevent the main memory from participating until some other thread is ready to perform one or more unlock operations.) After the lock operation has been performed, the body of the synchronized statement is executed. Normally, a compiler for the Java programming language ensures that the lock operation implemented by a monitorenter instruction executed prior to the execution of the body of the synchronized statement is matched by an unlock operation implemented by a monitorexit instruction whenever the synchronized statement completes, whether completion is normal or abrupt.

A synchronized method automatically performs a lock operation when it is invoked; its body is not executed until the lock operation has successfully completed. If the method is an instance method, it locks the lock associated with the instance for which it was invoked (that is, the object that will be known as this during execution of the method's body). If the method is static, it locks the lock associated with the Class object that represents the class in which the method is defined. If execution of the method's body is ever completed, either normally or abruptly, an unlock operation is automatically performed on that same lock.

Best practice is that if a variable is ever to be assigned by one thread and used or assigned by another, then all accesses to that variable should be enclosed in synchronized methods or synchronized statements.

Although a compiler for the Java programming language normally guarantees structured use of locks (see Section 7.14, "Synchronization"), there is no assurance that all code submitted to the Java virtual machine will obey this property. Implementations of the Java virtual machine are permitted but not required to enforce both of the following two rules guaranteeing structured locking.

Let T be a thread and L be a lock. Then:

  1. The number of lock operations performed by T on L during a method invocation must equal the number of unlock operations performed by T on L during the method invocation whether the method invocation completes normally or abruptly.

  2. At no point during a method invocation may the number of unlock operations performed by T on L since the method invocation exceed the number of lock operations performed by T on L since the method invocation.

In less formal terms, during a method invocation every unlock operation on L must match some preceding lock operation on L.

Note that the locking and unlocking automatically performed by the Java virtual machine when invoking a synchronized method are considered to occur during the calling method's invocation.

emory