Chapter 3 - management of shared model

Chapter 3 - management of shared model (4)

Thread state

Re understand thread state transition (key)

Suppose there is a thread Thread t

Case 1 new -- > runnable

  • When the t.start() method is called, new -- > runable

Case 2 runnable < – > waiting

  • After the t thread obtains the object lock with synchronized(obj)
  • Call obj When using the wait() method, the t thread starts from runnable -- > waiting
  • Call obj notify( ) , obj. When notifyall(), t.interrupt()
    • The contention lock succeeds, and the t thread starts from waiting -- > runnable
    • Contention lock failed, t thread from waiting -- > blocked
@Slf4j(topic = "c.TestWaitNotify")
public class TestWaitNotify {
    final static Object obj = new Object();

    public static void main(String[] args) {

        new Thread(() -> {
            synchronized (obj) {
                log.debug("implement....");
                try {
                    obj.wait(); // Let the thread wait on obj
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("Other codes....");
            }
        },"t1").start();

        new Thread(() -> {
            synchronized (obj) {
                log.debug("implement....");
                try {
                    obj.wait(); // Let the thread wait on obj
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("Other codes....");
            }
        },"t2").start();

        // The main thread executes in two seconds
        sleep(0.5);
        log.debug("awaken obj Other threads on");
        synchronized (obj) {
            obj.notifyAll(); // Wake up all waiting threads on obj
        }
    }
}

Schematic diagram of Monitor:

Sample debugging steps

  • Set 3 breakpoints

  • Execute the code in sequence. Threads t1 and t2 are wait ing. The breakpoint comes to notifyAll() of the main thread

  • When executing downward, the main thread notifyAll() wakes up all waiting threads, which move from the waitSet of the lock object obj to the EntryList competing object lock

  • The main thread executes downward, and out of the synchronized block, the lock is released. t1 and t2 can compete for locks. You can see that t2 competes for the lock successfully and becomes the owner of the lock object obj. t1 is still blocked in the entryList.

  • The t2 thread executes downward. When the synchronized block is released, the lock is released, and the t1 thread gets the lock

Case 3 runnable < – > waiting

  • When the current thread calls the} t.join() method, the current thread starts from runnable -- > waiting
    • Note that the current thread is waiting on the monitor of the t} thread object
  • t when the thread ends running, or the interrupt() of the current thread is called, the current thread starts from waiting -- > runnable

Case 4 runnable < – > waiting

  • The current thread calls locksupport The park () method will make the current thread from runnable -- > waiting
  • Call locksupport Unpark (target thread) or interrupt() that calls the thread will make the target thread from waiting -- > runnable

Case 5 runnable < – > timed_ WAITING

After the t thread obtains the object lock with synchronized(obj)

  • Call obj When using the wait (long n) method, the t thread starts from runnable -- > timed_ WAITING
  • T thread waiting time exceeds n milliseconds, or call obj notify( ) ,obj. When notifyall(), t.interrupt()
    • Thread contention, timed successful_ WAITING --> RUNNABLE
    • Contention lock failed, t thread from timed_ WAITING --> BLOCKED

Case 6 runnable < – > timed_ WAITING

  • When the current thread calls the t.join(long n) method, the current thread starts from runnable -- > timed_ WAITING
    • Note that the current thread is waiting on the monitor of the t thread object
  • When the waiting time of the current thread exceeds n milliseconds, or the running of the t thread ends, or the interrupt() of the current thread is called, the current thread starts from timed_ WAITING --> RUNNABLE

Case 7 runnable < – > timed_ WAITING

  • The current thread calls thread Sleep (long n), the current thread from runnable -- > timed_ WAITING
  • The waiting time of the current thread has exceeded n milliseconds. The current thread is from timed_ WAITING --> RUNNABLE

Case 8 runnable < – > timed_ WAITING

  • The current thread calls locksupport parkNanos(long nanos) LockSupport. When parkuntil (long miles), the current thread starts from runnable -- > timed_ WAITING
  • Call locksupport Unpark (target thread) or call the thread's interrupt() or wait timeout, which will make the target thread from TIMED_WAITING –> RUNNABLE

Case 9 runnable < – > blocked

  • When the t thread obtains the object lock with synchronized(obj), if the competition fails, it will start from runnable -- > blocked
  • After the synchronization code block of the obj lock thread is executed, all BLOCKED threads on the object will be awakened to compete again. If t threads compete successfully, from BLOCKED -- > runnable, other failed threads will still be BLOCKED

Case 10 runnable < – > terminated

  • After all the codes of the current thread have run, enter TERMINATED

Multiple locks

Multiple irrelevant locks

  • A big room has two functions: sleeping and studying, which are irrelevant to each other.
  • Now Xiao Nan has to study and the little girl has to sleep, but if only one room (one object lock) is used, the concurrency is very low
    • After Xiaonan got the lock, the little girl can come in and sleep after studying
  • The solution is to prepare multiple rooms (multiple object locks)

Code example

@Slf4j(topic = "c.BigRoomTest")
public class BigRoomTest {

    public static void main(String[] args) {
        BigRoom bigRoom = new BigRoom();
        new Thread(() -> {
            bigRoom.study();
        }, "Xiaonan").start();

        new Thread(() -> {
            bigRoom.sleep();
        }, "my daughter").start();
    }

}

@Slf4j(topic = "c.BigRoom")
class BigRoom {

    public void sleep() {
        synchronized (this) {
            log.debug("sleeping 2 hour");
            Sleeper.sleep(2);
        }
    }

    public void study() {
        synchronized (this) {
            log.debug("study 1 hour");
            Sleeper.sleep(1);
        }
    }

}

Improvement method: let Xiaonan and Xiaonv get different locks

@Slf4j(topic = "c.BigRoomTest")
public class BigRoomTest {

    public static void main(String[] args) {

        BigRoom bigRoom = new BigRoom();

        new Thread(() -> {
            bigRoom.study();
        }, "Xiaonan").start();

        new Thread(() -> {
            bigRoom.sleep();
        }, "my daughter").start();
    }

}

@Slf4j(topic = "c.BigRoom")
class BigRoom {

    private static final BigRoom bedRoom = new BigRoom(); //bedroom
    private static final BigRoom studyRoom = new BigRoom(); //Study

    public void sleep() {
        synchronized (bedRoom) {
            log.debug("sleeping 2 hour");
            Sleeper.sleep(2);
        }
    }

    public void study() {
        synchronized (studyRoom) {
            log.debug("study 1 hour");
            Sleeper.sleep(1);
        }
    }

}

  • Subdivide the granularity of locks
    • The advantage is that it can enhance the degree of concurrency
    • The disadvantage is that if a thread needs to obtain multiple locks at the same time, it is prone to deadlock

Activity

  • For some reason, the code can not be executed completely. This phenomenon is called "liveness"
  • A series of problems related to activity can be solved with re entrantlock.

Deadlock (key)

  • There is such a situation: a thread needs to obtain multiple locks at the same time. At this time, deadlock is easy to occur

Thread 1 obtains the A object lock, and thread 2 obtains the B object lock; At this time, thread 1 wants to obtain the B object lock, and thread 2 wants to obtain the A object lock; They all wait for the object to release the lock, which is called deadlock

@Slf4j(topic = "c.TestDeadLock")
public class TestDeadLock {

    public static void main(String[] args) {
        test1();
    }

    private static void test1() {
        Object A = new Object();
        Object B = new Object();
        Thread t1 = new Thread(() -> {
            synchronized (A) {
                log.debug("lock A");
                sleep(1);
                synchronized (B) {
                    log.debug("lock B");
                    log.debug("operation...");
                }
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            synchronized (B) {
                log.debug("lock B");
                sleep(0.5);
                synchronized (A) {
                    log.debug("lock A");
                    log.debug("operation...");
                }
            }
        }, "t2");
        t1.start();
        t2.start();
    }

}

Necessary conditions for deadlock (key points)

  • mutual exclusion
    • A resource can only be used by one process for a period of time
  • Request and hold conditions
    • The process already has at least one resource and applies for other resources at the same time. Because other resources are used by other processes, the process enters a blocking state and does not release its existing resources
  • Non preemptive condition
    • The resources obtained by the process cannot be forcibly occupied until they are used. They can only be released after the process is used
  • Cycle waiting condition
    • When the lock of life and death occurs, there must be a process - the circular chain of resources.

Method of locating deadlock

Method 1: JPS + JStack process ID

  • jps finds the JVM process first
  • jstack process ID
    • In the Terminal of the Java console, you can view the running process ID by entering the jps command, and you can view the process status by using jstack process ID.
jps

jstack 59114

  • To avoid deadlock, pay attention to the locking sequence
  • In addition, if a thread enters an endless loop, causing other threads to wait all the time, in this case, linux can first locate the Java process with high CPU consumption through top, then use the top -Hp process id to locate which thread, and finally use jstack to check

Mode 2: jconsole detects deadlock

Dining problem of philosophers

There are five philosophers sitting around the round table.

  • They only do two things, think and eat, think for a while, eat a meal, and then think after dinner.
  • When eating, you should use two chopsticks. There are five chopsticks on the table, and each philosopher has one chopstick on his left and right hands.
  • If the chopsticks are held by the people around you, you have to wait

Code example

public class TestDeadLock {

    public static void main(String[] args) {
        Chopstick c1 = new Chopstick("1");
        Chopstick c2 = new Chopstick("2");
        Chopstick c3 = new Chopstick("3");
        Chopstick c4 = new Chopstick("4");
        Chopstick c5 = new Chopstick("5");
        new Philosopher("Socrates", c1, c2).start();
        new Philosopher("Plato", c2, c3).start();
        new Philosopher("Aristotle", c3, c4).start();
        new Philosopher("Heraclitus", c4, c5).start();
        new Philosopher("Archimedes", c5, c1).start();
    }

}

@Slf4j(topic = "c.Philosopher")
// Philosophers
class Philosopher extends Thread {
    Chopstick left; // Chopsticks on the left
    Chopstick right; // Chopsticks on the right

    public Philosopher(String name, Chopstick left, Chopstick right) {
        super(name);
        this.left = left;
        this.right = right;
    }

    @Override
    public void run() {
        while (true) {
            //Try to get left-handed chopsticks
            synchronized (left) {
                // Try to get right-handed chopsticks
                synchronized (right) {
                    // having dinner
                    eat();
                }
                // Put down your right chopsticks
            }
            // Put down your left chopsticks
        }
    }

    private void eat() {
        log.debug("eating...");
        Sleeper.sleep(0.5);
    }
}

// Chopsticks
class Chopstick {
    String name;

    public Chopstick(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "chopsticks{" + name + '}';
    }
}

After a while, it won't be carried out any more

  • Use jconsole to detect deadlock and find

  • This kind of thread does not end as expected and cannot be executed. It is classified as [activity] problem. In addition to deadlock, there are also two cases: livelock and hungry

Livelock

A livelock occurs when two threads change each other's end conditions, and finally no one can end. (a deadlock is one that no one can execute) for example

@Slf4j(topic = "c.TestLiveLock")
public class TestLiveLock {
    static volatile int count = 10;
    static final Object lock = new Object();

    public static void main(String[] args) {
        new Thread(() -> {
            // The expectation is reduced to 0 to exit the loop
            while (count > 0) {
                sleep(0.2);
                count--;
                log.debug("count: {}", count);
            }
        }, "t1").start();

        new Thread(() -> {
            // Expect more than 20 to exit the cycle
            while (count < 20) {
                sleep(0.2);
                count++;
                log.debug("count: {}", count);
            }
        }, "t2").start();
    }
}

Ways to avoid loose locks

  • During the execution of a thread, a {different interval is given in the middle of the thread to let a thread end first.

The difference between deadlock and livelock

  • Deadlock is the phenomenon that threads hold the locks they want from each other and do not release them. Finally, the threads block and stop running.
  • Livelock is the phenomenon that the code is running all the time but can't finish running because threads modify each other's end conditions.

hunger

  • In many tutorials, hunger is defined as: a thread cannot be scheduled and executed by the CPU due to its low priority. Hunger is not easy to demonstrate. Hunger will be involved when talking about read-write locks
  • Let me talk about an example of thread starvation I encountered. First, let's take a look at using sequential locking to solve the previous deadlock problem

Solution of sequential locking

Although there will be no deadlock, there will be hunger

public class TestDeadLock {
    public static void main(String[] args) {
        Chopstick c1 = new Chopstick("1");
        Chopstick c2 = new Chopstick("2");
        Chopstick c3 = new Chopstick("3");
        Chopstick c4 = new Chopstick("4");
        Chopstick c5 = new Chopstick("5");
        new Philosopher("Socrates", c1, c2).start();
        new Philosopher("Plato", c2, c3).start();
        new Philosopher("Aristotle", c3, c4).start();
        new Philosopher("Heraclitus", c4, c5).start();
        new Philosopher("Archimedes", c1, c5).start();
    }
}
  • By previous
new Philosopher("Archimedes", c5, c1).start();
  • Change to
new Philosopher("Archimedes", c1, c5).start();
  • As you can see, there is no deadlock in the program, but you can see that some threads have no chance to execute at all

ReentrantLock (key)

Features of ReentrantLock (not available in synchronized)

  • Support reentrant
    • Reentrant lock means that if the same thread obtains the lock for the first time, it has the right to obtain the lock again because it is the owner of the lock
  • Interruptible
    • lock. Lockinterrupt(): an interrupt lock that can be interrupted by other threads
  • You can set the timeout
    • lock. Trylock (time): try to obtain the lock object. If the set time is exceeded and the lock has not been obtained, exit the blocking queue and release the lock you own
  • Can be set to fair lock
    • (first come, first served) the default is unfair, and true is fair. new ReentrantLock(true)
  • Support multiple conditional variables (multiple waitsets)
    • (false wakeups can be avoided) - lock Newcondition() creates a condition variable object; Call the await/signal method through the condition variable object to wait / wake up

Basic grammar

//Get ReentrantLock object
private ReentrantLock lock = new ReentrantLock();
//Lock
lock.lock();
try {
	// Critical zone
}finally {
	// Release lock
	lock.unlock();
}

Support reentrant

  • Reentrant lock means that if the same thread obtains the lock for the first time, it has the right to obtain the lock again because it is the owner of the lock
  • If it is a non reentrant lock, you will be blocked by the lock the second time you obtain the lock
/**
 * @author xiexu
 * @create 2022-02-01 8:56 AM
 */
@Slf4j(topic = "c.ReentrantTest")
public class ReentrantTest {

    // Get ReentrantLock object
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        // If there is competition, enter the "blocking queue" and wait all the time without being interrupted
        lock.lock();
        try {
            log.debug("entry main...");
            m1();
        } finally {
            lock.unlock();
        }
    }

    private static void m1() {
        lock.lock();
        try {
            log.debug("entry m1...");
            m2();
        } finally {
            lock.unlock();
        }
    }

    private static void m2() {
        lock.lock();
        try {
            log.debug("entry m2....");
        } finally {
            lock.unlock();
        }
    }

}

Interruptible (for the interrupt lock obtained by lockinterruptible() method) directly exits the blocking queue, and obtaining the lock fails

synchronized and reentrantlock The lock of lock() cannot be broken; In other words, other threads have obtained locks, so my thread needs to wait all the time and cannot be interrupted

  • Interruptible lock through lock The lock object obtained by lockinterruptibly () can be blocked by calling the interrupt() method of the thread
  • If a thread is in a blocking state, you can call its interrupt method to stop blocking and fail to obtain the lock
    • If a thread is in a blocked state, it doesn't need to be blocked if it is interrupted. It stops running directly
  • Interruptible locks can passively reduce the probability of deadlock to a certain extent. The reason for passivity is that we need to manually call the interrupt method of blocking threads;

The test uses lock Lockinterrupt() can interrupt from the blocking queue

/**
 * Demonstrate the interruptible lock method in RenntrantLock lockInterruptibly();
 *
 * @author xiexu
 * @create 2022-02-01 10:38 AM
 */
@Slf4j(topic = "c.ReentrantTest")
public class ReentrantTest {

    private static final ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {

        Thread t1 = new Thread(() -> {
            log.debug("t1 Thread start...");
            try {
                // If there is no competition, this method will acquire the lock object lock
                // If there is competition, it enters the blocking queue and can be interrupted by other threads with the interrupt method
                log.debug("Attempt to acquire lock");
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.debug("The process of waiting for the lock is interrupted. If the lock is not obtained, return"); //You don't need to go down without a lock
                return;
            }
            try {
                log.debug("t1 The thread acquired the lock");
            } finally {
                log.debug("t1 The thread released the lock");
                lock.unlock();
            }
        }, "t1");

        // The main thread obtains a lock (this lock cannot be broken)
        lock.lock();
        log.debug("main The thread acquired the lock");
        // Start t1 thread
        t1.start();
        try {
            Sleeper.sleep(1);
            log.debug("interrupt t1 thread ");
            t1.interrupt();            //Interrupt t1 thread
        } finally {
            lock.unlock();
        }
    }
}

The test uses lock Lock() cannot be interrupted from the blocking queue, waiting for another thread to release the lock

@Slf4j(topic = "c.ReentrantTest")
public class ReentrantTest {

    private static final ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {

        Thread t1 = new Thread(() -> {
            log.debug("t1 Thread start...");
            log.debug("Attempt to acquire lock");
            lock.lock();
            try {
                log.debug("t1 The thread acquired the lock");
            } finally {
                log.debug("t1 The thread released the lock");
                lock.unlock();
            }
        }, "t1");

        // The main thread obtains a lock (this lock cannot be broken)
        lock.lock();
        log.debug("main The thread acquired the lock");
        // Start t1 thread
        t1.start();
        try {
            Sleeper.sleep(1);
            log.debug("interrupt t1 thread ");
            t1.interrupt();            //Interrupt t1 thread
        } finally {
            log.debug("main The thread released the lock");
            lock.unlock();
        }
    }
}
  • The lock() lock can not be interrupted, calling t1. in the main thread. When the main thread () releases the main lock, the main thread () does not use the interrupt lock

Lock timeout (lock.tryLock()) directly exits the blocking queue, and obtaining the lock fails

Prevent unlimited waiting and reduce deadlocks

  • Use lock The trylock() method returns whether the lock acquisition was successful. If successful, it returns true; otherwise, it returns false.
  • And the tryLock method can set the specified waiting time. The parameters are: tryLock(long timeout, TimeUnit unit), where timeout is the longest waiting time and TimeUnit is the time unit
  • In the process of obtaining the lock, if the waiting time is exceeded or interrupted, it will be directly removed from the blocking queue. At this time, obtaining the lock will fail and will not be blocked all the time! (can be used to implement deadlock problem)

Do not set the waiting time and fail immediately

/**
 * Demonstrate tryLock() in RenntrantLock
 *
 * @author xiexu
 * @create 2022-02-01 10:38 AM
 */
@Slf4j(topic = "c.ReentrantTest")
public class ReentrantTest {

    private static final ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            log.debug("Attempt to acquire lock");
            // At this point, the acquisition must fail because the main thread has obtained the lock object
            if (!lock.tryLock()) {
                log.debug("Unable to obtain lock, return");
                return;
            }
            try {
                log.debug("Get lock");
            } finally {
                log.debug("The lock was released");
                lock.unlock();
            }
        }, "t1");

        lock.lock(); // The main thread obtains the lock first
        log.debug("Get lock");
        t1.start();
        // The lock is not released until 2s after the main thread
        Sleeper.sleep(2);
        log.debug("The lock was released");
        lock.unlock();
    }

}

Set the waiting time. If the waiting time is exceeded and the lock is not obtained, it fails. Remove the thread from the blocking queue

/**
 * Demonstrate tryLock(long mills) in RenntrantLock, 
 * If the waiting time set by the lock is exceeded or interrupted, it is removed from the blocking queue
 *
 * @author xiexu
 * @create 2022-02-01 10:38 AM
 */
@Slf4j(topic = "c.ReentrantTest")
public class ReentrantTest {

    private static final ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            log.debug("Attempt to acquire lock");
            try {
                // Set the waiting time. If the waiting time is exceeded or interrupted, the lock acquisition will fail; Exit blocking queue
                if (!lock.tryLock(3, TimeUnit.SECONDS)) {
                    log.debug("Unable to obtain lock, return");
                    return;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.debug("Interrupted, Failed to acquire lock, return");
                return;
            }
            try {
                log.debug("Get lock");
            } finally {
                log.debug("The lock was released");
                lock.unlock();
            }
        }, "t1");

        lock.lock(); // The main thread obtains the lock first
        log.debug("Get lock");
        t1.start();
        t1.interrupt(); // Interrupt t1 thread
        // The lock is not released until 5s after the main thread
        Sleeper.sleep(5);
        lock.unlock();
        log.debug("The lock was released");
    }

}

Through lock Trylock() to solve the dining problem of philosophers (key points)

lock. Trylock (time): try to obtain the lock object. If the set time is exceeded and the lock has not been obtained, exit the blocking queue and release the lock you own

/**
 * ReentrantLock lock lock is used. There is a tryLock() method in this class. If the lock object cannot be obtained within the specified time, it will be removed from the blocking queue without waiting.
 * After obtaining the left-hand chopsticks, try to obtain the right-hand chopsticks. If the chopsticks are occupied by other philosophers and the acquisition fails, put your left-hand chopsticks first,
 * Let go This avoids the deadlock problem
 */
@Slf4j(topic = "c.TestDeadLock")
public class TestDeadLock {

    public static void main(String[] args) {
        Chopstick c1 = new Chopstick("1");
        Chopstick c2 = new Chopstick("2");
        Chopstick c3 = new Chopstick("3");
        Chopstick c4 = new Chopstick("4");
        Chopstick c5 = new Chopstick("5");
        new Philosopher("Socrates", c1, c2).start();
        new Philosopher("Plato", c2, c3).start();
        new Philosopher("Aristotle", c3, c4).start();
        new Philosopher("Heraclitus", c4, c5).start();
        new Philosopher("Archimedes", c5, c1).start();
    }

}

@Slf4j(topic = "c.Philosopher")
// Philosophers
class Philosopher extends Thread {
    Chopstick left; // Chopsticks on the left
    Chopstick right; // Chopsticks on the right

    public Philosopher(String name, Chopstick left, Chopstick right) {
        super(name);
        this.left = left;
        this.right = right;
    }

    @Override
    public void run() {
        while (true) {
            // Got the left-hand chopsticks (for five philosophers, they must have got the left chopsticks at first)
            if (left.tryLock()) {
                try {
                    // At this time, it is found that its right chopsticks are occupied. Use tryLock(),
                    // If the attempt fails, it will release its left chopstick
                    // Critical zone code
                    if (right.tryLock()) { //Try to get the right-hand chopsticks. If the acquisition fails, the left chopsticks will be released
                        try {
                            eat();
                        } finally {
                            right.unlock();
                        }
                    }
                } finally {
                    left.unlock(); //Release the chopsticks in your left hand
                }
            }
        }
    }

    private void eat() {
        log.debug("eating...");
        Sleeper.sleep(0.5);
    }
}

// Inherit ReentrantLock and make chopsticks called locks
class Chopstick extends ReentrantLock {

    String name;

    public Chopstick(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "chopsticks{" + name + '}';
    }

}

Fair lock new ReentrantLock(true)

  • ReentrantLock is a non fair lock by default, but it can be specified as a fair lock.
  • When a thread fails to acquire a lock and enters the blocking queue, the first to enter will obtain the lock after the lock is released. Such access is fair.
  • Generally, ReentrantLock should not be set as fair, which will reduce the concurrency
  • The Monitor lock at the bottom of Synchronized is unfair and has nothing to do with who enters the blocking queue first.
//The default is an unfair lock, which needs to be specified as a fair lock when it is created
ReentrantLock lock = new ReentrantLock(true);

What is a fair lock? What is an unfair lock?

  • New reentrantlock (true)
    • Fair lock can put competing threads on a first in first out blocking queue
    • As long as the thread holding the lock finishes executing, wake up the next thread in the blocking queue to obtain the lock; At this time, the thread that first enters the blocking queue obtains the lock first
  • synchronized, new ReentrantLock())
    • For an unfair lock, when there is A waiting thread A in the blocking queue, the next thread B tries to see whether the lock object can be obtained If successful, there is no need to enter the blocking queue at this time In this way, subsequent thread B obtains the lock first
  • Therefore, the difference between fair and unfair is: whether the thread will try to obtain the lock when executing the synchronous code block. If it will try to obtain the lock, it is unfair. If it will not try to obtain the lock, it will directly enter the blocking queue and wait to be awakened, it is fair

Conditional variable (to avoid false wakeups) - lock Newcondition() creates a condition variable object; Call the await/signal method through the condition variable object to wait / wake up

  • There are also conditional variables in Synchronized, that is, the waitSet waiting set in Monitor. When the conditions are not met, enter waitSet waiting
  • ReentrantLock's conditional variables are more powerful than synchronized in that it supports multiple conditional variables.
    • synchronized means that those threads that do not meet the conditions are notified in a lounge; (false awakening will be caused at this time)
    • ReentrantLock supports multiple lounges, including a lounge dedicated to waiting for cigarettes and a lounge dedicated to waiting for breakfast. It also wakes up according to the lounge when waking up; (false awakening can be avoided)

Use process

  • You need to obtain a lock before await
  • After await is executed, it will release the lock and enter the conditionObject (condition variable) to wait
  • await's thread is awakened (or interrupted, or timed out) to re compete for the lock lock
  • After the lock contention is successful, the execution continues after await
  • The signal method is used to wake up a waiting thread of a condition variable (Lounge)
  • The signalAll method is used to wake up all threads in a condition variable (Lounge)
/**
 * ReentrantLock You can set multiple condition variables (multiple restrooms) relative to the waitSet in the synchronized underlying monitor lock
 *
 * @author xiexu
 * @create 2022-02-03 10:18 AM
 */
@Slf4j(topic = "c.ConditionVariable")
public class ConditionVariable {

    private static boolean hasCigarette = false;
    private static boolean hasTakeout = false;
    private static final ReentrantLock lock = new ReentrantLock();
    // Create a new condition variable (Lounge)
    // Waiting room for cigarettes
    static Condition waitCigaretteSet = lock.newCondition();
    // Waiting room for takeout
    static Condition waitTakeoutSet = lock.newCondition();

    public static void main(String[] args) {

        new Thread(() -> {
            lock.lock();
            try {
                log.debug("Any smoke?[{}]", hasCigarette);
                while (!hasCigarette) {
                    log.debug("No smoke, take a break!");
                    try {
                        // At this time, Xiao Nan enters the lounge waiting for cigarettes
                        waitCigaretteSet.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("Here comes the smoke, You can start working");
            } finally {
                lock.unlock();
            }
        }, "Xiaonan").start();

        new Thread(() -> {
            lock.lock();
            try {
                log.debug("Did you deliver the takeout?[{}]", hasTakeout);
                while (!hasTakeout) {
                    log.debug("No takeout, take a break!");
                    try {
                        // At this time, the little girl entered the lounge waiting for takeout
                        waitTakeoutSet.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("Here comes the takeout, You can start working");
            } finally {
                lock.unlock();
            }
        }, "my daughter").start();

        Sleeper.sleep(1);
        new Thread(() -> {
            lock.lock();
            try {
                log.debug("Here comes the takeout~");
                hasTakeout = true;
                // Wake up the little female thread of takeout
                waitTakeoutSet.signal();
            } finally {
                lock.unlock();
            }
        }, "Delivery").start();

        Sleeper.sleep(1);
        new Thread(() -> {
            lock.lock();
            try {
                log.debug("Here comes the smoker~");
                hasCigarette = true;
                // Wake up Xiaonan thread waiting for smoke
                waitCigaretteSet.signal();
            } finally {
                lock.unlock();
            }
        }, "Cigarette delivery").start();
    }

}

Sequential control of synchronous mode

Fixed running sequence

  • If there are two threads, thread A prints 1 and thread B prints 2
  • Requirements: the program prints 2 first and then 1

await/signal using ReentrantLock

/**
 * Use await/sinal of ReentrantLock to realize sequential printing 2,1
 *
 * @author xiexu
 * @create 2022-02-03 10:42 AM
 */
@Slf4j(topic = "c.SyncPrintWaitTest")
public class SyncPrintWaitTest {

    public static final ReentrantLock lock = new ReentrantLock();
    public static Condition condition = lock.newCondition();
    // Indicates whether the t2 thread has run
    public static boolean t2Runned = false;

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            lock.lock();
            try {
                // Critical zone
                while (!t2Runned) {
                    try {
                        condition.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("1");
            } finally {
                lock.unlock();
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            lock.lock();
            try {
                log.debug("2");
                t2Runned = true;
                condition.signal();
            } finally {
                lock.unlock();
            }
        }, "t2");

        t1.start();
        t2.start();
    }
}

Implementation of Wait/Notify version

/**
 * Use wait/notify to realize sequential printing 2, 1
 *
 * @author xiexu
 * @create 2022-02-03 10:30 AM
 */
@Slf4j(topic = "c.SyncPrintWaitTest")
public class SyncPrintWaitTest {

    public static final Object lock = new Object();
    // Indicates whether the t2 thread has run
    public static boolean t2Runned = false;

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (lock) {
                while (!t2Runned) {
                    try {
                        // Entering the waitset will release the lock
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("1");
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            synchronized (lock) {
                log.debug("2");
                t2Runned = true;
                lock.notify();
            }
        }, "t2");

        t1.start();
        t2.start();
    }
}

Use park/unpark in LockSupport

It can be seen that wait/notify is troublesome in implementation:

  • First of all, you need to ensure that you wait first and then notify, otherwise the wait thread will never wake up. Therefore, the "run flag" is used to judge whether to wait or not
  • Second, if some interfering threads incorrectly notify the wait thread and wait again when the conditions are not met, a while loop is used to solve this problem
  • Finally, notifyAll needs to be used for the wait thread on the wake-up object, because there may be more than one waiting thread on the synchronization object
/**
 * Use Park and unpark in LockSupport to print 2 and 1 in sequence
 *
 * @author xiexu
 * @create 2022-02-03 10:48 AM
 */
@Slf4j(topic = "c.SyncPrintWaitTest")
public class SyncPrintWaitTest {

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            LockSupport.park();
            log.debug("1");
        }, "t1");

        Thread t2 = new Thread(() -> {
            log.debug("2");
            LockSupport.unpark(t1);
        }, "t2");

        t1.start();
        t2.start();
    }

}

  • The park and unpark methods are flexible. They will call first and who will call later. In addition, the "pause" and "resume" are carried out in the unit of thread, and the "synchronization object" and "run flag" are not required
  • Because wait notify actually uses the protective pause mode, and the underlying implementation of Park unpark is actually the embodiment of protective pause

Alternate output

Thread 1 outputs a 5 times, thread 2 outputs b 5 times, and thread 3 outputs c 5 times. Now ask to output abcabcabcabcabcabc abc

wait/notify version

/**
 * Description: Use wait/notify to realize the alternate printing of abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcab
 *
 * @author xiexu
 * @create 2022-02-03 10:50 AM
 */
@Slf4j(topic = "c.TestWaitNotify")
public class TestWaitNotify {
    public static void main(String[] args) {
        WaitNotify waitNotify = new WaitNotify(1, 5);

        new Thread(() -> {
            waitNotify.print("a", 1, 2);

        }, "a thread ").start();

        new Thread(() -> {
            waitNotify.print("b", 2, 3);

        }, "b thread ").start();

        new Thread(() -> {
            waitNotify.print("c", 3, 1);

        }, "c thread ").start();
    }
}

@Slf4j(topic = "c.WaitNotify")
@Data
class WaitNotify {

    // Wait mark
    private int flag;

    // Number of cycles
    private int loopNumber;

    public WaitNotify(int flag, int loopNumber) {
        this.flag = flag;
        this.loopNumber = loopNumber;
    }

    /*
            The output content waits for the next tag
            a           1          2
            b           2          3
            c           3          1
         */

    /**
     * @param str      Printed content
     * @param waitFlag Wait mark
     * @param nextFlag Next tag
     */
    public void print(String str, int waitFlag, int nextFlag) {
        for (int i = 0; i < loopNumber; i++) {
            synchronized (this) {
                while (waitFlag != this.flag) {
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.print(str);
                this.flag = nextFlag;
                this.notifyAll();
            }
        }
    }

}

await/signal version

/**
 * Use await/signal to realize the alternating printing of abcabcabcabcabc abcabcabcabc by three threads
 *
 * @author xiexu
 * @create 2022-02-03 12:38 AM
 */
@Slf4j(topic = "c.TestWaitNotify")
public class TestAwaitSignal {

    public static void main(String[] args) throws InterruptedException {
        AwaitSignal awaitSignal = new AwaitSignal(5);
        // a thread Lounge
        Condition a_condition = awaitSignal.newCondition();
        // b thread Lounge
        Condition b_condition = awaitSignal.newCondition();
        // c thread Lounge
        Condition c_condition = awaitSignal.newCondition();

        new Thread(() -> {
            awaitSignal.print("a", a_condition, b_condition);
        }, "a").start();

        new Thread(() -> {
            awaitSignal.print("b", b_condition, c_condition);
        }, "b").start();

        new Thread(() -> {
            awaitSignal.print("c", c_condition, a_condition);
        }, "c").start();

        Thread.sleep(1000);
        System.out.println("==========start=========");
        awaitSignal.lock();
        try {
            a_condition.signal();  //Wake up thread a first
        } finally {
            awaitSignal.unlock();
        }
    }
}

class AwaitSignal extends ReentrantLock {

    // Number of cycles
    private final int loopNumber;

    public AwaitSignal(int loopNumber) {
        this.loopNumber = loopNumber;
    }

    /**
     * @param str     Printed content
     * @param current Current thread
     * @param next    Next thread Lounge
     */
    public void print(String str, Condition current, Condition next) {
        for (int i = 0; i < loopNumber; i++) {
            lock();
            try {
                try {
                    current.await();
                    //System.out.print("i:==="+i);
                    System.out.print(str);
                    next.signal();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } finally {
                unlock();
            }
        }
    }
}

park/unpark implementation of LockSupport

/**
 * Use park/unpark to realize three threads to print abcabcabcabcabc abcacabc alternately
 *
 * @author xiexu
 * @create 2022-02-03 12:50 AM
 */
@Slf4j(topic = "c.TestWaitNotify")
public class TestParkUnpark {
    static Thread t1;
    static Thread t2;
    static Thread t3;

    public static void main(String[] args) {
        ParkUnpark parkUnpark = new ParkUnpark(5);

        t1 = new Thread(() -> {
            parkUnpark.print("a", t2);
        }, "t1");

        t2 = new Thread(() -> {
            parkUnpark.print("b", t3);
        }, "t2");

        t3 = new Thread(() -> {
            parkUnpark.print("c", t1);
        }, "t3");

        t1.start();
        t2.start();
        t3.start();

        LockSupport.unpark(t1); //Wake up the t1 thread first
    }
}

class ParkUnpark {
    // Number of cycles
    private final int loopNumber;

    public ParkUnpark(int loopNumber) {
        this.loopNumber = loopNumber;
    }

    /**
     * @param str
     * @param nextThread Next thread to wake up
     */
    public void print(String str, Thread nextThread) {
        for (int i = 0; i < loopNumber; i++) {
            LockSupport.park(); // Blocking the current thread
            System.out.print(str);
            LockSupport.unpark(nextThread); // Wake up the next thread
        }
    }
}

Summary of this chapter

Keywords: Java Multithreading thread lock

Added by tave on Thu, 03 Feb 2022 12:19:48 +0200