Java thread in-depth learning

Keywords: Java Multithreading

Chapter 4: inter thread communication

4.1 waiting / notification mechanism

4.1.1 what is the waiting notification mechanism

    In single thread programming, the operation to be executed needs to meet certain conditions, which can be placed in the if statement block. In multi-threaded programming, the conditions of thread a may not be met, but this situation is only temporary. Later, other thread B may update the conditions to meet the conditions of thread A. at this time, thread a can be suspended until its conditions are met, and then thread a can be awakened. The pseudo code of this step:

        atomics{    // Atomic operation

                While (the condition is not true){

                        wait for

                }

                After the conditions for the current thread to be awakened are met, continue to perform the following operations

        }

4.1.2 implementation of waiting notification mechanism

    The wait() method in the Object class can make the thread executing the current code wait and suspend execution until it is notified or interrupted. Before executing the wait() method, the thread must obtain a lock. Note:

        ① The wait() method can only be called by a lock object in a synchronized code block.

        ② After calling the wait() method, the current thread immediately releases the lock.

    Pseudo code:

        // Obtain the internal lock of the object before calling the wait() method

        Synchronized (lock object){

                While (the condition is not true){

                        // Calling the wait() method to pause the thread through the lock object will release the lock object

                        Lock object. wait();

                }

                // If the thread condition is met, continue to execute downward

        }

    The notify () method of the Object class must also be called by the lock object in the synchronous code block. The IllegalMonitorStateException exception is thrown without calling the object wait()/notify(). If there are multiple waiting threads, the notify () method can only wake one of them. Note: calling notify () in the synchronous code block. Method does not release the lock object immediately. The lock object will not be released until the current synchronization code block is executed. Generally, the notify() method is placed at the end of the synchronization code block. Pseudo code:

        Synchronized (lock object){

                // Execute the code that modifies the protection condition

                // Wake up other threads

                Lock object. notify();

        }

public class Test{
    public static void main(String[] args){
        String lock = "abc";//Define a string as the lock object
        Thread t1 = new Thread(new Runnable(){
            public void run(){
                synchronized(lock){
                    System.out.println("Thread 1 started waiting" + System.currentTimeMillis()); 
                    try{
                        lock.wait();//When the thread waits, the lock object will be released, and the current thread will enter the blocked blocking state
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                    System.out.println("Thread 1 end wait" + System.currentTimeMillis()); 
                }
            }
        });

        Thread t2 = new Thread(new Runnable(){
            public void run(){
                synchronized(lock){
                    System.out.println("Thread 2 starts waking up" + System.currentTimeMillis()); 
                    try{
                        lock.notify();//Wake up a thread waiting on the lock object
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                    System.out.println("Thread 2 end wake" + System.currentTimeMillis()); 
                }
            }
        });
        
        t1.start();
        Thread.sleep(3000);//Make sure t1 enters the waiting state
        t2.start();
    }
}

4.1.3 interrupt() method will interrupt wait()

    When the thread is in the wait() waiting state, calling the interrupt() method of the thread object will interrupt the waiting state of the thread and generate an InterruptException.

public class Test{
    public static void main(String[] args){
        SubThread t = new SubThread;
        
        t.start();
        Thread.sleep(2000);
        t.interrupt();
    }
}

class SubThread extends Thread{
    private static final Object LOCK = new Object();//Define constants as lock objects
    
    public void run(){
        synchronized(LOCK){
            try{
                System.out.println("begin wait...");
                LOCK.wait();
                System.out,println("end wait");
            }catch(InterruptedException e){
                System.out.println("wait The waiting was interrupted");
            }
        }
    }
}

  4.1.4 difference between notify() and notifyAll()

    notify() can wake up only one thread at a time. If there are multiple waiting threads, it can wake up only one of them randomly; notifyAll() can wake up all waiting threads.

    Calling notify() once can only wake up one thread, while other waiting threads are still waiting. For waiting threads, the notification signal is missed. This phenomenon is also called signal loss.

4.1.5 use of wait (long) method

    wait(long) wait() with a long type parameter. If it is not awakened within the time specified by the parameter, it will wake up automatically after timeout.

4.1.6 premature notification

    After the thread waits for wait(), it can call notify() to wake up the thread. If it wakes up too early, calling notify() before waiting may disrupt the normal running logic of the program.

4.1.7 wait conditions changed

    When using the wait/notify mode, note that the wait conditions have changed, which may also cause logical confusion.

4.1.8 producer consumer model

    In Java, the module responsible for generating data is the producer, and the module responsible for using data is the consumer. Producers and consumers solve the problem of data balance, that is, they have data before they can use it. When there is no data, consumers need to wait.

        ① Production consumption: operating value

public class Test{
    public static void main(String[] args){
        ValueOP obj = new ValueOP();
        
        //Single producer, single consumer
        ProducerThread p = new ProducerThread(obj);
        ConsumerThread c = new ConsumerThread(obj);

        p.start();
        c.start();
    }
}


//Define thread classes to simulate producers
class ProducerThread extends Thread{
    private ValueOP obj;
    
    public ProducerThread(ValueOP obj){
        this.obj = obj;
    }

    public void run(){
        while(true){
            obj.setValue();
        }
    }
}

//Define thread classes to simulate consumers
class ConsumerThread extends Thread{
    private ValueOP obj;
    
    public ConsumerThread(ValueOP obj){
        this.obj = obj;
    }

    public void run(){
        while(true){
            obj.getValue();
        }
    }
}

//Define a class that operates on data
class ValueOP{
    private String value = "";
    
    //Define a method to modify the value of the value field. If it is not an empty string, it will not be modified
    public void setValue(){
        synchronized(this){
            //Wait if the string is not empty
            while(!value.equalsIgnoreCase("")){
                this.wait();
            }
            //If value is an empty string, set the value of the value field
            String value = System.currentTimeMillis() + "-" + System.nanoTime();
            System.out.println("set The set value is" + value);
            this.value = value;
            this.notify();
        }
    }
    
    //Define the method to read the field. If it is an empty string, it will not be read
    public void getValue(){
        synchronized(this){
            //If the string is empty, wait
            while(value.equalsIngoreCase("")){
                this.wait()
            }
            //If value is not an empty string, the value of the value field is read
            System.out.println("get The value of is" + this.value);
            this.value = "";
            this.notify();
        }
    }
}

         In the case of multiple producers and multiple consumers, if the notify() method is still used, the producer may wake up the producer or cancel

       If the consumer wakes up and is in a suspended state, the notifyAll() method should be used.  

         ② Production consumption: operation stack. It enables producers to store data in the List set, and consumers to get data from the List set and use it

          List simulation stack.

public class Test{
    public static void main(String[] args){
         MyStacj stack = new MyStack();
        
        //Multi producer, multi consumer
        ProducerThread p1 = new ProducerThread(stack);
        ProducerThread p2 = new ProducerThread(stack);
        ProducerThread p3 = new ProducerThread(stack);
        ConsumerThread c1 = new ConsumerThread(stack);
        ConsumerThread c2 = new ConsumerThread(stack);
        ConsumerThread c3 = new ConsumerThread(stack);

        p1.start();
        p2.start();
        p3.start();
        c1.start();
        c1.start();
        c1.start();
    }
}

//Define thread classes to simulate producers
class ProducerThread extends Thread{
    private MyStack stack;
    
    public ProducerThread(MyStack stack){
        this.stack = stack;
    }

    public void run(){
        while(true){
            stack.push();
        }
    }
}

//Define thread classes to simulate consumers
class ConsumerThread extends Thread{
    private MyStack stack;
    
    public ConsumerThread(MyStack stack){
        this.stack = stack;
    }

    public void run(){
        while(true){
            stack.pop();
        }
    }
}


class MyStack{
    private List list = new ArrayList(); //Define set simulation stack
    private static final int MAX = 1; //Maximum capacity of the collection
    
    //Define method simulation stack
    public void push(){
        synchronized(this){
            //The data in the stack is full, unable to continue to enter the stack, wait
            while(list.size >= MAX){
                System.out.println(Thread.currentThread.getName() + "begin wait...");
                this.wait();
            }
            String data = "data--" + Math.random();
            System.out.println(Thread.currentThread.getName() + "Added data:" + data);
            list.add(data);
            this.notifyAll();
        }
    }

    //Define method simulation stack
    public void push(){
        synchronized(this){
            //The data in the stack is empty, unable to obtain data, wait
            while(list.size == MAX){
                System.out.println(Thread.currentThread.getName() + "begin wait...");
                this.wait();
            }
            System.out.println(Thread.currentThread.getName() + "Stack data:" + list.remove(0));
            this.notifyAll();
        }
    }
}

4.2 communication between threads through pipeline

    The PipeStream pipeline flow in the java.io package is used to transfer data between threads. One thread sends data to the output pipeline flow, and another thread reads data from the input pipeline.

    Related classes include:

        ① PipedInputStream and PipedOutputStream: byte stream.

        ② PipedReader and PipedWriter: character stream.

public class Test{
    public static void main(String[] args) throws IOException{
        //Define pipe byte stream
        PipedInputStream in = new PipedInputStream;
        PipedOutputStream out = new PipedOutputStream;
        //Establishes a connection between the input pipe flow and the output pipe flow
        in.connect(out);
        
        //Create a thread to write data to the pipeline flow
        new Thread(new Runnable(){
            public void run(){
                writeData(out);
            }
        }).start();
        
        //Create a thread to read data from the pipe stream
        new Thread(new Runnable(){
            public void run(){
                readData(in);
            }
        }).start();
    }

    //Define a method to write data to the pipeline flow
    public static void writeData(PipedOutputStream out){
        //Write the number between 0 and 100 into the pipeline respectively
        try{
            for(int i = 0;i < 100;i++){
                String data = "" + i;
                out.write(data.getBytes()); //Writes a byte array to the output pipeline stream
            }
        }catch(IOException e){
            e.printStackTrace();
        }finally{
            out.close();
        }
    }

    //Define a method to read data from a pipe stream
    public static void readData(PipedInputStream in){
        //Read the numbers between 0 and 100 into the pipeline respectively
        try{
            byte[] bytes = new byte[1024];
            int len = in.read(bytes);//Returns the number of bytes read. If no data is read, - 1 is returned
            while(len != -1){
                System.out.println(new String(bytes,0,len));
                len = in.read(bytes);//After reading, continue reading data from the pipe
            }
        }catch(IOException e){
            e.printStackTrace();
        }finally{
            in.close();
        }
    }
}

4.3 use of ThreadLocal

    In addition to controlling the access of resources, you can also increase resources to ensure thread safety. ThreadLocal is mainly used to bind its own value for each thread.

//Basic use of ThreadLocal

public class Test{
    //Define ThreadLocal object
    static ThreadLocal threadLocal = new ThreadLocal();
    
    //Define thread class
    static class SubThread extends Thread{
        public void run(){
            for(int i = 0;i < 20;i++){
                //Sets the value associated with the thread
                threadLocal.set(Thread.currentThread().getName() + "-" + i);
                //Call the get method to read the associated value
                System.out.println(Thread.currentThread().getName() + " value = " + threadLocal.get());
            }
        }
    }

    public static void main(String[] args){
        SunThread t1 = new SubThread();
        SunThread t2 = new SubThread();
        t1.start();
        t2.start();
    }
}

    Save the variable value of the thread for each thread. i in the example is a local variable, which is unique to each thread.

    ThreadLocal can specify an initial value.

//Example of setting initial value

public class SubThreadLocal extends ThreadLocal<Date>{
    //Override the initialValue method to set the initial value
    protected Date initialValue(){
        return new Date(); //Set the current date as the initial value
    }
}

//Then directly use SunThreadLocal to create ThreadLocal objects.
//static ThreadLocal threadLocal = new SubThreadLocal();

PS: sort out according to the power node course. If there is infringement, contact to delete it.

Posted by Pavel_Nedved on Wed, 22 Sep 2021 08:36:43 -0700