Java multithreading - preliminary learning notes

Multithreading

Main idea: guide step by step in multiple scenarios to understand and learn Java threads from "why, what and how"

Scenario 1: getting to know the thread concept

When using QQ, you can send messages to each other and receive messages from others

Why do you need the concept of threads?

  • Personal understanding:

If you follow the idea of writing a main method in the past, after writing the methods of sending and receiving messages respectively, you have to call the two methods circularly at a certain time interval, which is more troublesome, and you can't realize simultaneous and parallel.

However, when the concept of thread is introduced, the methods of sending and receiving messages can be separated:

Thread 1 waits for itself to enter a message and send a message,

Thread 2 waits for messages from others, and pushes them to the view layer when they are received.

  • Parallel concurrent extension:

It seems that threads 1 and 2 run "simultaneously". This "simultaneously" can be divided into two cases: parallel and concurrent.

  1. When different CPUs are specified for threads 1 and 2 on a multi-core cpu computer (SetThreadAffinityMask), the two threads are parallel;

  2. When the cpu is not specified, in most cases, multiple threads of the same process seize a cpu core and execute concurrently in time slices.

What are threads in JAVA?

From the process of using QQ in scenario 1, it is not difficult to see that threads are objects that Java can "run at the same time", which are used when you want to realize several functions at the same time.

The main thread is started by default, and other threads are created by the programmer.

Multithreading shares the heap memory of the process (mainly for object instances) and only shares its own part of the stack memory (for instructions and operands).

How to implement the simplest multithreading?

Although thread is a thread class, look at the source code. Thread implements the Runnable interface. Let's start with the Runnable interface.

Runnable interface

The source code is very simple. There is only one abstract method, run

  • Source code comments:

When an object * * * (not the object of Thread class) * * *, which implements the Runnable interface, creates * * * (new) * *, and starts * * * (start) a Thread, its run method will be automatically called in a separate Thread. This run method can perform any operation.

  • in other words:

The object of the Thread subclass we created does not need to call the run method explicitly. After creating a new and starting the start Thread object, the run method will be called automatically

  • experiment

MyThread.java

public class MyThread extends Thread{
		// Because the Thread class implements the run method, the Thread subclass can be overridden
    @Override
    public void run() {
        System.out.println("implement run Yes");
    }
}

Demo1.java

public class Demo1 {
    public static void main(String[] args) {
        Thread myThread = new MyThread();// Thread object
        System.out.println("this is the main thread.");
        myThread.start();// See if run is executed automatically
    }
}

Run result: the run of the Runnable interface is indeed executed automatically

Thread class

There are many methods and internal classes in the source code. Let's take a look at the basic.

Construction method

9 kinds!!!

There are four commonly used:

  1. No parameter
	//Allocates a new Thread object. This constructor has the same effect as Thread (null, null, gname), where gname is a newly generated name. Automatically generated names are of the form "Thread-"+n, where n is an integer.
	public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }

	// Look at the init method called
	private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null, true);
    }

	// No, init is an overloaded method, and the full version has two parameters
	private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) 

Since the init method called by the parameterless construction method actually has 6 parameters, it is assumed that other construction methods also change on the basis of these 6 parameters.

  1. With Runnable object parameters
// The Runnable object must implement the abstract run method. Passing this parameter means that the thread will automatically execute a process
	public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
  1. With name parameter
	// Name refers to the thread name
	public Thread(String name) {
        init(null, null, name, 0);
    }
  1. With ThreadGroup and name parameters
	public Thread(ThreadGroup group, String name) {
        init(group, null, name, 0);// Threads in the thread group can access the current thread group information, not those not in the current thread group. It feels a bit like a thread pool
    }

There are also many fields and methods in the thread group. The thread group can traverse threads to know which have been run and which are still active.

start()
Execute run()

Implement the run() method of the Runnable interface and execute it automatically after start ing

Current thread currentThread()
Thread.currentThread();
// Method to return the thread currently being executed.
Set / get thread name (set / getname)
Thread.currentThread().setName("Send a message");
Thread.currentThread().getName("Receive message");
// Method to set / get the thread name of the thread currently being executed.

Runnable is used in combination with Thread

It is not recommended to directly use Thread objects instead of Runnable objects, because:

  1. JAVA only has the limitation of single inheritance. If you inherit Thread, you can't inherit other classes
  2. Don't write subclasses of Thread
  3. Multiple threads (objects) can easily perform the same task
(1) A class that implements the Runnable interface
// Task class for thread execution
class MyRunnable implements Runnable{
    @Override
    public void run(){
        // Task content
    }
}

// Task class object
MyRunnable myRunnable = new Runnable();
(2) Object of Thread class
Thread thread = new Thread(myRunnable);//When used with Runnable, there is no need to write Thread class

Scenario 2: thread interruption

When using anti-virus software, the anti-virus is half. I want to stop anti-virus.

Why do threads interrupt?

Normally, a thread ends automatically after executing the run() method.

In actual use, users should be able to safely stop a function at any time.

What is a thread interrupt?

In the past, JAVA provided the stop() method for threads, but the external stop of a thread may generate resources that cannot be recycled (file handles, hardware resources, etc.), resulting in the continuous occupation of memory.

Therefore, using interrupt() now is equivalent to throwing an interrupt exception. The thread catches the exception and handles it accordingly.

How to implement thread interrupt safely?

Thread thread = new Thread(runnable);
thread.start();
sleep(5);
thread.interrupt();// Interrupt flag

// Overridden run method
@Override
public void run(){
  try{
    ...
  }catch (InterruptException e){
    ...// Release resources
    return;// Thread end
  }

Scenario 3: daemon thread

Classic daemon thread, GC garbage collector.

What is a daemon thread?

There are two types of JAVA threads: user thread and daemon thread

User thread: when a process does not contain any surviving user threads, the process ends.

Daemon thread: used to guard user threads. When the last user thread ends, all daemon threads will die automatically. For example, without the guardian (user thread), GC has no meaning.

Threads that are not specified as daemon threads are user threads.

How to implement daemon thread?

Thresd thread = new Thread();
thread.setDeamon(true);// The default is false, that is, the user thread, which must be set before start
thread.start();

Thread state

Scenario 3: thread synchronization

Three windows sell tickets together. When the remaining tickets are 0, all three windows can no longer buy tickets.

Why do threads need synchronization?

Mainly thread safety issues:

The examples of queuing up for dinner, queuing into the fitting room and selling tickets at multiple windows will be chaotic if there are no restrictions, that is, different people eat the same meal, enter the same fitting room and sell the - 1st ticket.

What is thread synchronization?

In fact, it is * * * a piece of code * * *, and the thread needs * * * to queue * * * to execute this code. In this way, it can judge whether * * * needs to execute this code at the beginning of this code.

How to achieve thread synchronization?

1. Synchronized code block (object o)

//Create three threads in the main() method to buy tickets
Runnable ticket = new Ticket();
//All perform the same task
new Thread.start(ticket);
new Thread.start(ticket);
new Thread.start(ticket);

//Task class
class Ticket implements Runnable{
    //The number of votes is shared by three threads, because it is called when it is nre Runnable
    private int count = 10;
    //The lock object is shared by three threads, because it is called when it is nre Runnable
    private Object o = new Object();
    
    //Called when start() is started
    @Override
    public void run(){
        //You can't write lock objects in run because each thread has its own lock. There is no need to queue up for execution. The locks of threads do not interfere with each other. You can use them if you want
        
        //Each thread cycles to buy tickets. When the number of votes is less than 0, it means that the tickets are sold out and the ticket buying should be ended
        while(count>0){
            //Synchronization code block, which is the critical area
            synchronized(o){//o can be changed to this. In short, it needs to be the same object
                //In synchronization, it is also necessary to actually judge the number of remaining votes to reach the number of votes that do not sell - 1
                if(count<0){
                    System.out.println("Preparing to buy tickets...");
                    try{
                        Thread.sleep(1000);//Thread sleep, in milliseconds
                    }catch(InterruptedException e){
                        e.pribtStackTrace();
                    }
                    count--;
                    System.out.println("Tickets sold successfully, remaining tickets:"+count);
                }else{
                    //End the thread, return is OK
                    break;
                }
            }
        }
    }
}

2. Synchronization method synchronized

Write the contents of the synchronization code block as a method. This method is modified with synchronized, which becomes a synchronization method.

//Called when start() is started
    @Override
    public void run(){
        while(true){
            boolean flag = sale();
            if(!flag){
                break;
            }
        }
    }

//Ticket selling process
public synchronized boolean sale(){
    if(count<0){
    	System.out.println("Preparing to buy tickets...");
        try{
            Thread.sleep(1000);//Unit: ms
        }catch(InterruptedException e){
            e.pribtStackTrace();
        }
        count--;
        System.out.println("Tickets sold successfully, remaining tickets:"+count);
        return true;
    }
    return false;         
}

3. Display Lock subclass ReentrantLock

//Task class
class Ticket implements Runnable{
    //The number of votes is shared by three threads, because it is called when it is nre Runnable
    private int count = 10;
    //Show lock objects
    private Lock lock = new ReentrantLock();
    
    //Called when start() is started
    @Override
    public void run(){
        //Each thread cycles to buy tickets. When the number of votes is less than 0, it means that the tickets are sold out and the ticket buying should be ended
        while(count>0){
            //Add a lock. When other threads see a lock, they queue up
            lock.lock();
            
            //Locked process, critical zone
            if(count<0){
                System.out.println("Preparing to buy tickets...");
                try{
                    Thread.sleep(1000);//Unit: ms
                }catch(InterruptedException e){
                    e.pribtStackTrace();
                }
                count--;
                System.out.println("Tickets sold successfully, remaining tickets:"+count);
            }else{
                //End the thread, return is OK
                break;
            }
            
            //Unlock
            lick.unlock()
        }
    }
}

Fair lock

Who got the lock? First come, first served

//Fair lock object, pass in the true parameter
private Lock lock = new ReentrantLock(fair: true);

Unfair lock

Who got the lock? Threads grab together.

Implicit locks and display locks are * * * unfair locks by default***

What is the difference between an implicit lock and a display lock?

To be added

thread deadlock

Multiple threads have entered the interdependent * * * synchronized * * method.

  • resolvent

In the code that may cause deadlock, do not call other code that may cause deadlock.

Scenario 5: thread communication

Producer and consumer issues

Chef and waiter questions:

One thread wait s for another thread to notify itself.

How to realize thread communication?

In short: synchronized + wait/notify

package threadCommunication;

/**
 * The cook cooked first and the waiter served later
 * The cook will cook and the waiter will serve
 * ......
 */
public class Demo1 {
    public static void main(String[] args) {
        Food f = new Food();
        Cook cook = new Cook(f);
        Waiter waiter = new Waiter(f);
        cook.start();
        waiter.start();
    }

    // Cook, cook three courses
    static class Cook extends Thread{
        private Food f;
        public Cook(Food f){
            this.f = f;
        }

        @Override
        public void run() {
            for (int i=0;i<3;i++){
                if (i % 2 == 0) {
                    f.setNameAndTaste("Kung Pao Chicken", "spicy ");
                }else {
                    f.setNameAndTaste("Boiled Fish with Pickled Cabbage and Chili", "sour and hot");
                }
            }
        }
    }

    // Waiter, serve three times
    static class Waiter extends Thread{
        private Food f;
        public Waiter(Food f){
            this.f = f;
        }

        @Override
        public void run() {
            for (int i=0;i<3;i++){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                f.getNameAndTaste();
            }
        }
    }

    // food
    static class Food{
        private String name;//Dish name
        private String taste;//flavor
        private boolean flag=true;//Sign of finished dishes

        // In the cooking method, it's useless to only add synchronized. This is an unfair lock. The chef may continue to seize the opportunity of execution and do not give the waiter the opportunity to serve dishes
        // Inconsistent with the display scene
        public synchronized void setNameAndTaste(String name, String taste){
            if (flag){
                this.name = name;
                // Cooking takes time
                try {
                    System.out.println("In cooking, dish name:"+name+",flavor:"+taste);
                    Thread.sleep(1000);
                    // Thread hibernation is used here, and thread interruption may occur
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.taste = taste;
                flag = false;
                this.notifyAll();// Wake up all waiting threads under this
                try {
                    this.wait();//Cook waiting
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        // Vegetable picking method
        public synchronized void getNameAndTaste() {
            if (!flag){
                System.out.println("The name of the dish served by the waiter:"+this.name+",Taste:"+this.taste);
                flag = true;
                this.notifyAll();
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }
}

Thread pool

Common thread steps:

Create a thread, create a task, execute a task, stop the thread (interrupt), and close the thread (the thread will be closed automatically after the run is completed)

Thread pool:

Executors.execute(new Runnable() {...// Common lambda expressions
                                 });

4 default thread pools

Cache, fixed length, single thread, fixed cycle length

1. Cache thread pool

Unlimited length

First judge whether there are idle threads in the thread pool. If not, create a new thread, which can flexibly recycle idle threads

ExecutorService service = Executors.newCachedThreadPool(nThreads: n);

2. Fixed length linear pool

Parameter specifies the thread pool length

ExecutorService service = Executors.newFixedThreadPool(nThreads: n);

3. Single thread pool

It can be executed in the specified order (FIFO, LIFO, priority)

ExecutorService service = Executors.newSingleThreadExecutor();

4. Periodic fixed length linear pool

ScheduleExecutorService service = Executors.newScheduledThreadPool(corePoolSize: n);

// 2 execution methods
// Execute a xx task after a certain time interval
service.schedule(new Runnable() {}, 5, TimeUnit.SECONDS);

// Execute xx tasks periodically and continuously
service.scheduleAtFixedRate(new Runnable() {}, initDelay: n, period: m TimeUnit.SECONDS);

Interface Callable with return value

Compare the differences between Runnable interfaces:

  1. The Callable interface uses generics

  2. The abstract call method has a return, so you need to specify the return value type

FutureTask task object

Lambda expressions / anonymous functions

Functional programming idea

Keep only the method part, independent of the object

Make the implementation of the interface simpler. This interface can only have one method to be implemented

(parameters) -> expression
// or
(parameters) ->{ statements; }
  • Practical examples
cachedThreadPool.execute(()->{
 System.out.println("Thread Name:" + Thread.currentThread().getName());
});

Tags: Java

Posted on Thu, 07 Oct 2021 14:32:29 -0400 by mhalloran