Communication between threads of JUC

Posted by phpnewbieca on Wed, 29 Dec 2021 23:53:57 +0100

Thread communication

Supplement to the last multithreaded programming step (middle):

  1. Create a resource class, and create properties and operation methods in the resource class
  2. Operate in the resource class
    • judge
    • work
    • notice
  3. Create multiple threads and call the operation methods of the resource class

Implementation example of thread communication:

Two threads operate on an initial variable of 0, one thread + 1 and one thread - 1, so that the variable result does not change

Thread communication using Synchronized:

package com.JUC;

/**
 * Create resource class
 */
class Share{
    //Initial value
    private int number = 0;

    //Create method
    public synchronized void incr() throws InterruptedException {
        //Judgment work notice
        if(number != 0){
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName()+"::"+number);
        //Notify other threads
        this.notifyAll();
        //System.out.println(this.getClass());
    }
    public synchronized void decr() throws InterruptedException {
        if(number != 1){
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"::"+number);
        //Wake up other threads. this here refers to the object calling the method in the method
        this.notifyAll();
    }

}
public class ThreadSignaling {
    public static void main(String[] args) throws InterruptedException {
        Share share = new Share();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.incr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"AAA").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.decr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"BBB").start();
    }
}

volatile and synchronized keywords

Volatile: visible. When modifying a variable, if the variable is modified by volatile, all other threads will perceive the change of the variable.

If you do not use this keyword:

Visibility means that when multiple threads access the same variable, one thread modifies the value of the variable, and other threads can immediately see the modified value.

For a simple example, look at the following code:

//Code executed by thread 1
int i = 0;
i = 10;

//Code executed by thread 2
j = i;

If CPU1 executes thread 1, CPU2 executes thread 2. It can be seen from the above analysis that when thread 1 executes the sentence i =10, it will first load the initial value of i into the cache of CPU1, and then assign it to 10. Then, in the cache of CPU1, the value of i becomes 10, but it is not written to the main memory immediately.

At this time, thread 2 executes j = i. it will first go to the main memory to read the value of i and load it into the cache of CPU2. Note that the value of i in the memory is still 0, so the value of j will be 0 instead of 10

This is the visibility problem. After thread 1 modifies variable i, thread 2 does not immediately see the value modified by thread 1

The above explanation can actually correspond to the following fragments in the book:

Java supports multiple threads to access an object or its member variables at the same time, Because each thread can have a copy of this variable (although the memory allocated by objects and member variables is in shared memory, each executing thread can still have a copy. The purpose of this is to speed up the execution of the program, which is a remarkable feature of modern multi-core processors). Therefore, during the execution of the program, the variables seen by a thread are not necessarily the latest.

The keyword synchronized can be used to modify methods or synchronization blocks;

Function: ensure that multiple threads can only be in the method or synchronization block by one thread at the same time, which ensures the visibility and exclusivity of thread access to variables.

Any object has its corresponding monitor. When this object is called by the synchronization block or synchronization method, the following logic is required:

When any thread accesses an Object (the Object is protected by synchronized), it first needs to obtain the monitor of the Object. If the acquisition fails, the thread enters the synchronization queue and the thread state changes to BLOCKED. When the precursor accessing the Object (the thread that has obtained the lock) releases the lock, the release operation wakes up the thread BLOCKED in the synchronization queue to try to obtain the monitor again.

Use of Condition

Another comparison with synchronized:

Lock replaces the synchronized method and statement, and Condition replaces the Object monitor methods wait notify and notifyAll;

Use the Lock condition interface to buy tickets:

package com.JUC;

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

class shareDemo {
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    private int number = 0;

    public void inc() throws InterruptedException {
        lock.lock();

        try{
            while(number != 0){
                condition.await();
            }
            number++;
            System.out.println(Thread.currentThread().getName()+"::"+number);
            /**
             * Wake up multiple waiting threads
             */
            condition.signalAll();

        }finally {
            lock.unlock();
        }
    }
    public void sub(){
        lock.lock();

        try{
            while(number != 1){
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName()+"::"+number);
            /**
             * Wake up multiple waiting threads
             */
            condition.signalAll();

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

public class ConditionLocal {
    public static void main(String[] args) {
        shareDemo share = new shareDemo();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.inc();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        },"AAA").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                share.sub();
            }

        },"BBB").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.inc();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        },"CCC").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                share.sub();
            }

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

In Book 4.3 1-4.3. 3 corresponds to the example of thread communication in this article.

Pipeline I / O flow:

The communication mode between threads also includes pipeline input / output stream: different from the input and output of files, it is mainly used for data transmission between threads, and the transmission medium is memory;

Here are the contents of the book:

The pipeline input / output stream mainly includes the following four specific implementations: PipedOutputStream, PipedInputStream, PipedReader and PipedWriter. The first two are byte oriented and the last two are character oriented.

Implementation example:

For Piped streams, you must bind them first, that is, call the connect() method. If there is no input / output

Bound to the output stream, the access to the stream will throw an exception

package com.JUC;

import java.io.IOException;
import java.io.PipedReader;
import java.io.PipedWriter;

public class PipeInOut {
    public static void main(String[] args) throws IOException {
        PipedWriter out = new PipedWriter();
        PipedReader in = new PipedReader();
        //Connect the input stream to the output stream, otherwise an IO error will occur
        out.connect(in);
        //Create a print thread to receive input from Main
        Thread thread = new Thread(new Print(in),"PrintThread");
        //Start the thread and start receiving data
        thread.start();
        int receive = 0;
        try {
            //Receive the input data and assign a value
            while((receive = System.in.read()) != -1){
                out.write(receive);
            }
        }finally{
            out.close();
        }

    }

    static class Print implements Runnable {
        private  PipedReader in;
        public Print(PipedReader in) {
            this.in = in;
        }

        @Override
        public void run() {
            int receive = 0;
            try {
                while((receive = in.read()) != -1){
                    System.out.print((char) receive);
                }
            }catch(IOException ex){

            }
        }
    }
}

Thread. Use of join()

Definition in the book: it means that the current thread A waits for the thread to terminate before starting from thread Join() returns; It doesn't feel easy to understand;

Java 7 Concurrency Cookbook

Is the main thread waiting for the termination of the child thread. That is, if the t.join() method is encountered in the code block of the main thread, the main thread needs to wait (block) and wait for the child thread to die, To continue executing the code block after t.join().

example:

package com.JUC;

import java.util.concurrent.TimeUnit;

public class Join {
    public static void main(String[] args) throws InterruptedException {
        Thread previous = Thread.currentThread();
        for (int i = 0; i < 10; i++) {
            // Each thread has a reference to the previous thread. You need to wait for the previous thread to terminate before you can return from the wait
            Thread thread = new Thread(new Domino(previous), String.valueOf(i));
            thread.start();
            previous = thread;
        }
        TimeUnit.SECONDS.sleep(5);
        //Main thread
        System.out.println(Thread.currentThread().getName()+"--terminate.");
    }

    static class Domino implements Runnable {
        private Thread thread;
        public Domino(Thread thread) {
            this.thread = thread;
        }

        @Override
        public void run() {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //Child thread
            System.out.println(Thread.currentThread().getName()+"---Terminate.");
        }
    }
}

The premise of each thread termination is the termination of the precursor thread. Each thread waits for the termination of the precursor thread before returning from the join() method;

We check the source code of the join method and find that it is also modified with synchronized;

public final void join() throws InterruptedException {
    join(0);
}
public final synchronized void join(long millis)
    throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;

    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (millis == 0) {
        while (isAlive()) {
            wait(0);
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

About 4.3 For the use of 6threadlocal, please refer to the following articles: Click to enter

reference resources:

The art of JUC concurrent programming

JUC concurrent programming of the necessary technology of [shangsilicon Valley] large factories