LockSupport and thread waiting wake-up mechanism

Keywords: Java

What is LockSupport

The LockSupport thread tool class defines many methods to control the current thread, commonly known as lock interrupt

Is the basic thread blocking primitive used to create locks and other synchronization classes.

park () and unpark () in LockSupport are blocking threads and unblocking threads respectively

LockSupport class uses a concept called Permit to block and wake up threads. Each thread has a license. The license has only two values: 1 and zero. The default is zero. Permission can be regarded as a (0,1) Semaphore, but unlike Semaphore, the upper limit of accumulation of permission is 1.

Thread waiting wake-up mechanism

Three ways to make threads wait and wake up

Method 1: use the wait() method in Object to make the thread wait, and use the notify() method in Object to wake up the thread

Object wait notify notifyAll

Method 2: use the await() method of Condition in the JUC package to make the thread wait, and use the signal() method to wake up the thread

Condition await signal signalAll

Method 3: LockSupport class can block the current thread and wake up the specified blocked thread

LockSupport park unpark

The wait and notify methods in the Object class implement thread waiting and wake-up

package com.dongguo.locksupport;

/**
 * @author Dongguo
 * @date 2021/9/6 0006-15:40
 * @description:
 */
public class LockSupportDemo {
    public static void main(String[] args) {
        Object objectLock = new Object();
        new Thread(()->{
            synchronized (objectLock){
                try {
                    System.out.println(Thread.currentThread().getName()+"--block");
                    objectLock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"--Awakened");
            }
        },"t1").start();
        new Thread(()->{
            synchronized (objectLock){
                objectLock.notify();
                System.out.println(Thread.currentThread().getName()+"--Send wake-up notification");
            }
        },"t2").start();
    }
}
Operation results:
t1--block
t2--Send wake-up notification
t1--Awakened

Abnormal one

The wait and notify methods in the Object class can only be used in synchronized code blocks or synchronization methods, and appear in pairs. Otherwise, an exception IllegalMonitorStateException will be reported

package com.dongguo.locksupport;

/**
 * @author Dongguo
 * @date 2021/9/6 0006-15:40
 * @description:
 */
public class LockSupportDemo {
    public static void main(String[] args) {
        Object objectLock = new Object();
        new Thread(()->{
//            synchronized (objectLock){
                try {
                    System.out.println(Thread.currentThread().getName()+"--block");
                    objectLock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"--Awakened");
//            }
        },"t1").start();
        new Thread(()->{
//            synchronized (objectLock){
                objectLock.notify();
                System.out.println(Thread.currentThread().getName()+"--Send wake-up notification");
//            }
        },"t2").start();
    }
}
Operation results
t1--block
Exception in thread "t1" Exception in thread "t2" java.lang.IllegalMonitorStateException
	at java.lang.Object.notify(Native Method)
	at com.dongguo.locksupport.LockSupportDemo.lambda$main$1(LockSupportDemo.java:24)
	at java.lang.Thread.run(Thread.java:748)
java.lang.IllegalMonitorStateException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at com.dongguo.locksupport.LockSupportDemo.lambda$main$0(LockSupportDemo.java:15)
	at java.lang.Thread.run(Thread.java:748)

Anomaly two

wait() must be before notify(),

package com.dongguo.locksupport;

import java.util.concurrent.TimeUnit;

/**
 * @author Dongguo
 * @date 2021/9/6 0006-15:40
 * @description:
 */
public class LockSupportDemo {
    public static void main(String[] args) {
        Object objectLock = new Object();
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (objectLock){
                try {
                    System.out.println(Thread.currentThread().getName()+"--block");
                    objectLock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"--Awakened");
            }
        },"t1").start();
        new Thread(()->{
            synchronized (objectLock){
                objectLock.notify();
                System.out.println(Thread.currentThread().getName()+"--Send wake-up notification");
            }
        },"t2").start();
    }
}

Operation results

t1 blocking waiting to be awakened

The await post signal method in the Condition interface implements thread waiting and wake-up

package com.dongguo.locksupport;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author Dongguo
 * @date 2021/9/6 0006-15:40
 * @description:
 */
public class LockSupportDemo {
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();

        new Thread(() -> {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "--block");
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
            System.out.println(Thread.currentThread().getName() + "--Awakened");
        }, "t1").start();
        new Thread(() -> {
            lock.lock();
            try {
                condition.signal();
                System.out.println(Thread.currentThread().getName() + "--Send wake-up notification");
            } finally {
                lock.unlock();
            }
        }, "t2").start();
    }
}
Operation results
t1--block
t2--Send wake-up notification
t1--Awakened

Abnormal one

Only in lock and unlock can the methods of the thread in condition waiting for await and waking up signal be called correctly

package com.dongguo.locksupport;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author Dongguo
 * @date 2021/9/6 0006-15:40
 * @description:
 */
public class LockSupportDemo {
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();

        new Thread(() -> {
//            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "--block");
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
//                lock.unlock();
            }
            System.out.println(Thread.currentThread().getName() + "--Awakened");
        }, "t1").start();
        new Thread(() -> {
//            lock.lock();
            try {
                condition.signal();
                System.out.println(Thread.currentThread().getName() + "--Send wake-up notification");
            } finally {
//                lock.unlock();
            }
        }, "t2").start();
    }
}
Operation results
t1--block
Exception in thread "t1" Exception in thread "t2" java.lang.IllegalMonitorStateException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.signal(AbstractQueuedSynchronizer.java:1939)
	at com.dongguo.locksupport.LockSupportDemo.lambda$main$1(LockSupportDemo.java:33)
	at java.lang.Thread.run(Thread.java:748)
java.lang.IllegalMonitorStateException
	at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.fullyRelease(AbstractQueuedSynchronizer.java:1723)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2036)
	at com.dongguo.locksupport.LockSupportDemo.lambda$main$0(LockSupportDemo.java:22)
	at java.lang.Thread.run(Thread.java:748)

Anomaly two

The order of await before signal cannot be changed

package com.dongguo.locksupport;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author Dongguo
 * @date 2021/9/6 0006-15:40
 * @description:
 */
public class LockSupportDemo {
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();

        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "--block");
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
            System.out.println(Thread.currentThread().getName() + "--Awakened");
        }, "t1").start();
        new Thread(() -> {
            lock.lock();
            try {
                condition.signal();
                System.out.println(Thread.currentThread().getName() + "--Send wake-up notification");
            } finally {
                lock.unlock();
            }
        }, "t2").start();
    }
}

Operation results

t1 blocked

Constraints used by Object and Condition

To obtain and hold a lock, a thread must be in a lock block (synchronized or lock) and wait/notify or await/signal appear in pairs

You must wait before waking up before the thread can be awakened

Locking will be blocked

park wait and unpark wake in LockSupport class

Main methods

block

park() /park(Object blocker)

Block current thread / block incoming concrete thread

Generally use park()

When calling LockSupport.park()

park()

    /**
     * For thread scheduling, block the current thread before the license is available. 
     * If the license is available, the license is used, and the call returns immediately;
     * Otherwise, disable the current thread for thread scheduling and put it into sleep before one of the following three conditions occurs:
     *  1. Another thread calls unpark with the current thread as the target
     *  2. Some other thread interrupts the current thread
     *  3. The call returns illogically (i.e., without reason)
     */
public static void park() {
    UNSAFE.park(false, 0L);
}

park(Object blocker)

public static void park(Object blocker) {
   //Get current thread
    Thread t = Thread.currentThread();
   //Record the reason why the current thread is blocked. The bottom layer is unsafe.putObject, which is to store the object
    setBlocker(t, blocker);
    //Execute park
    unsafe.park(false, 0L);
   //After thread recovery, remove the blocking cause
    setBlocker(t, null);
}

The permission is zero by default, so the current thread will block when the park() method is called at the beginning. When another thread sets the permission of the current thread to 1, the park method will wake up,
It then sets permit to zero again and returns.

awaken

unpark(Thread thread)

Wakes up the specified thread that is blocked

LockSupport.unpark(thread);

  /**
     * If the license for the given thread is not yet available, make it available.
     * If a thread is blocked on a park, it will unblock it.
     * Otherwise, ensure that the next call to park will not be blocked.
     * If the given thread has not been started, there is no guarantee that this operation will have any effect. 
     * @param thread: The thread to perform the unpark operation; A null parameter indicates that this operation has no effect.
     */
public static void unpark(Thread thread) {
    if (thread != null)
        UNSAFE.unpark(thread);
}

After calling the unpark(thread) method, the permission permission of the thread will be set to 1 (note that calling the unpark method multiple times will not accumulate, and the permission value is still 1) will automatically wake up the thread, that is, the LockSupport.park() method in the previous blocking will return immediately.

From the source code, we can see that the real implementation is in Unsafe.class

public native void unpark(Object var1);

public native void park(boolean var1, long var2);

Compared with wait & notify (await signal of condition) of Object

wait, notify and notifyAll must be used together with Object Monitor, but park and unpark do not
Park & unpark is used to block and wake up threads by thread, while notify can only wake up one waiting thread randomly, notifyAll
It is not so accurate to wake up all waiting threads
Park & unpark can unpark first, while wait & notify cannot notify first

code

package com.dongguo.locksupport;

import java.util.concurrent.locks.LockSupport;

/**
 * @author Dongguo
 * @date 2021/9/6 0006-16:14
 * @description:
 */
public class LockSupportDemo1 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "--block");
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + "--Awakened");
        }, "t1");
        t1.start();
        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "--Send wake-up notification");
            LockSupport.unpark(t1);
        },"t2").start();
    }
}
Operation results
t1--block
t2--Send wake-up notification
t1--Awakened

LockSupport solves thread blocking and wakeup in Object and Condition

To obtain and hold a lock, a thread must solve the problem in the lock block (synchronized or lock)

Moreover, the order of park and unpark can not be fixed

package com.dongguo.locksupport;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

/**
 * @author Dongguo
 * @date 2021/9/6 0006-16:14
 * @description:
 */
public class LockSupportDemo1 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "--block");
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + "--Awakened");
        }, "t1");
        t1.start();
        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "--Send wake-up notification");
            LockSupport.unpark(t1);
        },"t2").start();
    }
}
Operation results
t2--Send wake-up notification
t1--block
t1--Awakened

It is difficult to understand that the unpark operation can be performed before the park operation. That is, the license is provided first. When a thread calls Park and has a license, it consumes the license and can continue to run. This is actually necessary. Consider the simplest producer consumer model: the consumer needs to consume a resource, so it calls the park operation to wait; Producer then produces resources, and then calls unpark to grant Consumer permission. It is very likely that the producer produces first, and the consumer may not be constructed (for example, the thread has not been started or switched to the thread). When the consumer is ready to consume, it is obvious that the resources have been produced and can be used directly. Of course, the park operation can run directly. Without this semantics, it will be very difficult to operate.

However, this "license" cannot be superimposed, and the "license" is one-time.

For example, thread t2 calls the unpark function three times in a row. When thread t1 calls the park function, this "permission" will be used. If thread t1 calls Park again, it will enter the waiting state.

Execute two park() before unpark()

package com.dongguo.locksupport;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

/**
 * @author Dongguo
 * @date 2021/9/6 0006-16:14
 * @description:
 */
public class LockSupportDemo1 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {

            System.out.println(Thread.currentThread().getName() + "--block");
            LockSupport.park();
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + "--Awakened");
        }, "t1");
        t1.start();
        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "--Send wake-up notification");
            LockSupport.unpark(t1);
            LockSupport.unpark(t1);
            LockSupport.unpark(t1);
        },"t2").start();
    }
}

Operation results

Solve the problem of using two threads and waking up twice

package com.dongguo.locksupport;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

/**
 * @author Dongguo
 * @date 2021/9/6 0006-16:14
 * @description:
 */
public class LockSupportDemo1 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {

            System.out.println(Thread.currentThread().getName() + "--block");
            LockSupport.park();
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + "--Awakened");
        }, "t1");
        t1.start();

        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "--t2 Send wake-up notification");
            LockSupport.unpark(t1);
        },"t2").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "--t3 Send wake-up notification");
            LockSupport.unpark(t1);
        },"t3").start();
    }
}
Operation results
t1--block
t2--t2 Send wake-up notification
t3--t3 Send wake-up notification
t1--Awakened

Make sure that unpark does not wake up the same park

Execute two unpark() before park()

package com.dongguo.locksupport;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

/**
 * @author Dongguo
 * @date 2021/9/6 0006-16:14
 * @description:
 */
public class LockSupportDemo1 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "--block");
            LockSupport.park();
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + "--Awakened");
        }, "t1");
        t1.start();
        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "--Send wake-up notification");
            LockSupport.unpark(t1);
            LockSupport.unpark(t1);
        },"t2").start();
    }
}

Operation results

Execute t2 first and unpark twice to set the license to 1

LockSupport.unpark(t1);
LockSupport.unpark(t1);

Execute t1

Execute park LockSupport.park() for the first time; Consumption license

The park permission of the second execution is 0, and blocking is entered

Responsiveness of LockSupport to interruptions

The bottom layer of AQS (AbstractQueuedSynchronizer) is to call the park() method to ensure that after the blocking is awakened, if locking fails, it can block again to reduce resource consumption. When a waiting queue occurs, a node in the queue can respond to the interrupt:

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

If the thread is blocked by calling park, it can respond to the interrupt request (the interrupt status is set to true), but it will not throw an InterruptedException.

package com.dongguo.locksupport;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

/**
 * @author Dongguo
 * @date 2021/9/6 0006-20:49
 * @description:
 */
public class LockSupportDemo2 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println( "park Front interrupt flag bit 1:" + Thread.currentThread().isInterrupted());
            LockSupport.park();
            System.out.println(Thread.currentThread().getName()+"Wake up interrupt state 2"+ Thread.currentThread().isInterrupted());
        }, "t1");
        t1.start();

        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t1.interrupt();
    }
}
Operation results
park Front interrupt flag bit 1: false
t1 Wake up interrupt state 2 true

The state of the thread of park() after being interrupted() is the same as that of the normal thread after being interrupted()

The park method can respond to interrupts

Therefore, the park method allows the thread to wake up after waiting

1unpark

2interrupt

However, unpark is recommended

If the break flag is already true, the park will be invalidated

After the thread of park() is interrupted()

park() fails again and cannot be blocked

package com.dongguo.locksupport;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

/**
 * @author Dongguo
 * @date 2021/9/6 0006-20:49
 * @description:
 */
public class LockSupportDemo2 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println( "park Front interrupt flag bit 1:" + Thread.currentThread().isInterrupted());
            LockSupport.park();
            System.out.println(Thread.currentThread().getName()+"Wake up interrupt state 2"+ Thread.currentThread().isInterrupted());
        }, "t1");
        t1.start();

        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t1.interrupt();
        LockSupport.unpark(t1);
    }
}  
Operation results
park Front interrupt flag bit 1: false
t1 Wake up interrupt state 2 true
park Front interrupt flag bit 1: true
t1 Wake up interrupt state 2 true
park Front interrupt flag bit 1: true
t1 Wake up interrupt state 2 true
park Front interrupt flag bit 1: true
t1 Wake up interrupt state 2 true
park Front interrupt flag bit 1: true
t1 Wake up interrupt state 2 true

The for loop is awakened by interrupt() after the first park block

After that, the loop park cannot block the thread.

You can use Thread.interrupted() to clear the broken state

package com.dongguo.locksupport;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

/**
 * @author Dongguo
 * @date 2021/9/6 0006-20:49
 * @description:
 */
public class LockSupportDemo2 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println( "park Front interrupt flag bit 1:" + Thread.currentThread().isInterrupted());
                Thread.interrupted();//Returns the interrupt status and clears the interrupt status
                LockSupport.park();
                System.out.println(Thread.currentThread().getName()+"Wake up interrupt state 2"+ Thread.currentThread().isInterrupted());
            }
        }, "t1");
        t1.start();

        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t1.interrupt();
    }
}

reference resources

[talk about Java concurrency] talk about LockSupport

Posted by alluoshi on Mon, 20 Sep 2021 19:56:19 -0700