Pipeline 4 of the java Concurrent Programming sharing model

Posted by yoma31 on Wed, 26 Jan 2022 04:46:49 +0100

Preface

This series is based on black horse videos: java Concurrent Programming I haven't finished yet. Overall, this is the best concurrent programming video I've ever seen. Here are notes based on videos. In the notes below, Java concurrent programming is also interspersed with an explanation of this book

1. Activeness

1. Deadlock problem

Some references to the book The Art of Java Concurrent Programming

When using locks, deadlocks can be easily caused if they are not used correctly. For example, thread t1 acquires the lock of A and then wants to acquire the lock of B, while thread t2 acquires the lock of B and wants to acquire the lock of A. Both threads can't run at this time, they both need the resources that the other side gets. At this time, the two threads can't continue running, which is deadlock.

Of course, we would not write this deadlock so clearly in normal projects, but it is unavoidable that thread abnormalities will occur in projects, especially in complex projects. This problem may occur when t1 thread gets the lock because some abnormalities can not be released in time, or t1 gets a database lock. An exception was thrown when the lock was released, but it was not released.

Once a deadlock occurs, we can feel that the business stops and cannot continue, at which point we need to see which thread is having a problem with the dump thread. For example, the following code: Thread A acquires Lock A, but needs Lock B, Thread B acquires Lock B, and needs Lock A

public class DeadLock {

    private static String A = "A";
    private static String B = "B";

    private static void deadLock(){
        Thread t1 = new Thread(()->{
            synchronized (A){
                System.out.println("thread A Lock taken A, Lock required B");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (B){

                }
            }
        }, "thread A");

        Thread t2 = new Thread(()->{
            synchronized (B){
                System.out.println("thread B Lock taken B, Lock required A");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (A){

                }
            }
            //This should be B, just know it
        }, "thread A");

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


    public static void main(String[] args) {
        deadLock();
        //Thread A got lock A and needed lock B
        //Thread B got Lock B and needed Lock A
    }
}

We look at deadlocks through the dump thread:

  1. View deadlock threads using jps
		D:\javaCode\JULLearn>jps
		56160 Launcher
		36504 DeadLock
		36648 Jps
		49304

  1. Using the jstack command to output the deadlock message to the file, you can see that the deadlock occurred on lines 40 and 26. That is, the segment where the B lock is acquired in thread A and the segment where the A lock is acquired in thread B.
 jstack 36504 > deadLock.log
  1. Open the file to find the deadlock information, two threads A because they forgot to rename when copying above
Found one Java-level deadlock:
=============================
"thread A":
  waiting to lock monitor 0x000001ad809541b8 (object 0x000000076ba739d8, a java.lang.String),
  which is held by "??Check A"
"thread A":
  waiting to lock monitor 0x000001ad80956af8 (object 0x000000076ba73a08, a java.lang.String),
  which is held by "thread A"

Java stack information for the threads listed above:
===================================================
"thread A":
	//40 lines
	at com.jianglianghao.HeiMaJUC.Unit4.DeadLock.lambda$deadLock$1(DeadLock.java:40)
	- waiting to lock <0x000000076ba739d8> (a java.lang.String)
	- locked <0x000000076ba73a08> (a java.lang.String)
	at com.jianglianghao.HeiMaJUC.Unit4.DeadLock$$Lambda$2/1607521710.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:748)
	
"thread A":
	//26 lines
	at com.jianglianghao.HeiMaJUC.Unit4.DeadLock.lambda$deadLock$0(DeadLock.java:26)
	- waiting to lock <0x000000076ba73a08> (a java.lang.String)
	- locked <0x000000076ba739d8> (a java.lang.String)
	at com.jianglianghao.HeiMaJUC.Unit4.DeadLock$$Lambda$1/1828972342.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.



Several common ways to avoid deadlocks:

  • Avoid multiple locks acquired simultaneously by one thread
  • Avoid one thread consuming multiple resources simultaneously within a lock, and try to ensure that each lock consumes only one resource
  • Try using a timer lock, use lock. The tryLock (timeout) method replaces the internal locking mechanism. The advantage is that if a lock is not acquired, it can be discarded to avoid deadlocks
  • For database locks, both locking and unlocking must be in a database connection pool, otherwise unlocking fails.



2. Philosopher's Dining Question


There are five philosophers sitting around the round table. They do only two things, think and eat, think for a while and then think after dinner. There are five chopsticks on the table. Each philosopher has one chopstick at his left and right hand. If chopsticks are held by someone around you, you have to wait

When each philosopher, a thread, holds a chopstick, they wait for another thread to release the lock, causing a deadlock. This thread did not end as expected and could not continue to execute. It is classified as an "activity" problem. In addition to deadlocks, there are also two situations, a live lock and a hungry person.

We can solve this problem by tryLock(): process, everyone uses tryLock to get the chopsticks of the other hand first, and unlock them if they can't get them, so that they can be released in time even if they don't need to.

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();
    }
}

@Slf4j
class Philosopher extends Thread {
    Chopstick left;
    Chopstick 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
            if(left.tryLock()) {
                //Try to get the chopstick lock
                try {
                    //Try to get the right chopstick lock
                    if(right.tryLock()){
                        try{
                            eat();
                        }finally{
                            right.unlock();
                        }
                    }
                }finally {
                    //If you can't get your right hand, put your left hand on yourself
                    left.unlock();
                }
            }
        }
    }

    Random random = new Random();
    private void eat() {
        log.debug("eating...");
        sleep.mySleep(1);
    }
}

class Chopstick extends ReentrantLock {
    String name;

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

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

DEBUG [Aristotle] (22:28:53,840) (TestDeadLock.java:67) - eating...
DEBUG [Socrates] (22:28:53,840) (TestDeadLock.java:67) - eating...
DEBUG [Aristotle] (22:28:54,860) (TestDeadLock.java:67) - eating...
DEBUG [Socrates] (22:28:54,863) (TestDeadLock.java:67) - eating...
DEBUG [Aristotle] (22:28:55,860) (TestDeadLock.java:67) - eating...
DEBUG [Archimedes] (22:28:55,876) (TestDeadLock.java:67) - eating...
DEBUG [Plato] (22:28:56,866) (TestDeadLock.java:67) - eating...
DEBUG [Heraclitus] (22:28:56,882) (TestDeadLock.java:67) - eating...
DEBUG [Socrates] (22:28:57,872) (TestDeadLock.java:67) - eating...
DEBUG [Heraclitus] (22:28:57,887) (TestDeadLock.java:67) - eating...
DEBUG [Socrates] (22:28:58,877) (TestDeadLock.java:67) - eating..



3. Active Lock

A live lock occurs when two threads change each other's end condition. Finally, no one can end, for example, by defining a count with an initial value of 10, then thread 1 + 1 per second, thread 2 - 1 per second, thread 1's end condition is count to 20, and thread 2's end condition is count to 0, at which point neither thread can end.

@Slf4j
public class TestLiveLock {

    static volatile AtomicInteger count = new AtomicInteger(10);

    static final Object obj = new Object();

    public static void main(String[] args) {
        new Thread(()->{
            while(true){
                try {
                    Thread.sleep(1000);
                    if(count.decrementAndGet() == 0){
                        break;
                    }
                    log.debug("thread t1 ==> {}", count.get());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }


            }
        }, "t1").start();

        new Thread(()->{
            while(true){
                try {
                    Thread.sleep(1000);
                    if(count.getAndIncrement() == 0){
                        break;
                    }
                    log.debug("thread t2 ==> {}", count.get());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                
                //"D:\Java\jdk-8u281-windows X64\bin\java.exe" "-javaagent:D:\IDAE\IntelliJ IDEA 2020.3.2\lib\idea_rt.jar=61052:D:\IDAE\IntelliJ IDEA 2020.3.2\bin" -Dfile.encoding=UTF-8 -classpath "D:\java\jdk-8u281-windows X64\jre\lib\charsets.jar;D:\java\jdk-8u281-windows X64\jre\lib\deploy.jar;D:\java\jdk-8u281-windows X64\jre\lib\ext\access-bridge-64.jar;D:\java\jdk-8u281-windows X64\jre\lib\ext\cldrdata.jar;D:\java\jdk-8u281-windows X64\jre\lib\ext\dnsns.jar;D:\java\jdk-8u281-windows X64\jre\lib\ext\jaccess.jar;D:\java\jdk-8u281-windows X64\jre\lib\ext\jfxrt.jar;D:\java\jdk-8u281-windows X64\jre\lib\ext\localedata.jar;D:\java\jdk-8u281-windows X64\jre\lib\ext\nashorn.jar;D:\java\jdk-8u281-windows X64\jre\lib\ext\sunec.jar;D:\java\jdk-8u281-windows X64\jre\lib\ext\sunjce_provider.jar;D:\java\jdk-8u281-windows X64\jre\lib\ext\sunmscapi.jar;D:\java\jdk-8u281-windows X64\jre\lib\ext\sunpkcs11.jar;D:\java\jdk-8u281-windows X64\jre\lib\ext\zipfs.jar;D:\java\jdk-8u281-windows X64\jre\lib\javaws.jar;D:\java\jdk-8u281-windows X64\jre\lib\jce.jar;D:\java\jdk-8u281-windows X64\jre\lib\jfr.jar;D:\java\jdk-8u281-windows X64\jre\lib\jfxswt.jar;D:\java\jdk-8u281-windows X64\jre\lib\jsse.jar;D:\java\jdk-8u281-windows X64\jre\lib\management-agent.jar;D:\java\jdk-8u281-windows X64\jre\lib\plugin.jar;D:\java\jdk-8u281-windows X64\jre\lib\resources.jar;D:\java\jdk-8u281-windows X64\jre\lib\rt.jar;D:\javaCode\JULLearn\target\classes;D:\tools\java\maven\repository\org\slf4j\slf4j-api\1.7.32\slf4j-api-1.7.32.jar;D:\tools\java\maven\repository\org\slf4j\slf4j-log4j12\1.7.21\slf4j-log4j12-1.7.21.jar;D:\tools\java\maven\repository\log4j\log4j\1.2.17\log4j-1.2.17.jar;D:\tools\java\maven\repository\org\projectlombok\lombok\1.18.22\lombok-1.18.22.jar;D:\tools\java\maven\repository\org\openjdk\jol\jol-core\0.16\jol-core-0.16.jar" com.jianglianghao.HeiMaJUC.Unit4.TestLiveLock
                //DEBUG [t1] (22:53:07,615) (TestLiveLock.java:30) - Thread T1 ==> 10
                //DEBUG [t2] (22:53:07,615) (TestLiveLock.java:46) - Thread T2 ==> 11
                //DEBUG [t2] (22:53:08,623) (TestLiveLock.java:46) - Thread T2 ==> 10
                //DEBUG [t1] (22:53:08,623) (TestLiveLock.java:30) - Thread T1 ==> 10
                //DEBUG [t2] (22:53:09,631) (TestLiveLock.java:46) - Thread T2 ==> 11
                //DEBUG [t1] (22:53:09,631) (TestLiveLock.java:30) - Thread T1 ==> 10
                //DEBUG [t2] (22:53:10,642) (TestLiveLock.java:46) - Thread T2 ==> 11
                //DEBUG [t1] (22:53:10,642) (TestLiveLock.java:30) - Thread T1 ==> 10
                //DEBUG [t1] (22:53:11,649) (TestLiveLock.java:30) - Thread T1 ==> 10
                //DEBUG [t2] (22:53:11,649) (TestLiveLock.java:46) - Thread T2 ==> 11
                //DEBUG [t1] (22:53:12,657) (TestLiveLock.java:30) - Thread T1 ==> 10
                //DEBUG [t2] (22:53:12,657) (TestLiveLock.java:46) - Thread T2 ==> 10
                //DEBUG [t2] (22:53:13,659) (TestLiveLock.java:46) - Thread T2 ==> 10
                //DEBUG [t1] (22:53:13,659) (TestLiveLock.java:30) - Thread T1 ==> 10
                //DEBUG [t1] (22:53:14,666) (TestLiveLock.java:30) - Thread T1 ==> 10
                //DEBUG [t2] (22:53:14,666) (TestLiveLock.java:46) - Thread T2 ==> 11
                //DEBUG [t1] (22:53:15,675) (TestLiveLock.java:30) - Thread T1 ==> 9
                //DEBUG [t2] (22:53:15,675) (TestLiveLock.java:46) - Thread T2 ==> 10
            }
        }, "t2").start();
    }
}

The solution is to change the amount of sleep.



4. Hunger

Hunger is defined in many tutorials as a thread that, because of its low priority, can never get the CPU scheduled execution and cannot end. Hunger is not easy to demonstrate, and it can involve hunger when reading and writing locks.

In the following picture, two threads acquire locks in the same order, which creates a deadlock problem

Solution: We change the order in which locks are acquired, for example, thread A acquires lock A first, and then thread B tries to acquire lock B. It finds that lock B cannot be acquired, thread A acquires lock B, and thread B tries to acquire lock B, and it finds that lock B cannot be acquired. This avoids the deadlock problem.



5. ReentrantLock

It has the following characteristics relative to synchronized

  • Interruptable
  • Timeout can be set
  • It can be set to a fair lock, where each thread has a chance to compete,
  • Supports multiple conditional variables
    Like synchronized, reentrant is supported

Basic grammar:

// Acquire locks
reentrantLock.lock();
try {
 // Critical zone
} finally {
 // Release lock
 reentrantLock.unlock();
}



1. Reentrant

  • Reentrant means that the same thread, if it acquired the lock for the first time, has the right to acquire it again because it is the owner of the lock
  • If the lock is not reentrant, the second time the lock is acquired, the lock itself will be blocked
  • Here re-entry means that the lock has been acquired for the first time and can be acquired again
Code
@Slf4j
public class Test22 {
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        lock.lock();
        try {
            log.debug("entry main");
            m1();
        }finally {
            lock.unlock();
        }
    }

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

    public static void m2(){
        lock.lock();
        try {
            log.debug("entry m2");
        }finally {
            lock.unlock();
        }
    }
    //DEBUG [main] (13:11:56,073) (Test22.java:21) - entry main
    //DEBUG [main] (13:11:56,075) (Test22.java:31) - entry m1
    //DEBUG [main] (13:11:56,075) (Test22.java:41) - entry m2
}



2. Interruptable

Use lock.lockInterruptibly(); Method to get the lock, when another thread gets it, the lock can be interrupted, throwing an exception

@Slf4j
public class Test23 {

    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            //If there is no competition then this method will acquire lock object locks
            //If there is competition, it enters the blocking queue and can be interrupted by other threads using the interrupt method
            try {
                log.debug("Attempting to acquire a lock");
                //lock.lockInterruptibly():The lockInterruptibly() method is special.
                // When a lock is acquired this way, if another thread is waiting to acquire the lock, the thread can respond to the interruption.
                // This is the wait state of the interrupt thread. That is, when two threads pass through lock at the same time. LockInterruptibly()
                // If thread A acquires a lock and thread B waits, threadB is called on thread B. Interrupt()
                // Method can interrupt the waiting process of thread B.
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.debug("Interrupted, No Lock Acquired, Return");
                return;
            }

            try {
                log.debug("Acquire locks");
            } finally {
                lock.unlock();
            }
        }, "t1");

        lock.lock();
        thread.start();
        sleep.mySleep(1);
        thread.interrupt();
        //DEBUG [t1] (13:22:49,872) (Test23.java:25) - Attempting to acquire a lock
        //java.lang.InterruptedException
        //	at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
        //	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
        //	at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
        //	at com.jianglianghao.HeiMaJUC.Unit4.Test23.lambda$main$0(Test23.java:31)
        //	at java.lang.Thread.run(Thread.java:748)
        //DEBUG [t1] (13:22:50,876) (Test23.java:34) - Interrupted, unlocked, returned
    }
}



3. Lock timeout

Lock acquisition time can be set, in the specified time can not be obtained directly not to get, no exception thrown, interruption will throw an exception

@Slf4j
//You can set it to return without having to wait until the lock is acquired
public class Test24 {

    private static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            //Attempt to acquire a lock, succeed and fail without entering the blocking queue
            boolean b = false;
            try {
                //Attempt to acquire a lock, fail return directly if not acquired in 1 second, avoid waiting all the time
                //tryLock can also interrupt and throw exceptions
                b = lock.tryLock(2, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.debug("Unable to acquire lock");
                return;
            }
            if(b == false){
                log.debug("Unable to acquire lock");
                return;
            }

            try {
                log.debug("Lock acquired");
            }finally {
                lock.unlock();
            }
        }, "t1");
        
        //DEBUG [main] (00:22:09,185) (Test24.java:48) - The main thread acquired the lock
        //DEBUG [t1] (00:22:11,196) (Test24.java:35) - Unable to acquire locks

        lock.lock();
        log.debug("Lock acquired by main thread");
        t1.start();
        sleep.mySleep(4);
        lock.unlock();
    }

}



4. Fair Lock

ReentrantLock is unfair by default. Threads do not need to lock sequentially. Instead, each thread can grab and have a chance to get a lock. A fair lock is generally unnecessary and reduces concurrency.

public class UnEqualLock {
    public static void main(String[] args) throws InterruptedException {
        ReentrantLock lock = new ReentrantLock(false);
        lock.lock();
        for (int i = 0; i < 500; i++) {
            new Thread(() -> {
                lock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + " running...");
                } finally {
                    lock.unlock();
                }
            }, "t" + i).start();
        }
        // Contend for locks after 1s
        Thread.sleep(1000);
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " start...");
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + " running...");
            } finally {
                lock.unlock();
            }
        }, "Force Insert").start();
        lock.unlock();
    }

    //t0 running...
    //Force insert start...
    //t1 running...
    //t2 running...
    //t3 running...
    //t6 running...
    //t4 running...
    //t5 running...
}



5. Conditional variables

There is also a conditional variable in synchronized, which is the waitSet lounge where we talked about principles, which enters the waitSet when the conditions are not satisfied and waits until the fields are satisfied before entering the queue to compete for the use of locks

ReentrantLock's conditional variable is more powerful than synchronized because it supports multiple conditional variables, which means different threads can be given a wait environment based on different variables

  • synchronized is a message that threads that do not meet the criteria are all in a lounge, etc.
  • And ReentrantLock supports multiple lounges, with specially smoked lounges, specially breakfast lounges, and also by lounge when waking up. The advantage is that it is easier to control thread execution.

Key points for use:

  • Lock required before await
  • After await executes, it releases the lock, enters conditionObject and waits
  • The await thread was awakened (or interrupted, or timed out) to reclaim the lock
  • After successful competitive lock, continue execution from await
@Slf4j
public class Test25 {
    static final Object room = new Object();
    static boolean hasCigarette = false;
    static boolean hasTakeout = false;
    static ReentrantLock ROOM = new ReentrantLock();
    // Lounge waiting for smoke
    static Condition waitCigaretteSet = ROOM.newCondition();
    // Lounge waiting for takeout
    static Condition waitTakeoutSet = ROOM.newCondition();

    public static void main(String[] args) {


        new Thread(() -> {
            ROOM.lock();
            try {
                log.debug("Is there any smoke?[{}]", hasCigarette);
                while (!hasCigarette) {
                    log.debug("No smoke, take a break!");
                    try {
                        waitCigaretteSet.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("You're ready to start working");
            } finally {
                ROOM.unlock();
            }
        }, "Xiaonan").start();

        new Thread(() -> {
            ROOM.lock();
            try {
                log.debug("Has the delivery been delivered?[{}]", hasTakeout);
                while (!hasTakeout) {
                    log.debug("No takeaway, break up first!");
                    try {
                        waitTakeoutSet.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("You're ready to start working");
            } finally {
                ROOM.unlock();
            }
        }, "my daughter").start();

        sleep.mySleep(1);

        //Delivery
        new Thread(() -> {
            ROOM.lock();
            try {
                hasTakeout = true;
                log.debug("Take-out delivered");
                waitTakeoutSet.signal();
            } finally {
                ROOM.unlock();
            }
        }, "Delivery").start();

        sleep.mySleep(1);

        //Cigarette-feeding
        new Thread(() -> {
            ROOM.lock();
            try {
                hasCigarette = true;
                log.debug("The smoke arrived");
                waitCigaretteSet.signal();
            } finally {
                ROOM.unlock();
            }
        }, "Cigarette-feeding").start();
    }

}

Result:

DEBUG [Xiaonan] (00:44:42,937) (Test25.java:33) - Is there any smoke?[false]
DEBUG [Xiaonan] (00:44:42,939) (Test25.java:35) - No smoke, take a break!
DEBUG [my daughter] (00:44:42,939) (Test25.java:51) - Has the delivery been delivered?[false]
DEBUG [my daughter] (00:44:42,939) (Test25.java:53) - No takeaway, break up first!
DEBUG [Delivery] (00:44:43,947) (Test25.java:73) - Take-out delivered
DEBUG [my daughter] (00:44:43,947) (Test25.java:60) - You're ready to start working
DEBUG [Cigarette-feeding] (00:44:44,955) (Test25.java:87) - The smoke arrived
DEBUG [Xiaonan] (00:44:44,956) (Test25.java:42) - You're ready to start working

Process finished with exit code 0



2. Control of Synchronization Mode

1. Fixed running order

1. Use wait - notify to achieve the effect of inter-thread communication

The following code uses t2 to wake up t1

@Slf4j
public class Test26 {

    static final ReentrantLock lock = new ReentrantLock();
    //Indicates whether t2 has been run
    static boolean run2 = false;

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (lock){
                //t2 waits without running t1
                while(run2 != true){
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("1");
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            synchronized (lock){
                log.debug("2");
                run2 = true;
                lock.notifyAll();
            }

        }, "t2");

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

Result:

DEBUG [t2] (00:49:04,144) (Test26.java:38) - 2
DEBUG [t1] (00:49:04,146) (Test26.java:32) - 1



2. park-unpark: thread t2 wakes up thread t1

@Slf4j
public class Test27 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            LockSupport.park();
            log.debug("1");
        }, "t1");
        t1.start();
        new Thread(()->{
            log.debug("2");
            LockSupport.unpark(t1);
        }, "t2").start();
    }
}

Result:

DEBUG [t2] (00:52:05,548) (Test27.java:24) - 2
DEBUG [t1] (00:52:05,550) (Test27.java:20) - 1



2. Alternate Output

1. wait-notify

This means setting the current flag bit and the next flag bit, which is set to true after the current thread has finished executing

@Slf4j
public class Test28 {
    public static void main(String[] args) {
        waitNotify wn = new waitNotify(1, 5);
        new Thread(()->{
            wn.print("a", 1, 2);
        }, "t1").start();
        new Thread(()->{
            wn.print("b", 2, 3);
        }, "t2").start();
        new Thread(()->{
            wn.print("c", 3, 1);
        }, "t3").start();
    }
}

class waitNotify{
    //Print
    public void print(String str, int waitFlag, int nextFlag){
        for (int i = 0; i < loopNumber; i++) {
            synchronized (this){
                while(flag != waitFlag){
                    //wait for
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //Output current
                System.out.print(str);
                //Set Next
                flag = nextFlag;
                //Wake up all threads, only the next one can actually execute
                this.notifyAll();
            }
        }
    }

    //sign
    private int flag;
    //Number of loops
    private int loopNumber;

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

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

Output results:

abcabcabcabcabc



2. await-signal

How to use it: Load three threads into different waiting rooms. First the main thread wakes up a, then a wakes up b after execution is complete...

public class Test30 {
    public static void main(String[] args) throws InterruptedException {
        AwaitSingle awaitSingle = new AwaitSingle(5);
        Condition a = awaitSingle.newCondition();
        Condition b = awaitSingle.newCondition();
        Condition c = awaitSingle.newCondition();
        //All other threads begin await
        new Thread(()->{
            awaitSingle.print("a", a, b);
        }, "t1").start();
        new Thread(()->{
            awaitSingle.print("b", b, c);
        }, "t2").start();
        new Thread(()->{
            awaitSingle.print("c", c, a);
        }, "t3").start();
        //Main thread wakes up a first
        Thread.sleep(1000);
        awaitSingle.lock();
        try{
            //Main Thread Start
            System.out.println("start...");
            // Wake up first a
            a.signal();
        }finally {
            awaitSingle.unlock();
        }
    }
}

class AwaitSingle extends ReentrantLock {
    private int loopNumber;

    public AwaitSingle(int loopNumber) {
        this.loopNumber = loopNumber;
    }
    //Parameter 1: Printed Content Parameter 2: Which Lounge to Enter Waiting
    public void print(String str, Condition current, Condition next){
        for (int i = 0; i < loopNumber; i++) {
            lock();
            try {
                current.await();
                //Print the current lounge's
                System.out.print(str);
                //Wake up the next Lounge
                next.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally{
                unlock();
            }
        }
    }
}


Execution results:

start...
abcabcabcabcabc



3. park-unpark

All threads pause with park, the main thread wakes t1, and t2 wakes up after T1 execution is complete, and loops on

public class Test31 {
    static Thread t1;
    static Thread t2;
    static Thread t3;
    public static void main(String[] args) {
        ParkUnpark pk = new ParkUnpark(5);
        t1 = new Thread(()->{
            pk.print("a", t2);
        }, "t1");
        t2 = new Thread(()->{
            pk.print("b", t3);
        }, "t2");
        t3 = new Thread(()->{
            pk.print("c", t1);
        }, "t3");
        t1.start();
        t2.start();
        t3.start();

        //Main Thread Initiation
        LockSupport.unpark(t1);
    }
}

class ParkUnpark{
    private int loopNumber;

    public ParkUnpark(int loopNumber) {
        this.loopNumber = loopNumber;
    }
    //park stops the current thread directly, just knowing what the next thread is
    public void print(String str, Thread next){
        for (int i = 0; i < loopNumber; i++) {
            LockSupport.park();
            System.out.print(str);
            LockSupport.unpark(next);
        }
    }
}

Output of results:

abcabcabcabcabc






If there are any errors, please point out!!!

Topics: Java Back-end Multithreading