java-fair lock-re-lockable-deadlock-interrupt (how to gracefully stop a thread)

Keywords: Java Multithreading Rust

1. Fair-Unfair

1.1 Ticket Selling Cases (Unfair)

class Ticket
{
    private int number = 50;
    private Lock lock = new ReentrantLock(); //The default is an unfair lock. If you want an average allocation, =--, to be fair, change the constructor parameter to true
    public void sale()
    {
        lock.lock();
        try
        {
            if(number > 0)
            {
                System.out.println(Thread.currentThread().getName()+"\t Sell No.: "+(number--)+"\t Remaining: "+number);
            }
        }finally {
            lock.unlock();
        }
    }

    /*Object objectLock = new Object();

    public void sale()
    {
        synchronized (objectLock)
        {
            if(number > 0)
            {
                System.out.println(Thread.currentThread().getName()+"\t Sell: "+(number--)+"t Remaining: "+number";
            }
        }
    }*/
}

/**
 * @auther zzyy
 * @create 2020-07-09 17:48
 */
public class SaleTicketDemo
{
    public static void main(String[] args)
    {
        Ticket ticket = new Ticket();

        new Thread(() -> { for (int i = 1; i <=55; i++) ticket.sale(); },"a").start();
        new Thread(() -> { for (int i = 1; i <=55; i++) ticket.sale(); },"b").start();
        new Thread(() -> { for (int i = 1; i <=55; i++) ticket.sale(); },"c").start();
        new Thread(() -> { for (int i = 1; i <=55; i++) ticket.sale(); },"d").start();
        new Thread(() -> { for (int i = 1; i <=55; i++) ticket.sale(); },"e").start();
    }
}

Result:

It is easy to see a thread grabbing all resources and unevenly allocated

2.1 Three questions:

1.1 Why is there a fair/unfair design?
There is still a slight time lag between resuming a suspended thread and acquiring a true lock, which is negligible from the developer's point of view, but significant from the CPU's point of view. Therefore, unfair locks make better use of CPU time slices and minimize CPU idle state time.
1.2 Why default unfair:
An important consideration for using multithreads is the overhead of thread switching. When unfair locks are used, when a thread requests a lock to acquire the synchronization state and then releases the synchronization state, since there is no need to consider whether there are any precursor nodes, the probability that a thread that just released the lock will acquire the synchronization state again at this point becomes very large, thus reducing the overhead of the thread.

2. Fair Lock:
Fair locking guarantees fairness in queuing. Unfair locking arbitrarily ignores this rule, so it may result in queuing for a long time and no chance to get a lock.
This is the legend "lock hunger"

3. When to use equity and when to be unfair:
To throughput is unfair, otherwise it is fair to lock.

2. Re-lockable:

Both synchronized and ReenctrantLock are reentrant
Concepts:
Multiple processes in a thread can acquire the same lock and hold it to enter again. They can acquire their own internal locks.

A lock is always available when another synchronized-decorated method or code block of this class is called inside a synchronized-decorated method or code block.

public class ReEntryLockDemo
{
    static Lock lock = new ReentrantLock();

    public static void main(String[] args)
    {
        new Thread(() -> {
            lock.lock();
            try
            {
                System.out.println("----Outer Call lock");
                lock.lock();
                try
                {
                    System.out.println("----Inner Call lock");
                }finally {
                    // Here is a deliberate comment that the number of locks and releases is different
                    // Because the number of locks and releases is different, the second thread is always unable to get the lock, causing it to wait.
                    lock.unlock(); // Normally, unlock several times
                }
            }finally {
                lock.unlock();
            }
        },"a").start();

        new Thread(() -> {
            lock.lock();
            try
            {
                System.out.println("b thread----Outer Call lock");
            }finally {
                lock.unlock();
            }
        },"b").start();

    }
}

ReenctrantLock must be unlocked several times if it is locked, otherwise the lower thread will be blocked if the current thread is exhausted and not released cleanly.

_2.1 Deadlock:

public class DeadLockDemo
{
    static Object lockA = new Object();
    static Object lockB = new Object();


    public static void main(String[] args)
    {

        Thread a = new Thread(() -> {
            synchronized (lockA) {
                System.out.println(Thread.currentThread().getName() + "\t" + " Own Hold A Lock, expected B lock");

                try {TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

                synchronized (lockB) {
                    System.out.println(Thread.currentThread().getName() + "\t Get B Lock Success");
                }
            }
        }, "a");
        a.start();

        new Thread(() -> {
            synchronized (lockB)
            {
                System.out.println(Thread.currentThread().getName()+"\t"+" Own Hold B Lock, expected A lock");

                try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

                synchronized (lockA)
                {
                    System.out.println(Thread.currentThread().getName()+"\t Get A Lock Success");
                }
            }
        },"b").start();


    }
}

Investigation proves deadlock:
Jps-l_jstack class name
Or jconsole investigation

3. Interruption

1. First
A thread should not be forced to interrupt or stop by other threads, but should stop by itself.
So Thread.stop, Thread.suspend, Thread.resume have all been discarded.

Secondly
There is no way to stop a thread immediately in Java, but stopping a thread is especially important, such as canceling a time-consuming operation.
Therefore, Java provides a mechanism for stopping threads -- interrupts.

Interrupts are just a collaboration mechanism. Java does not add any syntax to interrupts. The process of interrupting completely requires the programmer to implement it.
To interrupt a thread, you need to manually call the thread's interrupt method, which simply sets the interrupt identifier of the thread object to true.
Then you need to write your own code to constantly detect the identity bit of the current thread. If true, it means that another thread requires the thread to break.
You need to write your own code to do exactly what you should do at this point.

Each thread object has an identity indicating whether or not the thread was interrupted; the identity bit is true for interruption and false for uninterrupted.
The thread's identity bit is set to true by calling the thread object's interrupt method; it can be called from another thread or from its own thread.

_3.1 Interruption Example 1: volatile

 public static void main(String[] args) {
        new Thread(()->{

            while (true){
                if (isStop){
                    System.out.println("========isStop=true,Program End");
                    break;
                }
                System.out.println("-----hello,");
            }

        },"t1").start();

        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(()->{
                isStop=true;
                },"t2").start();
    }
}

Result:

-----hello,
-----hello,
-----hello,
-----hello,
-----hello,
========isStop=true,Program End

_3.2 Elegant Stop Thread: Interrupt Example 2 - Atomic Classes

static AtomicBoolean atomicBoolean=new AtomicBoolean(false);

    public static void main(String[] args) {
        new Thread(()->{

            while (true){
                if (atomicBoolean.get()){
                    System.out.println("========atomicBoolean=true,Program End");
                    break;
                }
                System.out.println("-----hello,atomicBoolean");
            }

        },"t1").start();

        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(()->{
            atomicBoolean.set(true);
        },"t2").start();
    }

Result:

-----hello,atomicBoolean
-----hello,atomicBoolean
-----hello,atomicBoolean
-----hello,atomicBoolean
-----hello,atomicBoolean
========atomicBoolean=true,Program End

_3.3Thread's native API implementation

public static void main(String[] args) {
        Thread t1 = new Thread(() -> {

            while (true) {
            //isInterrupted self-checks if the interrupt flag bit is true
                if (Thread.currentThread().isInterrupted()) { 
                    System.out.println("========isInterrupted,Program End");
                    break;
                }
                System.out.println("-----hello,isInterrupted");
            }

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

        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(()->{
            t1.interrupt();
        },"t2").start();
    }

3.4 When the interrupt of the current thread is identified as true, it does not stop immediately!

When interrupt() is called on a thread:
(1) If a thread is active, the interrupt flag for that thread is set to true, and that's all.
Threads with an interrupt flag set will continue to function normally and will not be affected. Therefore, interrupt() does not really interrupt a thread, and the called thread needs to cooperate with itself.

(2) If the thread is blocked (e.g., sleep, wait, join, etc.), call the interrupt method of the current thread object in another thread,
The thread exits the blocked state immediately and throws an InterruptedException exception.

The 3.4.1 true interrupt does not immediately stop the program case (the called thread does not match):

The main thread notifies the t1 thread of interruption, but the t1 thread in this case is not actively responding to the terminal notification of the main thread. The t1 execution ends when the thread dies.

public static void main(String[] args) {

        //  When true is interrupted, the program is not stopped immediately because this thread does not work with isInterrupt()
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 300; i++) {
                System.out.println(("------i: " + i));
            }
            System.out.println("t1.interrupt 02 after call: " + Thread.currentThread().isInterrupted());
        }, "t1");
        t1.start();

        System.out.println("t1.interrupt()Before call t1 Thread interrupt flag default value: "+t1.isInterrupted());
        try { TimeUnit.MILLISECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
        //  Setting the interrupt flag bit to true does not really stop the thread
        t1.interrupt();
        // Active state, t1 thread still executing
        System.out.println("-----t1.interrupt()01 interrupt flag default value after call:  "+t1.isInterrupted());
        try { TimeUnit.MILLISECONDS.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); }
        //  Inactive state, t1 thread has ended execution
        System.out.println("------t1.interrupt()03 interrupt flag default value after call:  "+t1.isInterrupted());
    }

Result: Suggest self-observation

The interrupt flag was modified by 3.5 and the t1 thread responded positively, but the t1 program was still not stopped

public static void main(String[] args) {
        Thread t1 = new Thread(() -> {

            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println("========isInterrupted,Program End");
                    break;
                }
//t2 has set the flag bit to true, but after the t1 thread sleep is interrupted by true, the flag bit becomes false again, so it continues to run even if an exception is thrown
                try {
                    TimeUnit.MILLISECONDS.sleep(500);
                } catch (InterruptedException e) {
//  The interrupt flag bit of the thread is false and cannot be stopped. You need to call interrupt() again to set the flag bit to true!!
                    Thread.currentThread().interrupt();
                    e.printStackTrace();
                }

                System.out.println("-----hello,isInterrupted");
            }

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

        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(()->{
            t1.interrupt();
        },"t2").start();
    }

Conclusion:

Posted by SuperCam on Mon, 13 Sep 2021 20:06:10 -0700