Kav
Kav

Reputation: 3

Different outputs each time this synchronized multithread program is run(Java.)

    class Callme{
    synchronized void call(String msg){
        System.out.print(msg);
        try{
            Thread.sleep(500);
            System.out.println("message");
        }catch(InterruptedException e){
            System.out.println(e);
        }
        
        
    }
}
class Caller implements Runnable{
    String message;
    Thread t;
    Callme target;
    Caller(Callme target, String msg){
        t = new Thread(this);
        this.target = target;
        message = msg;
        t.start();
        
    }
    public void run(){
        target.call(message);
    }
}

class Synch{
    public static void main(String args[]){
        Callme c = new Callme();
        Caller ob1 = new Caller(c,"1");
        Caller ob2 = new Caller(c,"2");
        Caller ob3 = new Caller(c,"3");
        
        try{
            ob1.t.join();
            ob2.t.join();
            ob3.t.join();
            
        }catch(InterruptedException e){
            System.out.println(e);
        }
    }
    
}

I am new to Java. I tried this sample program while learning about synchronization in threads. The first time I ran it, it gave me the expected output.

1message
2message
3message

But as I started running it repeatedly, the order changed. Like:

1message
3message
2message

Why does this happen? Shouldn't the threads enter call() in the specified order(i.e ob1,ob2,ob3)?

Upvotes: 0

Views: 58

Answers (3)

xtratic
xtratic

Reputation: 4699

The reason you get output in random order is because the threads are running parallel and any of them could reach the synchronized block first, so they could print in any order.

If you want to get results in a defined order then you could use a Callable<String> instead of a Runnable and have it return the string you compute in the thread, then use the Futures of the tasks you submitted and get the results in your submission order. I added a timestamp to show that the later tasks sometimes still complete first, since they're all running parallel, but all the processing in CallMe.call(String) is still synchronized and thread safe-ish.

Using Callables and Futures lets you still get the benefits of parallelism and also gives you control when processing the results.

import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
...
public static void main(String args[]) {
    ExecutorService executor = Executors.newFixedThreadPool(3);
    CallMe c = new CallMe();

    List<Future<String>> futures = new ArrayList<>();
    futures.add(executor.submit(new Caller(c, "1")));
    futures.add(executor.submit(new Caller(c, "2")));
    futures.add(executor.submit(new Caller(c, "3")));

    try {
        for (Future<String> future : futures) {
            System.out.println(future.get());
        }
    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    }
    executor.shutdown();
}
class Caller implements Callable<String> {
    private String message;
    private CallMe target;

    Caller(CallMe target, String msg) {
        this.target = target;
        this.message = msg;
    }

    @Override public String call() {
        return target.call(message);
    }
}
class CallMe {
    synchronized String call(String msg) {
        try {
            Thread.sleep(500);
            return "message:" + msg + " - completed at system time " + new Timestamp(System.currentTimeMillis());
        } catch (InterruptedException e) {
            System.out.println(e);
            return "error:" + e.getMessage();
        }
    }
}

Upvotes: 1

Schottky
Schottky

Reputation: 2024

The synchronized keyword does not what you think it does. It simply protects the method, so only one thread can access it at a time. Therefore, the next number ("1", "2" or "3") can only be seen after "message" has been sent. The order of the threads, however, is not defined. Think about it like this: You basically call the methods at the same time (approximately). The JVM will spawn three threads that all begin execution immediately. One thread (always the first one) will call the method call and lock it so no other thread may access it. Now there are two threads waiting until call has finished so that they may use the method. The order in which they will call that method is completely arbitrary.

Upvotes: 0

Most Noble Rabbit
Most Noble Rabbit

Reputation: 2776

For me it's:

3message
2message
1message

Why?

Because we started 3 threads that run concurrently.

Once we started the threads, the OS should give them CPU time. The first thread that arrives at the synchronized block will print to screen, the others will wait for it to finish, and so on.

If you want the printing to be pre-defined, then you should do:

    Caller ob1 = new Caller(c,"1");
    ob1.t.join();
    Caller ob2 = new Caller(c,"2");
    ob2.t.join();
    Caller ob3 = new Caller(c,"3");
    ob3.t.join(); 

But in most use-cases, you wouldn't do that, and instead don't use threads.

Upvotes: 0

Related Questions