Day25 communication between Java threads (unfinished)

Multithreading (Continued)

Lock lock

Although the synchronization code block and synchronization method solve the thread safety problem, they can't express where the Lock is added and where the Lock is released, so in jdk1 After 5, a new Lock object is provided: Lock

Lock is an interface with two methods:

void lock(): lock; void lock(): release the lock

When creating a Lock object, you need to use its implementation class: ReentrantLock

Take cinema ticket sales as an example: that is, the second way to solve the thread safety problem

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

public class RunableDemo2 implements Runnable{
    private int ticket=100;
    private Lock lock=new ReentrantLock();
    @Override
    public void run() {
        while(true){
            lock.lock();
            if(ticket>0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"In the process of sale"+(ticket--)+"Ticket");
            }
            lock.unlock();
        }
    }
}

Test class:

public class TicketDemo2 {
    public static void main(String[] args) {
        RunableDemo2 runableDemo2 = new RunableDemo2();
        Thread t1 = new Thread(runableDemo2);
        Thread t2 = new Thread(runableDemo2);
        Thread t3 = new Thread(runableDemo2);
        t1.setName("Big black");
        t2.setName("Two black");
        t3.setName("Three black");

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

The output result is the same as the previous result

deadlock

It refers to the phenomenon that two or more threads wait for each other due to competing for resources during execution

This is also one of the disadvantages of synchronization: low efficiency; If synchronous nesting occurs, it is easy to cause deadlock.

Code example:

Create two classes of lock objects:

public class Dlock {
    public static final Object lock1 = new Object();
    public static final Object lock2 = new Object();
}

Entity class:

public class Ddemo1 extends Thread{
   private boolean flag;
   public Ddemo1(boolean flag){
       this.flag=flag;
   }
    @Override
    public void run() {
        if(flag){
            synchronized (Dlock.lock1){
                System.out.println("if lock1");
                synchronized (Dlock.lock2){
                    System.out.println("if lock2");
                }
            }
        }else{
            synchronized (Dlock.lock2){
                System.out.println("else lock2");
                synchronized (Dlock.lock1){
                    System.out.println("else lock1");
                }
            }
        }
    }
}

Test class:

public class Ddemotest {
    public static void main(String[] args) {
        Ddemo1 ddemo1 = new Ddemo1(true);
        Ddemo1 ddemo2 = new Ddemo1(false);

        ddemo1.start();
        ddemo2.start();
    }
}

Results: there will be four results, two of which are normal and two of which are deadlocked

Normal result: the program ends directly after running

Abnormal result: deadlock occurs: two threads enter at the same time. After the first synchronization operation, because another lock object required by each other is being called, the two threads are waiting for each other to release the lock object. Therefore, a deadlock occurs and will wait forever. After running, the program will run all the time and will not stop.

 

Inter thread communication

There are different kinds of threads for the operation of the same resource

For example: students need to buy tickets to go to the cinema. When the tickets are sold out, the cinema needs to produce movie tickets again.

According to the normal analysis: two entity classes need to be created to implement the Runable interface, one represents the producer, one represents the consumer, and then create a student object class, which means that the two entity classes operate on the object, and create student objects in both entity classes to operate on the student object.

But in this way, the output result will always be the default values null and 0, because the objects operated in the two entity classes are not the same object.

Improvement: create an object in the outside world and pass it into two entity classes for operation in the form of parameters. Add a loop and another student object to make the results clearer:

Code implementation:

Entity class: get class

public class Stget implements Runnable{
    private Student s;
    public Stget(Student s){
        this.s=s;
    }
    @Override
    public void run() {
        while(true){
            System.out.println(s.name+"---"+s.age);
        }

    }
}

set class:

public class Stset implements Runnable{
    private Student s;
    private int i=0;
    public Stset(Student s){
        this.s=s;
    }
    @Override
    public void run() {
        while(true){
            if (i%2==0){
                s.name="Big black";
                s.age=18;
            } else{
                s.name="Big white";
                s.age=20;
            }
            i++;
        }


    }
}

Students:

public class Student {
    public String name;
    public int age;
}

Test class:

public class Stdemo1 {
    public static void main(String[] args) {
//        Create a student object in the outside world and pass it into the thread class in the form of parameters
        Student s = new Student();
        Stget stget = new Stget(s);
        Stset stset = new Stset(s);

        Thread t1 = new Thread(stget);
        Thread t2 = new Thread(stset);

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

The output results show that:

The names and ages of two student objects will not match and repeat

terms of settlement:

Meet the three conditions of thread safety, so you need to add the synchronized keyword Lock or Lock lock

Code perfection:

Add synchronized locking:

get class:

public class Stget implements Runnable{
    private Student s;
    public Stget(Student s){
        this.s=s;
    }
    @Override
    public void run() {
        while(true){
            synchronized (s){
                System.out.println(s.name+"---"+s.age);
            }

        }

    }
}

set class:

public class Stset implements Runnable {
    private Student s;
    private int i = 0;

    public Stset(Student s) {
        this.s = s;
    }

    @Override
    public void run() {

        while (true) {
            synchronized (s) {
                if (i % 2 == 0) {
                    s.name = "Big black";
                    s.age = 18;
                } else {
                    s.name = "Big white";
                    s.age = 20;
                }
                i++;
            }
        }

    }
}

There will be no mismatch between name and age in the output result, but a new problem arises: a data operation has been performed multiple times

Cinema tickets are sold only when there are tickets in the cinema. If there are no tickets, they should be sold. So should consumers. After selling a ticket, they continue to buy tickets. If there are no tickets in the cinema, they should wait until the cinema produces tickets.

This problem of production consumers is solved by using the waiting wake-up mechanism provided by Java

Wait for wake-up mechanism

How to add a wake-up waiting mechanism? The method to be called is in the Object class. There are three methods in the Object class that we need to learn.

void notify() wakes up a single thread waiting for the object monitor.

void notifyAll() wakes up all threads waiting for the object monitor.

void wait() causes the current thread to wait until another thread calls the notify() method or notifyAll() method of the object.

Why not define these three methods in the Thread method:

If these methods want to be called, they must be called through the lock Object, because if the chain objects are different, there is no need to wait for wake-up and directly execute the logical code. Now, the lock Object of the synchronization code block is an arbitrary Object, and the type cannot be determined, so these methods are defined in the Object class, because all classes in Java have a common parent class Object

Code implementation:

set class:

public class Stset implements Runnable {
    private Student s;
    private int i = 0;

    public Stset(Student s) {
        this.s = s;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (s) {
                if(s.flag){//If there is a value, wait for the consumer to consume
                    try {
                        s.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                if (i % 2 == 0) {
                    s.name = "Big black";
                    s.age = 18;
                } else {
                    s.name = "Big white";
                    s.age = 20;
                }
                i++;
                s.notify();
                s.flag=true;
            }
        }

    }
}

get class:

public class Stget implements Runnable{
    private Student s;
    public Stget(Student s){
        this.s=s;
    }
    @Override
    public void run() {
        while(true){
            synchronized (s){
                if(!s.flag){//If there is no value, wait for the output value of the producer
                    try {
                        s.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(s.name+"---"+s.age);
                s.notify();//Wake up producers after consumption
                s.flag=false;//Change value to no value
            }

        }

    }
}

Output result: in this way, the cinema can produce one ticket and consumers can buy one ticket

 

Thread group

In Java, ThreadGroup is used to represent thread group. It can classify and manage a batch of threads. Java allows programs to control thread group directly.

Thread pool

The cost of starting a new thread is relatively high because it involves interacting with the operating system. Using thread pool can improve performance, especially when a large number of threads with short lifetime are to be created in the program, thread pool should be considered.

After the end of each thread code in the thread pool, it will not die, but return to the thread pool again to become idle and wait for the next object to use. Before JDK5, we must manually implement our own thread pool. Starting from JDK5, Java built-in supports thread pool

Implement thread pool

The third way to solve the problem of ticket buying thread safety in cinemas:

Implement Callable interface

Implement multithreading in the form of anonymous internal classes

Keywords: Java Back-end

Added by Dutch MaFia-boy on Mon, 21 Feb 2022 15:59:47 +0200