Learning Java multithreading is enough

1, Threads and processes

In programs that do not use multithreading, the program has only one main thread, and our program is top-down according to the code.
In multithreaded programs, other threads run simultaneously with the main thread.
As shown in the figure:
Next, we introduce related concepts
Speaking of process, we have to say program

1. Procedure

Program is an ordered collection of instructions and data. It has no running meaning and is a static concept

2. Process

Process is a dynamic concept. Each running program is a process and an independent unit for resource allocation and scheduling.

3. Thread

Usually, a process can contain several threads. Of course, a process has at least one thread. Thread is the unit of CPU scheduling and execution

A process is a container of threads. A process has at least one thread.

Each thread has its own thread stack and its own register environment.

4. Attention

① Process A and process B do not share resources
② In java: thread A and thread B, heap memory and method area are shared, and the stack of threads exists independently. One thread has one stack.
③ The reason why there are multithreads in java is to improve the processing efficiency of the program
④ When the main method ends, other threads may still be running

5. Advantages of multithreaded programming:

① Memory cannot be shared between processes, but it is very easy to share memory between threads.
② When the system creates a process, it needs to reallocate system resources for the process, but the cost of creating threads is much smaller. Therefore, using multithreading to realize multitask concurrency is more efficient than multi process.
③ Java language has built-in multi-threaded function support, rather than simply as the scheduling mode of the underlying operating system, which simplifies Java multi-threaded programming.

2, Implementation of multithreading

1. Inherit Thread class

Inherit the Thread class, rewrite the run() method, and call start to start the Thread

//Implement multithreading by inheriting Thread class
public class Thread1 extends Thread{
	public void run() {
		for(int i=0;i<20;i++) {
			System.out.println("World"+i);
		}
		
	}
	
	public static void main(String[] args) {
		new Thread1().start();
		for(int i=0;i<100;i++) {
			System.out.println("Hello"+i);
		}
	}
}

Note: thread startup is not necessarily executed immediately, but is scheduled by CPU

2. Implement Runnable interface

Define the class, implement the Runnable interface, implement the run() method, write the thread execution body, create the thread object, and call the start() method to start the thread

public class TestThread2 implements Runnable{
    @Override
    public void run() {
        for(int i=0;i<20;i++){
            System.out.println("World==="+i);
        }
    }

    public static void main(String[] args) {
        //Create the implementation object of runnable interface
        TestThread2 testThread2=new TestThread2();

        //Create a thread object and start our thread agent (static agent) through the thread object
        new Thread(testThread2).start();
        for(int i=0;i<100;i++){
            System.out.println("Hello==="+i);
        }
    }
}

It is recommended to use Runnable object to avoid the limitation of java single inheritance and facilitate the use of the same object by multiple threads.

3. Implement Callable interface (just understand)

public class TestCallable implements Callable {
    @Override
    public Boolean call() throws Exception {
        for(int i=0;i<20;i++){
            System.out.println("World "+i);
        }
        return true;
    }

    public static void main(String[] args) {
        TestCallable testCallable=new TestCallable();

        //Create execution service
        ExecutorService ser= Executors.newFixedThreadPool(1);

        //Submit for execution
        Future<Boolean> r1=ser.submit(testCallable);

        //Main thread execution
        for(int i=0;i<100;i++){
            System.out.println("Hello "+i);
        }

        //Get results
        try {
            boolean rs1=r1.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        //Shut down service
        ser.shutdown();
    }
}

3, Thread execution state and method

1,Thread.sleep(long millis) thread sleep

  • Each thread has a lock, and sleep will not release the lock

The current thread is blocked for millis econd
Analog Countdown:

//Analog countdown
//Print current system time
public class TestSleep {
    public static void main(String[] args) {
        Date startTime=new Date(System.currentTimeMillis());
        for(int i=10;i>=0;i--){
            try {
                Thread.sleep(1000);//The main thread sleeps for 1 second
                System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
                startTime=new Date(System.currentTimeMillis());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

2. Thread stop

  • Recommended use times to avoid dead circulation
  • Flag bits are recommended
  • JDK obsolete methods such as stop and destroy are not recommended

Set the flag bit to stop the thread:

public class TestStop implements Runnable{
    //Set a flag bit
    private boolean flag=true;
    @Override
    public void run() {
        int i=0;
        while(flag){
            System.out.println("run......thread======"+i++);
        }
    }


    public void stop(){
        this.flag=false;
    }

    public static void main(String[] args) {
        TestStop testStop=new TestStop();
        new Thread(testStop).start();
        for(int i=0;i<1000;i++){
            System.out.println("main===="+i);
            if(i==900){
                testStop.stop();
                System.out.println("Thread stop");
            }
        }
    }
}

3,Thread.yield() thread comity

Pause the currently executing thread without blocking it
Change the thread from running state to ready state
If the CPU is rescheduled, comity may not succeed (depending on the probability)

public class TestYield{
    public static void main(String[] args) {
        MyYield myYield1=new MyYield();

        new Thread(myYield1,"a").start();

        new Thread(myYield1,"b").start();
    }

}
class MyYield implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"Thread start===");
        Thread.yield();
        System.out.println(Thread.currentThread().getName()+"Thread end===");
    }
}

Comity failed:

a Thread start===
a Thread end===
b Thread start===
b Thread end===

Comity success:

a Thread start===
b Thread start===
a Thread end===
b Thread end===

4,Thread.join() thread enforcement

join merge threads, and execute other threads after this thread is completed, that is, other threads are blocked, which can be imagined as queue jumping

public class TestJoin implements Runnable{
    @Override
    public void run() {
        for (int i=0;i<1000;i++){
            System.out.println("VIP==="+i);
        }
    }


    public static void main(String[] args) {
        TestJoin testJoin=new TestJoin();
        Thread thread=new Thread(testJoin);
        thread.start();

        //Main thread
        for(int i=0;i<500;i++){
            if(i==100){
                try {
                    thread.join();  //Queue jumping (enforced, prone to blocking)
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("Main thread==="+i);
        }
    }
}

When the main thread loop reaches 100 times, it will let the thread run first.

5. Daemon thread

Java threads are divided into user threads and daemon threads.
Daemon thread is a thread that provides a general service in the background when the program is running. When all user threads stop, the process will stop all daemon threads and exit the program.
The method to set a thread as a daemon thread in Java: setDaemon(true) method.

public class TestDaemon implements Runnable{
    @Override
    public void run() {
        while(true){
            System.out.println("Daemon thread running===");
        }
    }

    public static void main(String[] args) {
        TestDaemon testDaemon=new TestDaemon();
        Thread thread=new Thread(testDaemon);
        thread.setDaemon(true); //Set as daemon thread
        thread.start();

        for(int i=0;i<10;i++){
            System.out.println("The main thread (user thread) runs==="+i);
        }
        System.out.println("Main thread exit===");
    }
}

As can be seen from the print results, the daemon thread stops running after the main thread ends and the jvm exits
be careful:

  • setDaemon(true) must be set before start(), otherwise an IllegalThreadStateException will be thrown. The thread is still a user thread by default and continues to execute
  • A thread created by a daemon thread is also a daemon thread
  • The daemon thread should not access or write persistent resources, such as files and databases, because it will be stopped at any time, resulting in problems such as unreleased resources and interrupted data writing

4, Thread synchronization

Let's talk about what thread synchronization is not safe before we talk about it
We simulated a thread unsafe ticket buying process

//Unsafe ticket buying
//Each thread interacts in its own working memory. Improper memory control will cause data inconsistency
public class UnsafeBuyTickets {

    public static void main(String[] args) {
        Station station=new Station();
        new Thread(station,"Xiao Ming").start();
        new Thread(station,"Xiao Hong").start();
        new Thread(station,"ben").start();
    }
}

class Station implements Runnable{

    //ticket
    static int tickets=10;

    //Flag bit
    static boolean flag=true;
    @Override
    public void run() {
        while(flag){
            try {
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void buy() throws InterruptedException {
        if(tickets<=0){
            flag=false;
            return;
        }

        
        //Buy a ticket
        System.out.println(Thread.currentThread().getName()+"Yes"+tickets--);
        //Analog delay
        Thread.sleep(100);
    }
}
Xiao Ming bought 10
ben Bought 9
 Xiao Hong bought 8
 Xiao Ming bought 7
ben Bought 6
 Xiao Hong bought 7
ben Bought 5
 Xiao Hong bought 5
 Xiao Ming bought 4
 Xiao Hong bought 3
ben Bought 2
 Xiao Ming bought 3
ben Bought 1
 Xiao Hong bought 0
 Xiao Ming bought it-1

The final result was negative and obviously wrong.
Reason for this error: because multiple threads of the unified process share a piece of storage space, it brings convenience and access conflict. For example, two threads access the resource at the same time

1. synchronized keyword

  • Because we can use the private keyword to ensure that data objects can only be accessed by methods, we want to propose a mechanism for methods. This mechanism is the synchronized keyword, which includes two uses: the synchronized method and the synchronized block.
  • Synchronized methods control access to "objects". Each object corresponds to a lock. Each synchronized method must obtain the lock of the object calling the method before execution. Otherwise, the thread will block. Once the method is executed, it will monopolize the lock until the method returns.

Let's rewrite the above unsafe ticket buying process and add the synchronized keyword:

① Synchronization method

//Buy tickets safely
public class SafeBuyTickets {
    public static void main(String[] args) {
        Station1 station1=new Station1();
        new Thread(station1,"Xiao Ming").start();
        new Thread(station1,"Xiao Hong").start();
        new Thread(station1,"ben").start();
    }


}

class Station1 implements Runnable{

    //ticket
    static int tickets=10;

    //Flag bit
    static boolean flag=true;
    @Override
    public void run() {
        while(flag){
            try {
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


    //Synchronous lock, lock this
    public synchronized void buy() throws InterruptedException {
        if(tickets<=0){
            flag=false;
            return;
        }

        //Analog delay
        Thread.sleep(15);
        //Buy a ticket
        System.out.println(Thread.currentThread().getName()+"Yes"+tickets--);
    }
}

The result is:

Xiao Ming bought 10
 Xiao Hong bought 9
 Xiao Hong bought 8
 Xiao Hong bought 7
 Xiao Hong bought 6
 Xiao Hong bought 5
 Xiao Hong bought 4
 Xiao Hong bought 3
ben Bought 2
ben Bought 1

There are also some problems with the locking mechanism

  • A thread holding a lock will cause all threads that need the lock to hang
  • In multi-threaded competition, locking and releasing locks will produce more context switching and scheduling delays, causing performance problems
  • If a high priority thread waits for a low priority thread to release the lock, it will lead to priority inversion and performance problems

② Synchronization block

We know that ArrayList is a thread unsafe collection

public class UnsafeArrayList {
    public static void main(String[] args) {
        ArrayList<String> list=new ArrayList<>();
        for(int i=0;i<10000;i++){
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

The number of objects in the list should have been 10000, but there is thread insecurity

9999

At this time, we use synchronized to lock the list, that is, the statement of adding objects to the list is wrapped in a synchronization block

public class SafeArrayList {
    public static void main(String[] args) {
        ArrayList<String> list=new ArrayList<>();
        for (int i=0;i<10000;i++){
            new Thread(() -> {
                synchronized (list) {
                    list.add(Thread.currentThread().getName());
                }
            }).start();
            }
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

The list size is 10000

10000

2. Lock

  • From jdk5 Since 0, Java has provided a powerful thread synchronization mechanism - synchronization is achieved by explicitly defining synchronization Lock objects. Synchronous locks use Lock objects as
  • java.util.concurrent.locks.Lock interface is a tool that controls multiple threads to access shared resources. Lock provides exclusive access to shared resources. Only one ready-made lock object can be locked at a time. Threads should obtain the lock object before accessing shared resources
  • ReentrantLock class implements Lock. It has the same concurrency and memory semantics as synchronized. ReentrantLock is commonly used in thread safety control. It can explicitly add and release locks.

Using Lock to realize synchronous Lock ticket buying process

public class TestLock {
    public static void main(String[] args) {
        TestLock2 testLock2=new TestLock2();
        new Thread(testLock2).start();
        new Thread(testLock2).start();
        new Thread(testLock2).start();
    }

}

class TestLock2 implements Runnable{

    int ticketNum=10;

    //Define a lock
    ReentrantLock lock=new ReentrantLock();


    @Override
    public void run() {

        while(true){
            try{
                lock.lock();    //Lock
                if(ticketNum>0){
                    try {
                        Thread.sleep(1000);
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                    System.out.println(ticketNum--);
                }else{
                    break;
                }
            }finally {
                 lock.unlock();     //Release lock
            }
        }
    }
}

3. synchronized vs Lock

  • Lock is an explicit lock (opened and closed manually), and synchronized is an implicit lock, which is automatically released out of the scope.
  • Lock has code block lock and synchonized has code block lock and method lock.
  • Using Lock lock, the JVM will spend less time to schedule and perform better. And it has more scalability
  • Priority: Lock > sync code block > sync method

5, Thread communication

1. Producer consumer issues

Before talking about this part, we need to introduce a classic thread communication problem: "producer consumer problem".

Producer consumer problem, also known as bounded buffer problem, is a classic case of multi-threaded synchronization. This problem describes what happens when two threads sharing a fixed size buffer -- the so-called "producer" and "consumer" -- actually run. The main function of the producer is to generate a certain amount of data into the buffer, and then repeat the process. At the same time, consumers consume this data in the buffer. The key to this problem is to ensure that producers will not add data when the buffer is full, and consumers will not consume data when the buffer is empty.
.
To solve this problem, the producer must sleep when the buffer is full (or simply give up the data). Only when the next consumer consumes the data in the buffer can the producer wake up and start adding data to the buffer. Similarly, consumers can sleep when the buffer is empty, and wake up consumers after the producer adds data to the buffer. Interprocess communication is usually used to solve this problem. If the solution is not perfect, it is prone to deadlock. When a deadlock occurs, both threads will fall into sleep and wait for each other to wake themselves up.

  • Suppose that the warehouse can only store one product, the producer puts the produced products into the warehouse, and the consumer takes away the products in the warehouse for consumption
  • If there is no product in the warehouse, the producer will put the product into the warehouse, otherwise stop production and wait until the product in the warehouse is taken away by the consumer
  • If there is a product in the warehouse, the consumer can take the product away for consumption, otherwise stop consumption and wait until the product is placed in the warehouse again

Analysis: producers and consumers share a resource, and they are interdependent and conditional on each other

  • For producers, they should inform consumers to wait before producing products; After the production of products, consumers should be informed of consumption
  • For consumers, after consumption, they should inform producers that they have finished consumption and need to produce new products

2. Method for solving thread communication problem


Note: all methods of Object class can only be used in synchronization methods or synchronization code blocks

3. Solving the problems of producers and consumers by pipe process method

Concurrent collaboration model - > management method
The product produced by the producer is put into the buffer zone, and the consumer takes out the product from the buffer zone

//Producer consumer model, pipe process method
public class PcTest {

    public static void main(String[] args) {
        SynContainer container=new SynContainer();

        Productor productor=new Productor(container);

        Comsumer comsumer=new Comsumer(container);

        productor.start();

        comsumer.start();
    }
}

//producer
class Productor extends Thread{

    private SynContainer container;

    public Productor(SynContainer container){
        this.container=container;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            container.push(new Product(i));
            System.out.println("The producer produced the second product"+i+"Products===");
        }
    }
}

//consumer
class Comsumer extends Thread{
    private SynContainer container;

    public Comsumer(SynContainer container){
        this.container=container;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {

            System.out.println("The consumer has consumed the second"+container.pop().id+"Products--->");
        }
    }
}

//product
class Product{
    public int id;
    public Product(int id){
        this.id=id;
    }
}

//Secure buffer
class SynContainer{
    Product[] products=new Product[10];

    //Quantity of products in container
    static int count=0;

    //Producers put in products
    public synchronized void push(Product product){
        //If the container is full, wait for the consumer to consume
        while(count>=products.length) {
            //Inform consumers to consume and producers to wait
            try {
                System.out.println("========The container is full========");
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //If it is not full, inform the consumer to consume
        products[count]=product;
        count++;
        this.notifyAll();
    }

    //Consumer products
    public synchronized Product pop(){
        //Judge whether it can be consumed
        while(count==0){
            //Waiting for producers to produce
            try{
                System.out.println("========The container is empty========");
                this.wait();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }

        //Can consume
        count--;

        Product product=products[count];
        this.notifyAll();
        return product;

6, Thread pool

  • JDK provides API executorservice and Executors related to thread pool
  • ExecutorService: the real thread pool interface. Common subclass ThreadPoolExecutor
  • void execute(Runnable command): used to return Runnable
//Test thread pool
public class TestPool {

    public static void main(String[] args) {

        //1. Create thread pool
        //newFixedThreadPool. The parameter is the size of the thread pool
        ExecutorService service= Executors.newFixedThreadPool(10);

        //implement
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());

        //2. Close link
        service.shutdown();
    }
}

class MyThread implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

result

pool-1-thread-1
pool-1-thread-4
pool-1-thread-3
pool-1-thread-2
pool-1-thread-6
pool-1-thread-5
pool-1-thread-7
pool-1-thread-8
pool-1-thread-9
pool-1-thread-9
pool-1-thread-10
pool-1-thread-9

No matter how many executions are execute d, only the threads in the thread pool will be taken

Reference blog: Crazy God says java - multithreaded notes (and source code)

Keywords: Java Algorithm leetcode

Added by benmay.org on Sun, 06 Mar 2022 09:04:53 +0200