Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding alien methods in Java concurrency

I'm reading J. Bloch's effective Java and now I'm at the section about alien methods.

I'm trying to understand alien methods in Java concurrency and the harm they could do. As he said, we basically don't know what alien methods could do and we may end up deadlocking. I tried to reproduce such a deadlocking behavior writing the following simple app (The alien method is in the same class for simplicity):

public class App {
    private static StringBuffer lines = new StringBuffer();

    public static void modifyLines(){
        System.out.println("Invocation modifyLines() started by " + Thread.currentThread().getName());
        synchronized (lines) {
            System.out.println("Entering modifyLines() synchronized " + Thread.currentThread().getName());
            lines.append("Modified");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        synchronized (lines) {
            System.out.println("Entering main() synchronized by " + Thread.currentThread().getName());
            alienMethod();
        }
    }

    public static void alienMethod(){
        ExecutorService es = Executors.newSingleThreadExecutor();
        es.submit(new Runnable() {
            @Override
            public void run() {
                modifyLines();
            }
        });
        es.shutdown();
    }
}

I expected a deadlock to take place and that the thread spawned by calling to alienMethod() would never enter the synchronized block within modifyLines(). But the program prints the following:

Entering main() synchronized by main
Invocation modifyLines() started by pool-1-thread-1
Entering modifyLines() synchronized pool-1-thread-1

Which means the deadlock did not happen. Why? What's wrong with the alien method example?

like image 228
St.Antario Avatar asked Nov 12 '15 05:11

St.Antario


2 Answers

This is a very old question and answer is also accepted but I had to took time to dig this old grave because I think answer is not entirely correct and could mislead.

Let me first start by highlighting how accepted answer is broken - if you run with es.awaitTermination(2, TimeUnit.SECONDS); then you will not get deadlock, because there was never a deadlock situation in the given code. Deadlock occurs when you have 2 threads waiting for each other to release a lock, typically for a deadlock to occur you would have at least 2 locks and 2 threads. As per the suggested answer, what was happening was that main thread was held using awaitTermination and since the main thread is holding the lock so the new thread had to "wait" until main thread releases the lock, now with Long.MAX_VALUE this wait period was too huge so it looked like a deadlock, but actually it was "waiting" instead of deadlock, and there is a difference in deadlock and waiting, in order to resolve a deadlock you have to adjust your locking code.

Now coming to alien methods: basically for a class a method would be considered as a "alien method" if it doesn't have any information about it or in other words implementation details of the method, now typically each class would not have any information about other class implementations (and this is what is expected also - "loose coupling") so "as such" every method of class B is "alien" for class A but we don't consider it alien in general context because that's what is expected, we call methods as alien methods only in context of synchronization, so when there is a synchronization and from the synchronized block a method is called for which the object doesn't have any information and object cannot be sure if it could result in deadlock then that method is called as "alient method".

Now, below is an example code to demonstrate (*read code comments marked with "*****"*) alien method, it shows that how depending on what the client method is doing, a deadlock may occur or a deadlock may not occur; here you have deadlock and not waiting, I have not used any waiting code.

SetObserver.java:

public interface SetObserver {
    void added(MyClass mc, SetObserver sc);
}

MyClass.java:

import java.util.concurrent.*;

public class MyClass {

    static Object o1 = new Object();

    public void test1(){
        synchronized(o1){
            System.out.println("test1");
        }
    }

    public void test3(SetObserver sc) throws InterruptedException{
        synchronized(o1){
            for (int i = 0; i < 100; i++) {
                System.out.print("test3 >>" + i);
                sc.added(this, sc);
                synchronized(sc){
                    System.out.println("<<");
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyClass mc = new MyClass();
        mc.test3(new SetObserver() {

            @Override
            public void added(final MyClass mc, final SetObserver sc) {
                // ***** This will not cause deadlock because it doesn't spawn a new thread, even though it synchronize on same object. *****
                /*synchronized(sc){
                    mc.test1();
                }*/

                // ***** This causes a deadlock because it spawns a new thread, so it will cause lock contention among threads. *****
                ExecutorService xc = Executors.newFixedThreadPool(1);
                xc.execute(new Runnable() {

                    @Override
                    public void run() {
                        synchronized(sc){
                            System.out.println("Calling test1");
                            mc.test1();
                        }
                    }
                });
                xc.shutdown();
            }
        });
    }
}
like image 90
hagrawal Avatar answered Nov 10 '22 12:11

hagrawal


Your alienMethod() does not wait till the submitted task is finished. If you wait for it, you will have a deadlock:

public static void alienMethod() throws InterruptedException{
    ExecutorService es = Executors.newSingleThreadExecutor();
    es.submit(new Runnable() {
        @Override
        public void run() {
            modifyLines();
        }
    });
    es.shutdown();
    es.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
}
like image 38
Tagir Valeev Avatar answered Nov 10 '22 11:11

Tagir Valeev