Reputation: 3
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
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 Future
s 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 Callable
s and Future
s 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
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
Reputation: 2776
For me it's:
3message
2message
1message
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