veritas
veritas

Reputation: 2444

Does synchronized park a concurrent thread like Lock.lock() does?

When we call either lock.lock() or try to enter a synchronized block then our thread blocks if some other thread has already taken that lock. Now my question is, when we look at the implementation of lock.lock() it delegates acquiring lock to AQS which actually parks the current thread (so that it cannot be scheduled further by scheduler).

Is it the same case with synchronized blocking also?

I even think my thread status are also different. For example, if my thread is blocked on synchronized block it will be BLOCKING while if I have called lock.lock(), then it will be WAITING. Am I right?

My Concern is the difference between the below two locking strategies in aspects of Thread.status and performance improvement by parking instead of busy waiting

  1. ReentrantLock.lock();
  2. synchronize { /*some code */ }

Upvotes: 3

Views: 2224

Answers (3)

linski
linski

Reputation: 5094

Calling upon lock or lockInterruptibly will put the thread in WAITING state:

Thread state for a waiting thread. A thread is in the waiting state due to calling one of the following methods:

  • Object.wait with no timeout
  • Thread.join with no timeout
  • LockSupport.park

The following code starts four threads, first two (A,B) run the same code and lock some monitor via the lock method. The other two (C,D) also run the same code, but they lock some another monitor via the lockInterruptibly method:

public static synchronized void dumpThreadState(List<Thread> threads) {
    System.out.println("thread state dump start");
   for (Thread t: threads) {
        System.out.println(t.getName()+" "+t.getState());
    } 
   System.out.println("thread state dump end\n");
}

public static void main(String[] args) throws InterruptedException {
    final Lock lock = new ReentrantLock();
    final Lock anotherLock = new ReentrantLock();
    List<Thread> threads = new LinkedList<Thread>();
    
    Runnable first = new Runnable() {

        @Override
        public void run() {                
            try {
                lock.lock();
            } 
            catch (Exception ex) {
                System.out.println(Thread.currentThread().getName()+" processing exception "+ex.getClass().getSimpleName());                    
            }
            while (true);                
        }
    } ;
    Runnable second = new Runnable() {

        @Override
        public void run() {         
            try {
                anotherLock.lockInterruptibly();
            } 
            catch (InterruptedException ex) {
                System.out.println(Thread.currentThread().getName()+" was interrupted");
            }
            while (true); 
        }
    };
    
    threads.add(new Thread(first,"A"));
    threads.add(new Thread(first,"B"));
    threads.add(new Thread(second,"C"));
    threads.add(new Thread(second,"D"));
           
    
    dumpThreadState(threads);
    
    for (Thread t: threads) {
        t.start();
    }
    
    Thread.currentThread().sleep(100);
    
    dumpThreadState(threads);
    
    System.out.println("interrupting " + threads.get(1).getName());
    threads.get(1).interrupt();
     
    dumpThreadState(threads);
    
    System.out.println("interrupting " + threads.get(3).getName());
    threads.get(3).interrupt();
    
    Thread.currentThread().sleep(100);
    dumpThreadState(threads);
    
    for (Thread t: threads) {
        t.join();
    }
}

It outputs:

thread state dump start
A NEW
B NEW
C NEW
D NEW
thread state dump end

thread state dump start
A RUNNABLE
B WAITING
C RUNNABLE
D WAITING
thread state dump end

interrupting B
thread state dump start
A RUNNABLE
B WAITING
C RUNNABLE
D WAITING
thread state dump end

interrupting D
D was interrupted
thread state dump start
A RUNNABLE
B WAITING
C RUNNABLE
D RUNNABLE
thread state dump end

As it can be seen the thread locked via the lock method can not be interrupted, while thread locked with lockInterruptibly can.

In the other example three threads are started, the first two (A,B) run the same code and lock upon the same monitor via the synchronized block. The third thread locks on another monitor but waits via the wait method:

 public static void main(String[] args) throws InterruptedException {
    final Object lock = new Object();
    final Object anotherLock = new Object();
    
    List<Thread> threads = new LinkedList<Thread>();
    
    Runnable first = new Runnable() {

        @Override
        public void run() {
            synchronized(lock) {
                while (true);
            }
        }
    } ;
    Runnable second = new Runnable() {

        @Override
        public void run() {         
            synchronized(anotherLock) {
                try {
                    anotherLock.wait();
                } 
                catch (InterruptedException ex) {
                    ex.printStackTrace();
                }
            }
        }
    };
    
    threads.add(new Thread(first,"A"));
    threads.add(new Thread(first,"B"));
    threads.add(new Thread(second,"C"));
    
    dumpThreadState(threads);
    
    for (Thread t: threads) {
        t.start();
    }
    
    Thread.currentThread().sleep(100);
    
    dumpThreadState(threads);
    
    for (Thread t: threads) {
        t.join();
    }
    
}

It outputs:

thread state dump start
A NEW
B NEW
C NEW
thread state dump end
thread state dump start
A RUNNABLE
B BLOCKED
C WAITING
thread state dump end

Thread C ended up in WAITING state while thread B ended up in BLOCKING state:

Thread state for a thread blocked waiting for a monitor lock. A thread in the blocked state is waiting for a monitor lock to enter a synchronized block/method or reenter a synchronized block/method after calling Object.wait.

EDIT:

Here is a real nice UML diagram of thread states.

Upvotes: 4

Peter Lawrey
Peter Lawrey

Reputation: 533780

BLOCKING - is blocked on a resource, cannot be interrupted

WAITING - is blocked on a resource, but can be interrupted or notified or unparked.

As you can see WAITING is better for control from another processed. e.g. if two threads are deadlocked you could break a lock() with an interrupt. With a two thread using synchronized you are stuck.

The behaviour of the synchronized vs lock is very similar and the exact details change between major revisions.

My advise is to use

  • synchronized for simpler code where you need thread safety but have a very low lock contention.

  • use Lock where you have identified you have lock contention, or you need additional functionality like tryLock.


If you do

final Lock lock = new ReentrantLock();
lock.lock();
Thread t = new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            lock.lockInterruptibly();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
});
t.start();
Thread.sleep(100);
System.out.println(t + " is " + t.getState());
lock.unlock();

prints

Thread[Thread-0,5,main] is WAITING

Thread.State states


Thread state for a waiting thread. A thread is in the waiting state due to calling one of the following methods:

  • Object.wait with no timeout
  • Thread.join with no timeout
  • LockSupport.park

A thread in the waiting state is waiting for another thread to perform a particular action. For example, a thread that has called Object.wait() on an object is waiting for another thread to call Object.notify() or Object.notifyAll() on that object. A thread that has called Thread.join() is waiting for a specified thread to terminate.

Upvotes: 4

cmbaxter
cmbaxter

Reputation: 35463

Parking a thread and synchronized blocking are very different. When you try and enter a synchronized block, you are explicitly attempting to acquire a monitor on an object instance. If you can not acquire the monitor, your thread will go into the BLOCKING state until the monitor is available. Parking is more similar to the Object.wait() method in that the code knows that it can't continue until some other condition becomes true. There's no sense in blocking here because it would be fruitless because my condition for continuing on is currently true. At this point I go into the WAITING or TIMED_WAITING (depends on how the wait is issued) state until I am notified (via something like notify(), notifyAll() or unpark()). Once my condition becomes true I come out if my wait state and then probably attempt to acquire monitors and go into BLOCKING if I need them. If I get my monitors, I go into RUNNING and continue on my merry way

So waiting is really about knowing that I can't do something and having some other thread notify me when it thinks I can. It can lead to blocking after I wake up though. Blocking is just competing for access to a monitor without an explicit other prerequisite condition.

When lock() is called on a Lock instance, the calling thread is actually put into a wait state and is not blocking. The benefit here is that this wait state can be interrupted and this helps to avoid deadlocks. With something like the Lock class, you have a bunch of options on desired waiting behaviors via tryLock(), tryLock(long,TimeUnit), lock() and lockInterruptibly(). You can specify things like how long you want to wait and if you can be interrupted via which method you call. With synchronized code, you don't have such options. You're blocking and you're stuck blocking until some thread gives up the monitor you want and if it never does, you are deadlocked. That's why since Java 5 and the concurrent package, you should avoid using the synchronized keyword and instead try and implement similar semantics with things like Lock and Condition.

Upvotes: 3

Related Questions