kebukeyi
kebukeyi

Reputation: 13

Does wait() release all locks?

I wrote this program to check if a thread_1 holding lock on two different objects: LOCK_OBJECT and FULL goes into waiting mode on LOCK_OBJECT using FULL.wait(). I didn't think the consumer would get the LOCK_OBJECT lock, but the print didn't. So is there anything you missed?

Why does the consumer get the LOCK_OBJECT lock based on the printed results?

This is my code:

public class TestSync {

    private volatile Integer amount = 0;
    private final Object LOCK_OBJECT = new Object();
    private final Object FULL = new Object();

    public void doubleSync() throws InterruptedException {
        System.out.println("Producer  trying to get LOCK_OBJECT lock ");
        synchronized (LOCK_OBJECT) {
            System.out.println("Producer get LOCK_OBJECT lock ");
            Print.sleep(3000);
            while (amount >= 0) {
                synchronized (FULL) {
                    System.out.println("full!");
                    FULL.wait();
                }
            }
            System.out.println("continue~");
            amount++;
        }
    }

    public void simpleSync() {
        System.out.println("Consumer trying to get LOCK_OBJECT lock ");
        synchronized (LOCK_OBJECT) {
            System.out.println("Consumer get LOCK_OBJECT lock");
        }
        System.out.println("Consumer release LOCK_OBJECT lock");
    }

    public static void main(String[] args) throws InterruptedException {
        AtomicInteger atomicInteger = new AtomicInteger(0);
        ExecutorService threadPool = Executors.newFixedThreadPool(1, new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                int index = atomicInteger.incrementAndGet();
                System.out.println("create no " + index + " thread");
                Thread t = new Thread(r, "one Thread-" + index);
                return t;
            }
        });
        threadPool.execute(() -> {
            try {
                new TestSync().doubleSync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        Print.sleep(1000);
        ExecutorService executorService = Executors.newFixedThreadPool(1, new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                int index = atomicInteger.incrementAndGet();
                System.out.println("create no " + index + " thread");
                Thread t = new Thread(r, "two Thread-" + index);
                return t;
            }
        });
        executorService.execute(() -> {
            try {
                new TestSync().simpleSync();
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }
}

And this is the print result:

create no 1 thread
sleep 1000
Producer  trying to get LOCK_OBJECT lock 
Producer get LOCK_OBJECT lock 
sleep 3000
create no 2 thread
Consumer trying to get LOCK_OBJECT lock 
Consumer get LOCK_OBJECT lock
Consumer release LOCK_OBJECT lock
full!

Upvotes: 1

Views: 194

Answers (3)

Stephen C
Stephen C

Reputation: 718788

Does wait() release all locks?

No. It only releases the lock for the object you call wait() on.

As for the behavior of your code ... we cannot fully explain it because it is missing a crucial method: Print.

However, it looks like you are using multiple instances of TestSync. That would mean that the producer and consumer threads have different objects for the respective lock objects ... and there is no mutual exclusion.


It is also a bad idea (wasteful) to use multiple thread pools, and particularly to create and destroy them dynamically. The primary advantage of thread pools compared with disposable threads is that the (significant) thread creation is amortized. But when you shut down (or "lose") a thread pool, the threads get discarded anyway ... and have to be created again for the "next" thread pool.

(This doesn't affect the behavior of your example. But it is a bad habit ... and it is liable to bite you if you do it in a real application.)


Lesson: Programming with primitive mutexes and wait/notify requires considerable care. It is better to use the higher level concurrency primitives ... where possible.

Upvotes: 0

Avinash Mishra
Avinash Mishra

Reputation: 21

1.It is always better to proceed with a one ExecutorService implementation (as we maintain limited number of thread alive all the time for performance reasons)

2.wait() does releases the lock for a single object but after that all the threads (considering same priority) are equally probable to get the lock

Upvotes: 0

Abra
Abra

Reputation: 20914

The code in your question does not compile. What is Print? It appears twice in your code. Once in method main

Print.sleep(1000);

In the below code, I simply created a class named Print and defined a static method sleep.

Locks work on objects that are shared between threads. In your code you create a separate TestSync object for each thread. Hence no shared objects. Try creating one instance of TestSync and send it to both threads. Also, you don't need a separate ExecutorService for each thread. You can use a single ExecutorService to launch many threads. The below code demonstrates.

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

public class TestSync {
    private volatile Integer amount = 0;
    private final Object LOCK_OBJECT = new Object();
    private final Object FULL = new Object();

    public void doubleSync() throws InterruptedException {
        System.out.println("Producer  trying to get LOCK_OBJECT lock ");
        synchronized (LOCK_OBJECT) {
            System.out.println("Producer get LOCK_OBJECT lock ");
            Print.sleep(3000);
            while (amount >= 0) {
                synchronized (FULL) {
                    System.out.println("full!");
                    FULL.wait();
                }
            }
            System.out.println("continue~");
            amount++;
        }
    }

    public void simpleSync() {
        System.out.println("Consumer trying to get LOCK_OBJECT lock ");
        synchronized (LOCK_OBJECT) {
            System.out.println("Consumer get LOCK_OBJECT lock");
        }
        System.out.println("Consumer release LOCK_OBJECT lock");
    }

    public static void main(String[] args) throws InterruptedException {
        AtomicInteger atomicInteger = new AtomicInteger(0);
        ExecutorService threadPool = Executors.newFixedThreadPool(2, new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                int index = atomicInteger.incrementAndGet();
                System.out.println("create no " + index + " thread");
                Thread t = new Thread(r, "Thread-" + index);
                return t;
            }
        });
        TestSync ts = new TestSync();
        threadPool.execute(() -> {
            try {
                ts.doubleSync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        Print.sleep(1000);
        threadPool.execute(() -> {
            try {
                ts.simpleSync();
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        threadPool.shutdown();
    }
}

class Print {
    public static void sleep(long interval) {
        System.out.println("sleep " + interval);
    }
}

Note that it is recommended to call method shutdown(), of interface ExecutorService, after you have submitted all your tasks.

Here is the output when I run the above code.

create no 1 thread
Producer  trying to get LOCK_OBJECT lock 
Producer get LOCK_OBJECT lock 
sleep 1000
create no 2 thread
Consumer trying to get LOCK_OBJECT lock 
sleep 3000
full!

As you can see, the consumer does not obtain the lock.

Upvotes: 1

Related Questions