Java multithreading detailed explanation, an article to understand multithreading.

1. Basic concepts

  • program

    A program is a set of instructions written in a certain language to complete a specific task. That is, a piece of static code (not yet running), static object.

  • process

    Process is an execution process of a program, that is, the program runs, loads into memory, and occupies cpu resources. This is a dynamic process: it has its own process of emergence, existence and extinction, which is also the life cycle of the process.

    Process is the unit of system resource allocation. The system will allocate different memory areas for each process at run time.

  • thread

    The process can be further refined into threads, which is the execution path within a program.

    If a process executes multiple threads in parallel at the same time, the process supports multithreading.

    Thread is the unit of cpu scheduling and execution. Each thread has an independent running stack and program counter (pc), and the overhead of thread switching is small.

    Multiple threads in a process share the same memory unit / memory address space - "they allocate objects from the same heap and can access the same variables and objects. This makes the communication between multipliers easier and funny. However, the system resources shared by sogg threads may bring security risks (the hidden danger is which thread operates the data, maybe a thread is operating the data, and a thread also operates the data v).

    • Understand with JVM memory structure (just understand)

      [the external chain picture transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-wjrafazw-1619229368626)( https://i.vgy.me/karuEk.png )]

      The class file is loaded into the memory space through the class loader.

      Each thread in the memory area will have virtual machine stack and program counter.

      Each process will have a method area and heap, and multiple threads share the method area and heap under the same process.

  • Understanding of CPU single core and multi-core

    Single core CPU is a fake multithreading, because it can only execute the task of one thread in a time unit. When multiple threads need to be run by the CPU in the same time period, the CPU can only alternately execute one of the multiple threads, but it can't feel it because its execution speed is very fast.

    Multi core CPU can better play the efficiency of multithreading.

    For Java applications, Java Exe, there are at least three threads: main() main thread, gc() garbage collection thread and exception handling thread. If an exception occurs, it will affect the main thread.

  • Classification of Java threads: user threads and daemon threads

    • Java's gc() garbage collection thread is a daemon thread
    • Daemon threads are used to serve user threads, and thread. is invoked before the start() method. Setdaemon (true) can turn a user thread into a daemon thread.
  • Parallelism and concurrency

    • Parallel: multiple CPUs execute multiple tasks at the same time. For example, many people do different things.
    • Concurrency: one cpu (using time slice) executes multiple tasks at the same time. For example, few or many people do the same thing.
  • Advantages of multithreading

    1. Improve application response. Heap graphical interface is more meaningful and can enhance the user experience.
    2. Improve the utilization of CPU in computer system.
    3. Improve program structure. The long and complex process is divided into multiple threads and run independently, which is conducive to understanding and modification.
  • When do I need multithreading

    • The program needs to perform two or more tasks at the same time.
    • When the program needs to realize some tasks that need to wait, such as user input, file reading and writing operation, network operation, search, etc.
    • When you need some programs running in the background.

2. Thread creation and startup

2.1. Principle of multithreading

  • The JVM of Java language allows programs to run multiple threads, which can be accessed through Java Lang. thread class.
  • Characteristics of Thread class
    • Each Thread completes the operation through the run() method of a specific Thread object. The body of the run() method is often called the Thread body.
    • Start the Thread through the start() method of the Thread method instead of calling run().

2.2. The creation of multithreading, method 1: inherit from the Thread class

  1. Create a subclass that inherits from the Thread class.
  2. Override the run() method of the Thread class.
  3. An object that creates a subclass of the Thread class.
  4. Start this thread by calling start().

**Code implementation: * * multiple threads execute the same piece of code

package com.broky.multiThread;

/**
 * @author 13roky
 * @date 2021-04-19 21:22
 */
public class ThreadTest extends Thread{
    @Override
    //Thread body, the code in the run() method will be run when the thread is started
    public void run() {
        //Output even numbers within 100
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0){
                System.out.println(Thread.currentThread().getName()+":\t"+i);
            }
        }
    }

    public static void main(String[] args) {
        //Create a subclass object of Thread class
        ThreadTest t1 = new ThreadTest();
        //Start a thread by calling start() from this object
        t1.start();
        //Note: a thread that has been started once cannot be started again
        //Create another thread
        ThreadTest t2 = new ThreadTest();
        t2.start();

        //Another call method that does not give an object a name
        new ThreadTest().start();

        System.out.println("Main thread");
    }
}

Running diagram of multithreaded code

[the external chain picture transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-pgun728e-1619229368627)( https://i.vgy.me/at2IMI.png )]

Multithreading executes multiple pieces of code

package com.broky.multiThread.exer;

/**
 * @author 13roky
 * @date 2021-04-19 22:43
 */
public class ThreadExerDemo01 {
    public static void main(String[] args) {
        new Thread01().start();
        new Thread02().start();
    }
}

class Thread01 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) System.out.println(Thread.currentThread().getName() + ":\t" + i);
        }
    }
}

class Thread02 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 != 0) System.out.println(Thread.currentThread().getName() + ":\t" + i);
        }
    }
}

2.3. Method 1: create an anonymous subclass of Thread (also belonging to method 1)

package com.broky.multiThread;

/**
 * @author 13roky
 * @date 2021-04-19 22:53
 */
public class AnonymousSubClass {
    public static void main(String[] args) {

        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    if (i % 2 == 0) System.out.println(Thread.currentThread().getName() + ":\t" + i);
                }
            }
        }.start();

    }
}

2.4. Multithreading is created in two ways: the Runnable interface is implemented

  1. Create a class that implements the Runnable interface.
  2. The implementation class implements the abstract method in the Runnable interface: run().
  3. Create an object that implements the class.
  4. Pass this object as a parameter to the constructor of Thread class to create the object of Thread class.
  5. The start() method is called through the object of the Thread class.
package com.broky.multiThread;

/**
 * @author 13roky
 * @date 2021-04-20 23:16
 */
public class RunnableThread {
    public static void main(String[] args) {
        //Create an object that implements the class
        RunnableThread01 runnableThread01 = new RunnableThread01();
        //Create an object of the Thread class and pass the object implementing the class into the constructor as a parameter
        Thread t1 = new Thread(runnableThread01);
        //Use the object of Thread class to call the start() method of Thread class: ① start the Thread; ② run() in Thread calls run() in Runnable
        t1.start();

        //When creating a Thread, you only need a new Thread class instead of a new implementation class
        Thread t2 = new Thread(runnableThread01);
        t2.start();
    }
}

//RunnableThread01 implements the run() abstract method of the Runnable interface
class RunnableThread01 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) System.out.println(Thread.currentThread().getName() + ":\t" + i);
        }
    }
}

2.4.1. Compare the two ways of creating threads

  • Only single process is allowed in Java. For ticketsales class, it is likely that this class has a parent class, so it can not inherit Thread class to complete multithreading. However, a class can implement multiple interfaces, so the implementation method has no limitation of single inheritance of class. It is more practical to complete multithreading by implementing Runnable interface.
  • The way to implement the Runnable interface naturally has the characteristics of sharing data (without static variables). Because of the implementation method of inheriting Thread, it is necessary to create multiple subclass objects for multithreading. If there is variable a in the subclass instead of static constraint variable, each subclass object will have its own independent variable A. only after static constraint a, the subclass objects will share variable a. To implement the Runnable interface, you only need to create an implementation class object. You need to pass this object into the Thread class and create multiple Thread class objects to complete multithreading, and these multiple Thread class objects are actually calling an implementation class object. The implementation method is more suitable to deal with the situation that multiple threads share data.
  • Connection: the Runnable interface is also implemented in the Thread class
  • In the same way, both methods need to rewrite the run() method, and the execution logic of the thread is in the run() method

2.5. Multithreading is created in three ways: implement the Callable interface

Callable is more powerful than Runnable

  1. Compared with the run() method, it can have a return value
  2. Method can throw an exception
  3. Return values that support generics
  4. You need to use the FutureTask class to get the returned results, for example
package com.broky.multiThread;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * The third way to create a thread is to implement the Callable interface--- New features of JDK5
 * How to understand that Callable is more powerful than Runnable?
 * 1.call()Can have return value
 * 2.call()Exceptions can be thrown and caught by external operations
 * @author 13roky
 * @date 2021-04-22 21:04
 */

//1. Create an implementation class that implements Callable
class NumThread implements Callable<Integer>{
    //2. Implement the call method and declare the operation to be performed by this thread in call()
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i < 100; i++) {
            if(i%2==0){
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}

public class ThreadNew {
    public static void main(String[] args) {
        //3. Create an object of the Callable interface implementation class
        NumThread numThread = new NumThread();
        //4. Pass the object of this Callable interface implementation class as a parameter to the FutureTask constructor to create a FutureTask object
        FutureTask<Integer> futureTask = new FutureTask(numThread);
        //5. Pass the FutureTask object as a parameter to the constructor of the Thread class, create the Thread object, and call start()
        new Thread(futureTask).start();

        try {
            //6. Get the return value of Call method in Callable
            Integer sum = futureTask.get();
            System.out.println("The sum is"+sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }
}

2.6. Multithreading creation, mode 4: thread pool

Background:

Resources that are often created and destroyed and used heavily, such as threads in concurrency, have a great impact on performance.

Idea:

Create many threads in advance, put them into the thread pool, get them directly when using them, and put them back into the pool after using them. It can avoid frequent creation, destruction and reuse. Similar to public transport in life.

advantage:

Improved response speed (reduced time to create new threads)

Reduce resource consumption (reuse threads in the thread pool and do not need to be created every time)

Easy thread management

package com.broky.multiThread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * Four ways to create threads: using thread pool
 * <p>
 * Interview question: how many ways to create multithreading
 *
 * @author 13roky
 * @date 2021-04-22 21:49
 */

class NumberThread implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":\t" + i);
            }
        }
    }
}

public class ThreadPool {
    public static void main(String[] args) {

        //1. Provide a thread pool with a specified number of threads
        ExecutorService service = Executors.newFixedThreadPool(10);
        ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
        //Set properties of thread pool
        //        System.out.println(service.getClass());
        //        service1.setCorePoolSize(15);
        //        service1.setKeepAliveTime();

        //2. Execute the operation of the specified thread. You need to provide an object that implements the Runnable interface or the Callable interface implementation class.
        service.execute(new NumberThread()); //Suitable for Runnable
        //        service.submit();  Suitable for Callable
        //Close thread pool
        service.shutdown();
    }
}

3. Common methods of thread class

  • start(): start the current thread and call the run() method of the current thread
  • run(): you usually need to override this method in the Thread class and declare the operation to be performed by the created Thread in this method
  • currentThread(): a static method that returns the thread of current code execution
  • getName(): get the name of the current thread
  • setName(): sets the name of the current thread
  • yield(): release the execution authority of the current CPU
  • join(): calling join() of thread b in thread a, when thread a enters a blocking state, the thread a ends the blocking state after the thread b is fully executed.
  • stop(): obsolete When this method is executed, the current thread is forced to end
  • Sleep (long duration): lets the thread sleep for the specified number of milliseconds. Within the specified time, the thread is in a blocked state
  • isAlive(): judge whether the current thread is alive

4. Thread scheduling

4.1. cpu scheduling strategy

  • **Time slice: * * the scheduling strategy of CPU under normal conditions. That is, the time allocated by the CPU to each program, and each thread is allocated a time period, called its time slice, that is, the time allowed for the process to run, so that each program is carried out at the same time on the surface. If the process is still running at the end of the time slice, the CPU will be stripped and allocated to another process. If the process blocks or ends before the end of the time slice, the CPU switches immediately. CPU resources will not be wasted. Macroscopically: we can open multiple applications at the same time, and each program runs side by side at the same time. But at the micro level: because there is only one CPU, it can only process part of the program requirements at a time. One way to deal with fairness is to introduce time slices and execute each program in turn.

  • **Preemptive: * * high priority threads preempt cpu.

4.2. Java scheduling algorithm:

  • Threads with the same priority form a first in first out queue (first in first out service) and use the time slice strategy.
  • The heap has high priority, and the preemptive strategy of priority scheduling is used.

Priority level of thread (10 gears in total)

  • MAX_PRIORITY: 10
  • MIN_PRIORITY: 1
  • NORM_PRIORITY: 5 (default priority)

Gets and sets the priority of the current thread

  • getPriority(); obtain
  • setPriority(int p); set up

Note: the high priority thread should seize the execution right of the low priority thread cpu. But only in terms of probability, high priority threads are executed with high probability. This does not mean that only after the execution of the high priority thread is completed, the low priority thread will execute.

5. Thread life cycle

  • Thread. Is used in JDk The state class defines several states of a thread

To implement multithreading, you must create a new Thread object in the main Thread. The Java language uses the objects of Thread class and its subclasses to represent threads. In a complete life cycle, it usually experiences the following five states:

  1. New: when the object of a Thread class or its subclass is declared and created, the new Thread object is in the new state.
  2. Ready: after the thread in the new state is started (), it will enter the thread queue and wait for the CPU time slice. At this time, it has met the running conditions, but it has not been allocated CPU resources.
  3. Run: when the ready thread is scheduled and obtains CPU resources, it enters the running state. The run() method defines the operation and function of the thread.
  4. Blocking: in a special case, when it is considered to suspend or perform input and output operations, give up the CPU and temporarily suspend its own execution to enter the blocking state.
  5. Death: the thread has completed all its work, or the thread is forcibly terminated in advance or abnormal inversion occurs, resulting in the end.

[external chain picture transfer failed. The source station may have anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-tfedo5ke-1619229368628)( https://i.vgy.me/qiV0by.png )]

6. Thread synchronization

6.1. Security analysis of multithreading

  • Thread safety
    • Uncertainty of multi thread execution and instability of hard execution results
    • The sharing of ledger by multiple threads will cause incomplete operation and destroy data
    • There may be security issues when multiple threads access shared data
  • Thread security Demo: there are duplicate tickets and wrong tickets in the ticket selling process (the following multi window ticket selling demo has multi thread security problems)
package com.broky.multiThread.safeThread;

/**
 * @author 13roky
 * @date 2021-04-21 20:39
 */
public class SafeTicketsWindow {
    public static void main(String[] args) {
        WindowThread ticketsThread02 = new WindowThread();
        Thread t1 = new Thread(ticketsThread02);
        Thread t2 = new Thread(ticketsThread02);
        Thread t3 = new Thread(ticketsThread02);

        t1.setName("Window 1");
        t2.setName("Window 2");
        t3.setName("Window 3");

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

class WindowThread implements Runnable {
    private int tiketsNum = 100;

    public void run() {
        while (true) {
            if (tiketsNum > 0) {
                try {
                    //Manually block the thread to increase the probability of wrong ticket
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + ":\t Ticket number:" + tiketsNum);
                /*try {
                    //Manually let the thread enter the block to increase the probability of duplicate tickets
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }*/
                tiketsNum--;
            } else {
                break;
            }
        }
    }
}

Wrong ticket analysis:

When the number of tickets is 1, one of the three threads is blocked and does not execute the operation of ticket number - 1. This is because other threads will judge through the if statement. In this way, one more ticket will be sold and the wrong ticket will appear.

In the extreme case, when the number of tickets is 1, the three threads judge to pass at the same time, enter the blocking, and then perform more ticket selling operations on both sides.

[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (IMG mkhmejhv-1619229368628)( https://i.vgy.me/WYm0AK.png )]

Duplicate ticket analysis:

If t1 is blocked between the operation of outputting ticket number 22 and ticket number - 1, t1 sells ticket number 22 at this time, but the total number of votes does not decrease. When t1 is blocked, if t2 runs to output the ticket number, t2 will also output the same ticket number 22 as t1

It can be seen from the above two situations that the thread safety problem is caused by multiple threads executing the code and other threads participating in the execution of the code.

6.2. Solution of multithreading security problem

Principle:

When a thread is operating on shared data, other threads cannot participate. Other threads can only operate when this thread has finished sharing data. Even if the thread is blocked when it operates on shared data, this situation cannot be changed.

In Java, we solve the thread safety problem through synchronization mechanism.

[external chain picture transfer failed. The source station may have anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-mtb4qkyr-1619229368629)( https://i.vgy.me/3Lo8QW.png )]

6.2.1. Solution to multithreading security problem 1: synchronize code blocks

Synchronized {code to be synchronized}

explain:

  1. The code that operates shared data (variables operated by multiple threads) is the code that needs to be synchronized. You can't include more code (it's inefficient. If you package it in front of while, it will become a single thread), and you can't include less code
  2. Shared data: variables operated by multiple threads.
  3. Synchronous monitor: commonly known as lock. The object of any class can act as a lock. But all threads must share a lock and an object.

Lock selection:

  1. Create and share objects, such as the Object object in the demo below.

  2. Use this to represent the object of the current class

    The lock in the method of inheriting thread cannot be replaced by this, because when inheriting thread to realize multithreading, multiple subclass objects will be created to represent multiple threads. At this time, this refers to multiple objects of the current class, which are not unique and cannot be used as locks.

    In the way of implementing the Runnable interface, this can be used as a lock, because this way only needs to create an object of the implementation class and pass the object of the implementation class to multiple Thread class objects as multiple threads. This is the object of the implementation class, which is the only object shared by all threads.

  3. Use class as lock. Take the following demo as an example. The lock can be written as windowthread Class, from which we can conclude that a class is also an object

Advantages: synchronous mode solves the problem of thread safety

Disadvantages: when operating synchronous code, only one thread can participate and other threads wait. It is equivalent to a single threaded process with low efficiency.

Demo

package com.broky.multiThread.safeThread;

/**
 * @author 13roky
 * @date 2021-04-21 20:39
 */
public class SafeTicketsWindow {
    public static void main(String[] args) {
        WindowThread ticketsThread02 = new WindowThread();
        Thread t1 = new Thread(ticketsThread02);
        Thread t2 = new Thread(ticketsThread02);
        Thread t3 = new Thread(ticketsThread02);

        t1.setName("Window 1");
        t2.setName("Window 2");
        t3.setName("Window 3");

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

class WindowThread implements Runnable {
    private int tiketsNum = 100;
    
    //Because Runnable implements multithreading and all threads share the Object of one implementation class, all three threads share the Object of this Object class in the implementation class.
    Object obj = new Object();
    //If you inherit the Thread class to implement multithreading, you need to use static Object obj = new Object();
    
    public void run() {
        
        //Object obj = new Object();
        //If the Object object is created in the run() method, each thread will generate its own Object class Object, which is not the shared Object of the three threads, so it is not locked.
        
        while (true) {
            synchronized (obj) {
                if (tiketsNum > 0) {
                    try {
                        //Manually block the thread to increase the probability of security
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":\t Ticket number:" + tiketsNum + "\t Remaining votes:" + --tiketsNum);
                } else {
                    break;
                }
            }
        }
    }
}

6.3.2. The second solution to multithreading safety problem: synchronization method

Put the code to be synchronized into a method and declare the method as synchronized. You can then invoke the synchronization method in the run() method.

main points:

  1. The synchronization method still involves the synchronization monitor, but we don't need to display the declaration.
  2. Non static synchronization method. The synchronization monitor is this.
  3. Static synchronization method. The synchronization monitor is the current class itself.

Demo

package com.broky.multiThread.safeThread;

/**
 * @author 13roky
 * @date 2021-04-21 22:39
 */
public class Window02 {
    public static void main(String[] args) {
        Window02Thread ticketsThread02 = new Window02Thread();
        Thread t1 = new Thread(ticketsThread02);
        Thread t2 = new Thread(ticketsThread02);
        Thread t3 = new Thread(ticketsThread02);

        t1.setName("Window 1");
        t2.setName("Window 2");
        t3.setName("Window 3");

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

class Window02Thread implements Runnable {
    private int tiketsNum = 100;

    @Override
    public void run() {
        while (tiketsNum > 0) {
            show();
        }
    }

    private synchronized void show() { //Synchronization monitor: this
        if (tiketsNum > 0) {
            try {
                //Manually block the thread to increase the probability of security
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":\t Ticket number:" + tiketsNum + "\t Remaining votes:" + --tiketsNum);
        }
    }
}
package com.broky.multiThread.safeThread;

/**
 * @author 13roky
 * @date 2021-04-21 22:59
 */
public class Window03 {
    public static void main(String[] args) {
        Window03Thread t1 = new Window03Thread();
        Window03Thread t2 = new Window03Thread();
        Window03Thread t3 = new Window03Thread();
        t1.setName("Window 1");
        t2.setName("Window 2");
        t3.setName("Window 3");
        t1.setPriority(Thread.MIN_PRIORITY);
        t3.setPriority(Thread.MAX_PRIORITY);
        t1.start();
        t2.start();
        t3.start();
    }
}

class Window03Thread extends Thread {
    public static int tiketsNum = 100;

    @Override
    public void run() {
        while (tiketsNum > 0) {
            show();
        }
    }

    public static synchronized void show() {//Synchronization monitor: winddoe03thread Class without static, the synchronization monitor is T1, T2 and T3, so there is an error
        if (tiketsNum > 0) {
            try {
                //Manually block the thread to increase the probability of security
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":\t Ticket number:" + tiketsNum + "\t Remaining votes:" + --tiketsNum);
        }
    }
}

Using synchronization to solve the thread safety problem of lazy mode

package com.broky.multiThread.safeThread;

/**
 * @author 13roky
 * @date 2021-04-22 7:24
 */
public class BankTest {
}

class Bank {
    private Bank() {
    }

    private static Bank instance = null;

    public static Bank getInstance() {
        //Method 1: poor efficiency. Each waiting thread will enter the synchronous code block
        //        synchronized (Bank.class) {
        //            if (instance == null) {
        //                instance = new Bank();
        //            }
        //        }

        //Method 2: in the outer layer of the synchronous code block, after judging once, all threads are prevented from entering the synchronous code block.
        if (instance == null) {
            synchronized (Bank.class) {
                if (instance == null) {
                    instance = new Bank();
                }
            }
        }
        return instance;
    }
}

6.2.3. Solution to multithreading security problem 2: Lock - jdk5 0 new features

JDK5. After 0, you can instantiate the ReentrantLock object, invoke the lock() method of the ReentrantLock object before the synchronization statement is needed, implement the synchronization lock, and call the unlock() method to finish the synchronization lock at the end of the synchronization statement.

Similarities and differences between synchronized and lock: (interview question)

1. Lcok Is an explicit lock (the lock needs to be opened and closed manually), synchronized Is an implicit lock, except that the scope is automatically released.
2. Lock Only code block locks, synchronized There are code block locks and method locks.
3. use Lcok Lock, JVM It will take less time to schedule threads for better performance. And it has better expansibility (providing more subclasses)

Suggested sequence: Lock - "synchronize code block (it has entered the method body and allocated corresponding resources) -" synchronize method (outside the method body)

Demo:

package com.broky.multiThread.safeThread;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @author 13roky
 * @date 2021-04-22 9:36
 */
public class SafeLock {
    public static void main(String[] args) {
        SafeLockThread safeLockThread = new SafeLockThread();
        Thread t1 = new Thread(safeLockThread);
        Thread t2 = new Thread(safeLockThread);
        Thread t3 = new Thread(safeLockThread);

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

class SafeLockThread implements Runnable{
    private int tickets = 100;
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (tickets>0) {
            try {
                //It's locked here. It's kind of like a synchronous monitor
                lock.lock();
                if (tickets > 0) {
                    Thread.sleep(100);
                    System.out.println(Thread.currentThread().getName() + ":\t Ticket number:" + tickets + "\t Remaining votes:" + --tickets);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                //Unlock here after sharing data
                lock.unlock();
            }
        }
    }
}

6.3. Deadlock problem of thread synchronization

Principle:

Different threads occupy the synchronization resources needed by the other party and do not give up. They are waiting for the other party to give up the synchronization resources they need, forming a deadlock.

After a deadlock occurs, there will be no exception or prompt, but all threads are blocked and cannot continue.

Deadlocks should be avoided when using synchronization.

The simplest case of deadlock in Java:

One thread T1 holds lock L1 and applies for lock L2, while the other thread T2 holds lock L2 and applies for lock L1. Because the default lock application operations are blocked, threads T1 and T2 are always blocked. Caused a deadlock. This is the easiest to understand and simplest form of deadlock. However, the deadlock in the actual environment is often much more complex than this. Multiple threads may form a deadlock loop. For example, thread T1 holds lock L1 and applies for lock L2, thread T2 holds lock L2 and applies for lock L3, and thread T3 holds lock L3 and applies for lock L1. This leads to a lock dependent loop: T1 depends on lock L2 of T2, T2 depends on lock L3 of T3, and T3 depends on lock L1 of T1. This leads to a deadlock.

From these two examples, we can conclude that the most fundamental reason for the possibility of deadlock is that the thread applies for another lock L2 when it obtains one lock L1, that is, the lock L1 wants to include lock L2, that is, it applies for lock L2 when it obtains lock L1 and does not release lock L1. This is the most fundamental reason for deadlock. Another reason is that the default lock request operation is blocked.

Solution to deadlock:

1. Special algorithms and principles.
2. Minimize the definition of synchronization resources.
3. Try to avoid nested synchronization.
package com.broky.multiThread.safeThread;

/**
 * @author 13roky
 * @date 2021-04-22 8:34
 */
public class DeadLock {
    public static void main(String[] args) {
        StringBuffer s1 = new StringBuffer();
        StringBuffer s2 = new StringBuffer();

        new Thread() {
            public void run() {
                synchronized (s1) {
                    s1.append("a");
                    s2.append("1");

                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    synchronized (s2) {
                        s1.append("b");
                        s2.append("2");

                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }.start();

        new Thread(new Runnable() {
            public void run() {
                synchronized (s2) {
                    s1.append("c");
                    s2.append("3");

                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    synchronized (s1) {
                        s1.append("d");
                        s2.append("4");

                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }).start();
    }
}

7. Thread communication

At this time, we need to create multiple threads to communicate with each other, although there are many cases when we need to create multiple threads.

Principle:

When a thread executes the code it should execute, manually put the thread into the blocking state. In this way, the next operation can only be operated by other threads. At the beginning of the execution of other threads, manually let the blocked thread stop blocking and enter the ready state. Although the blocked thread stops blocking at this time, the thread that stops blocking cannot execute immediately because the running thread holds the synchronization lock. In this way, the communication between threads can be completed.

Method used:

Wait(): once this method is executed, the current thread will enter blocking. Once wait() is executed, the synchronization monitor will be released.

notify(): once this method is executed, a thread that is waiting will be awakened. If multiple threads are waiting, the one with the highest priority will be awakened.

notifyAll(): once this method is executed, it will wake up all the threads wait ing

Description:

These three methods must be used in synchronous code blocks or synchronous methods.

The caller of the three methods must be the synchronization code block or the synchronization monitor in the synchronization method.

These three methods are not defined in the Thread class, but in the Object class. Because all objects can be used as synchronization monitors, and these three methods need to be called by the synchronization monitor, any class must be satisfied, so they can only be written in the Object class.

Similarities and differences between sleep() and wait(): (interview questions)

  1. The same point: once the two methods are executed, they can make the thread enter the blocking state.

  2. Differences: 1) the positions of the two method declarations are different: sleep() is declared in Thread class and wait() is declared in object class

    2) different call requirements: sleep() can be called in any required scenario. wait() must be invoked in the synchronous code block.

    2) whether to release the synchronization monitor: if both methods are used in the synchronization code block, sleep will not release the lock, and wait will release the lock.

Demo:

package com.broky.multiThread;

/**
 * @author 13roky
 * @date 2021-04-22 13:29
 */
public class Communication {
    public static void main(String[] args) {
        CommunicationThread communicationThread = new CommunicationThread();
        Thread t1 = new Thread(communicationThread);
        Thread t2 = new Thread(communicationThread);
        Thread t3 = new Thread(communicationThread);

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

class CommunicationThread implements Runnable {
    int Num = 1;

    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                notifyAll();
                if (Num <= 100) {
                    System.out.println(Thread.currentThread().getName() + ":\t" + Num);
                    Num++;

                    try {
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else{
                    break;
                }
            }

        }
    }
}

practice

  • Exercise 1:

The bank has an account.

There are two depositors who deposit 3000 yuan into the same account, 1000 yuan each time, three times. Print the account balance after each deposit.

package com.broky.multiThread.exer;

/**
 * Exercise 1
 * The bank has an account
 * There are two depositors who deposit 3000 yuan into the same account, 1000 yuan each time, three times. Print the account balance after each deposit.
 * analysis:
 * 1.Are there multiple threading problems? Yes, there are two depositor threads.
 * 2.Is there shared data? Yes, two depositors deposit money in the same account
 * 3.Whether there are thread safety problems: Yes
 *
 * @author 13roky
 * @date 2021-04-22 12:38
 */
public class AccountTest {
    public static void main(String[] args) {
        Account acct = new Account();
        Customer c1 = new Customer(acct);
        Customer c2 = new Customer(acct);

        c1.setName("Depositor 1");
        c2.setName("Depositor 2");

        c1.start();
        c2.start();

    }
}

class Account {
    private double accountSum;

    public Account() {
        this.accountSum = 0;
    }

    public Account(double accountSum) {
        this.accountSum = accountSum;
    }

    //save money
    public void deppsit(double depositNum) {
        synchronized (this) {
            if (depositNum > 0) {
                accountSum = accountSum + depositNum;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + ": Successfully saved money. The current balance is:\t" + accountSum);
            }
        }

    }

}

class Customer extends Thread {
    private Account acct;

    public Customer(Account acct) {
        this.acct = acct;
    }

    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            acct.deppsit(1000);
        }
    }
}
  • Classic example: producers and consumers are facing problems

The producer gives the product to the clerk, while the customer takes the product from the clerk. The clerk can only hold a fixed number of products at a time (e.g. 20). If the producer tries to produce more products, the clerk will ask the producer to stop. If there is an empty space in the store, the clerk will inform the producer to continue production; If there is no product in the store, the clerk will tell the consumer to wait. If there is a product in the store, he will inform the consumer to take the product.

package com.broky.multiThread.exer;

/**
 * - Classic example: producers and consumers are facing problems
 * The producer gives the product to the clerk, and the customer takes the product from the clerk,
 * The clerk can only hold a fixed number of products at a time (e.g. 20). If the producer tries to produce more products, the clerk will ask the producer to stop,
 * If there is a space in the store for products, inform the producer to continue production; If there are no products in the store, the clerk will tell the consumer to wait,
 * If there is a product in the store, inform the consumer to take the product.
 *
 * analysis:
 * 1.It is a multi-threaded problem. It can be assumed that multiple consumers and multiple producers are multi-threaded
 * 2.There is shared data for operation, and the dealer's stock needs to be operated during production and purchase.
 * 3.Handle thread safety issues.
 * 4.Three categories: producers, distributors and consumers. Dealers are shared by producers and consumers. The producer reads the dealer's inventory and produces products when the inventory is insufficient
 * And send it to the dealer to operate the dealer's inventory + 1. The consumer reads the dealer's inventory. When there is inventory, they can buy. After the purchase, the dealer's inventory is - 1
 * @author 13roky
 * @date 2021-04-22 14:36
 */
public class ProductTest {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();
        Producer p1 = new Producer(clerk);
        Producer p2 = new Producer(clerk);
        p1.setName("Producer 1");
        p2.setName("Producer 2");

        Consumer c1 = new Consumer(clerk);
        Consumer c2 = new Consumer(clerk);
        c1.setName("Consumer 1");
        c2.setName("Consumer 2");

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

class Clerk {
    private int productNum;

    public Clerk() {
        this.productNum = 0;
    }

    public int getProductNum() {
        return productNum;
    }

    public void setProductNum(int productNum) {
        this.productNum = productNum;
    }
}

class Producer extends Thread {
    private Clerk clerk;

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "Start production......");

        while(true){
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            produce();
        }
    }

    public Producer(Clerk clerk) {
        if (clerk != null) {
            this.clerk = clerk;
        }
    }

    private void produce() {
        synchronized (ProductTest.class) {
            ProductTest.class.notify();
            if (clerk.getProductNum() < 20) {
                clerk.setProductNum(clerk.getProductNum() + 1);
                System.out.println(Thread.currentThread().getName() + ":\t Production completion section " + clerk.getProductNum() + " Products");
            }else {
                try {
                    ProductTest.class.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

class Consumer extends Thread {
    private Clerk clerk;

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "Start consumption......");

        while(true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            buy();
        }
    }

    public Consumer(Clerk clerk) {
        if (clerk != null) {
            this.clerk = clerk;
        }
    }

    private void buy(){
        synchronized (ProductTest.class) {
            ProductTest.class.notify();
            if (clerk.getProductNum() > 0) {
                System.out.println(Thread.currentThread().getName() + ":\t Purchase completion page " + clerk.getProductNum() + " Products");
                clerk.setProductNum(clerk.getProductNum() - 1);
            }else {

                try {
                    ProductTest.class.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

Keywords: Java Programming Multithreading

Added by Nilpez on Sun, 20 Feb 2022 04:07:41 +0200