[Java Advanced Programming] Multithreading

insert image description here

1. Basic concepts: program, process, thread

1.1. Procedure

  • Concept: It is a collection of a set of instructions written in a certain language to complete a specific task. That is, a piece of static code.

1.2. Process

  • Concept: an execution process of a program, or a program that is running.
  • Note: Processes are the unit of resource allocation, and the system will allocate different memory areas for each process during runtime.

1.3, thread

  • Concept: A process can be further refined into a thread, which is an execution path inside a program.
  • Description: Thread is the unit of scheduling and execution. Each thread has an independent running stack and program counter (pc), and the overhead of thread switching is small.
  • Each thread has its own independent: virtual machine stack, program counter
  • Multiple threads share structures in the same process: method area, heap

1.4. Understanding of single-core CPU and multi-core CPU

  • A single-core CPU is actually a kind of fake multi-threading, because it can only execute the task of one thread in one time unit. But because the CPU time unit is very short, it can't be felt.
  • If it is multi-core, the efficiency of multi-threading can be better utilized.
  • A Java application java.exe actually has at least three threads: main() main thread, gc() garbage collection thread, and exception handling thread. Of course, if an exception occurs, it will affect the main thread.

1.5. Parallelism and concurrency

  • Parallelism: Multiple CPUs execute multiple tasks at the same time.
  • Concurrency: A CPU (using time slices) executes multiple tasks at the same time.

1.6. Advantages of using multithreading

  • Background: Taking a single CPU as an example, using only a single thread to complete multiple tasks one after another (calling multiple methods) is definitely shorter than using multiple threads to complete it. Why do we still need multi-threading?
  • Advantages of multithreaded programs:
    • 1. Improve the response of the application. Makes more sense for graphical interfaces and enhances user experience.
    • 2. Improve the CPU utilization of the computer system
    • 3. Improve program structure. Divide a long and complex process into multiple threads and run independently, which is easy to understand and modify

1.7. When do you need multithreading?

  • A program needs to perform two or more tasks simultaneously
  • When the program needs to implement some tasks that need to wait, such as user input, file read and write operations, network operations, search, etc.
  • When some programs running in the background are required.

2. Creation and use of threads

2.1. One way to create multi-thread: Inherit the Thread class

  • 1. Create a subclass that inherits from the Thread class
  • 2. Rewrite the run() of the Thread class --> declare the operations performed by this thread in run()
  • 3. Create an object of a subclass of the Thread class
  • 4. Call start() through this object, the function of start():
    • 1. Start the current thread
    • 2. Call the run() of the current thread
//1、创建一个继承于Thread类的子类
class MyThread extends Thread {
    //2、重写Thread类的run()
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(i);
            }
        }
    }
}

public class ThreadTest {
    public static void main(String[] args) {
        //3、创建Thread类的子类的对象
        MyThread t1 = new MyThread();

        //4、通过此对象调用start()
        t1.start();

        // 或者通过创建Thread类的匿名子类的方式
        new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    if (i % 2 == 0) {
                        System.out.println(Thread.currentThread().getName() + ":" + i);
                    }
                }
            }
        }.start();
    }
}
  • Note 1: We cannot start threads by calling run() directly
  • Note 2: Re-starting a thread requires re-creating a thread object. It is not allowed to start() the thread that has already started(), otherwise an IllegalThreadStateException will be reported

2.2. Related methods of the Thread class

  • void start(): Start the current thread and call the run() of the current thread
  • run(): It is usually necessary to override this method in the Thread class, and declare the operations to be performed by the created thread in this method
  • String getName(): Get the name of the current thread
  • void setName(String name): Set the name of the current thread
  • static Thread currentThread(): static method, get the thread that executes the current code
  • static void yield(): Release the execution right of the current CPU
  • join(): Call the join() of thread b in thread a. At this time, thread a enters the blocking state. Thread a does not end the blocking state until thread b is completely executed.
  • static void sleep(long millitime): Let the current thread "sleep" for the specified millitime milliseconds. During the specified millitime milliseconds, the current thread is blocked.
  • stop(): Obsolete. When this method is executed, the current thread is forcibly terminated.
  • boolean isAlive(): Determine whether the current thread is alive

2.3, thread scheduling

  • Scheduling strategy
    • time slice
    • Preemptive: High-priority threads preempt CPU
  • Java's dispatch method
    • Threads of the same priority form a first-in-first-out queue, using the time slice strategy
    • For high priority, use the preemptive strategy of priority scheduling

2.4, thread priority

  • thread priority
    • MAX_PRIORITY:10
    • MIN_PRIORITY:1
    • NORM_PRIORITY:5 --> default priority
  • method involved
    • getPriority(): Get the priority of the thread
    • setPriority(int newPriority): Set the priority of the thread
  • When a thread is created, it inherits the priority of the parent thread
  • Low priority is just a low probability of being scheduled, not necessarily scheduled after high priority threads

2.5. The second way to create multithreading: implement the Runnable interface

  • 1. Create a class that implements the Runnable interface
  • 2. Implement the class to implement the abstract method in Runnable: run()
  • 3. Create an object of the implementation class
  • 4. Pass this object as a parameter to the constructor of the Thread class to create an object of the Thread class
  • 5. Call start() through the object of the Thread class
//1、创建一个实现了Runnable接口的类
class MThread implements Runnable {
    //2、实现类去实现Runnable中的抽象方法:run()
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(i);
            }
        }
    }
}

public class ThreadTest1 {
    public static void main(String[] args) {
        //3、创建实现类的对象
        MThread mThread = new MThread();
        //4、将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
        Thread t1 = new Thread(mThread);
        //5、通过Thread类的对象调用start():启动线程,调用当前线程的run() --> 调用了Runnable类型的target的run()
        t1.start();

        //再启动一个线程
        Thread t2 = new Thread(mThread);
        t2.start();
    }
}

2.6. Compare the two ways of creating threads

  • In development: the way to implement the Runnable interface is preferred
    • 1. The implementation method has no limitation of single inheritance of classes
    • 2. The implementation method is more suitable for handling the situation where multiple threads have shared data
  • 联系:public class Thread implements Runnable
  • The same point: both methods need to rewrite run(), and declare the logic to be executed by the thread in run()

3. Thread life cycle

graph LR
A(新建) --> |"调用start()"| B(就绪)
B --> |"获取CPU执行权"| C(运行)
C --> |"执行完run();调用线程的stop();出现Error/Exception且没有处理"| D(死亡)
C --> |"失去CPU执行权或yield()"| B
C --> |"sleep(long time);join();等待同步锁;wait();suspend()"| E(阻塞)
E --> |"sleep()时间到;join()结束;获取同步锁;notify()/notifyAll();resume()"| B

4. Thread synchronization

4.1. Thread synchronization method 1: Synchronize code blocks

synchronized(同步监视器) {
    // 需要被同步的代码
}
  • 1. The code that operates the shared data is the code that needs to be synchronized
  • 2. Shared data: Variables that multiple threads operate together.
  • 3. Synchronization monitor, commonly known as: lock. Objects of any class can act as locks.
    • Requirements: Multiple threads must share the same lock.
  • Supplement: In the way of implementing the Runnable interface to create multiple threads, we can consider using this as a synchronization monitor
  • In the way of inheriting the Thread class to create multi-threads, use this carefully as a synchronization monitor, and consider using the current class as a synchronization monitor
public class Window1 implements Runnable{
    private int ticket = 100;
    Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            //正确,只要保证多个线程使用同一个对象当做锁即可
            // synchronized (obj) {
            // 此时的this:唯一的Window1的对象
            synchronized (this) {
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}
public class Window2 extends Thread{
    private static int ticket = 100;
    private static Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            //正确
            // synchronized (obj) {
            // 错误,因为通过继承Thread方式,需要通过不同的对象来创建线程
            // 此时的this代表着不同的对象 
            // synchronized (this) {
            // 正确,Class clazz = Window2.class,Window2.class只会加载一次
            synchronized (Window2.class) {
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}
  • Benefits: The synchronization method solves the thread safety problem.
  • Limitations: When operating synchronous code, only one thread can participate, while other threads wait. Equivalent to a single-threaded process, the efficiency is low.

4.2. Thread synchronization method 2: Synchronization method

  • Synchronous methods still involve a sync monitor, they just don't need to be explicitly declared by us.
  • For non-static synchronization methods, the synchronization monitor is: this; for static synchronization methods, the synchronization monitor is: the current class itself
public class Window3 implements Runnable{
    private int ticket = 100;

    @Override
    public void run() {
        while (true) {
            show();
        }
    }

    private synchronized void show () {//同步监视器:this
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":" + ticket);
            ticket--;
        }
    }
}
public class Window4 extends Thread {
    private static int ticket = 100;

    @Override
    public void run() {
        while (true) {
            show();
        }
    }

    private static synchronized void show () {//同步监视器:Window4.class
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":" + ticket);
            ticket--;
        }
    }
}

4.3. Design pattern: singleton pattern

// 饿汉式
class Bank {
	// 1、私有化类的构造器
	private Bank() {}
	// 2、内部创建类的对象
	// 4、要求此对象也必须声明为静态的
	private static Bank instance = new Bank();
	// 3、提供公共的静态的方法,返回类的对象
	public static Bank getInstance() {
		return instance;
	}
}

// 懒汉式方式一:同步方法
class Order {
	// 1、私有化类的构造器
	private Order() {}
	// 2、声明当前类对象,没有初始化
	// 4、此对象也必须声明为static的
	private static Order instance = null;
	// 3、声明public、static的返回当前类对象的方法
	public static synchronized Order getInstance() {
		if (instance == null) {
			instance = new Order();
		}
		return instance;
	}
}

// 懒汉式方式二:同步代码块
class Order {
	// 1、私有化类的构造器
	private Order() {}
	// 2、声明当前类对象,没有初始化
	// 4、此对象也必须声明为static的
	private static Order instance = null;
	// 3、声明public、static的返回当前类对象的方法
	public static Order getInstance() {
    // 方式一:效率稍差
    // synchronized (Order.class) {
    //     if (instance == null) {
    // 	    instance = new Order();
    //     }
    //     return instance;
    // }
    // 方式二:效率更高
    if (instance == null) {
      synchronized (Order.class) {
        if (instance == null) {
            instance = new Order();
        }
      }
    }
		return instance;
	}
}

4.4, thread deadlock problem

  • deadlock
    • Different threads occupy the synchronization resources needed by the other party and do not give up. They are all waiting for the other party to give up the synchronization resources they need, which forms a thread deadlock.
    • After a deadlock occurs, there will be no exception and no prompt, but all threads are blocked and cannot continue
  • Solution
    • specialized algorithms and principles
    • Minimize the definition of synchronization resources
    • Try to avoid nested synchronization

4.5, thread synchronization method three: Lock (lock)

class Window implements Runnable {
    private int ticket = 100;
    // 1、实例化ReentrantLock
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                // 2、调用锁定方法lock()
                lock.lock();

                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + ":" + ticket);
                    ticket--;
                } else {
                    break;
                }
            } finally {
                // 3、调用解锁方法:unlock()
                lock.unlock();
            }
        }
    }
}
  • Interview question: Similarities and differences between synchronized and lock?
    • The same: both can solve the thread safety problem
    • Different: the synchronized mechanism automatically releases the synchronization monitor after executing the corresponding synchronization code; Lock needs to manually start the synchronization (Lock()), and at the same time, the end of the synchronization also needs to be manually implemented (unlock())
  • Order of preference:
    • Lock --> Synchronization code block --> Synchronization method

5. Thread communication

  • Three methods involved:

    • wait(): Once this method is executed, the current thread enters a blocked state and releases the synchronization monitor
    • notify(): Once this method is executed, a thread that was wait() will be awakened. If multiple threads are waited on, wake up the thread with higher priority.
    • notifyAll(): Once this method is executed, all waited threads will be woken up
  • illustrate:

    • wait(), notify(), notifyAll() three methods must be used in synchronous code blocks or synchronous methods
    • The callers of wait(), notify(), and notifyAll() methods must be synchronous code blocks or synchronous monitors in synchronous methods, otherwise an IllegalMonitorStateException will occur
    • The three methods of wait(), notify(), and notifyAll() are defined in the java.lang.Object class
  • Interview question: similarities and differences between sleep() and wait()?

    • The same point: once the method is executed, the current thread can be blocked
    • difference:
      • 1. The positions of the two method declarations are different: sleep() is declared in the Thread class, and wait() is declared in the Object class
      • 2. The calling requirements are different: sleep() can be called in any required scenario. wait() must be used in a synchronized code block
      • 3. Regarding whether to release the synchronization monitor: if both methods are used in a synchronization code block or a synchronization method, sleep() will not release the lock, and wait() will release the lock

6. JDK5.0 adds thread creation method

6.1. New method 1: implement the Callable interface

  • Callable is more powerful than using Runnable
    • Compared with the run() method, it can have a return value
    • method can throw an exception
    • Support for generic return values
    • You need to use the FutureTask class, such as getting the return result
  • Future interface
    • You can cancel the execution results of specific Runnable and Callable tasks, query whether they are completed, obtain results, etc.
    • FutrueTask is the only implementation class of the Futrue interface
    • FutureTask also implements the Runnable and Future interfaces. It can be executed by a thread as a Runnable, and can also be used as a Future to get the return value of Callable
// 1、创建一个实现Callable的实现类
class NumThread implements Callable {
    // 2、实现call方法,将此线程需要执行的操作声明在call()中
    @Override
    public Object 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、创建Callable接口实现类的对象
        NumThread numThread = new NumThread();
        // 4、将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
        FutureTask futureTask = new FutureTask(numThread);
        // 5、将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
        new Thread(futureTask).start();

        try {
            // 6、获取Callable中call方法的返回值
            // get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值
            Object sum = futureTask.get();
            System.out.println("总和为:" + sum);
        } catch (ExecutionException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

6.2. New method 2: use thread pool

  • Background: resources that are frequently created and destroyed, and use a large amount of resources, such as threads in concurrent situations, have a great impact on performance
  • Idea: Create multiple threads in advance, put them into the thread pool, get them directly when using them, and put them back into the pool after use. It can avoid frequent creation and destruction and achieve reuse.
  • benefit:
    • Improved responsiveness (reduced time to create new threads)
    • Reduce resource consumption (reuse threads in the thread pool, do not need to create each time)
    • Easy thread management
      • corePoolSize: the size of the core pool
      • maximumPoolSize: maximum number of threads
      • keepAliveTime: When the thread has no tasks, it will be terminated after how long it will keep at most
  • ExecutorService: The real thread pool interface. Common subclass ThreadPoolExecutor
    • void execute(Runnable command): execute task/command, no return value, generally used to execute Runnable
    • Future submit(Callable task): Execute the task with a return value, generally used to execute Callable
    • void shutdown(): close the connection pool
  • Executors: Tool classes, factory classes for thread pools, used to create and return different types of thread pools
    • Executors.newCachedThreadPool(): Creates a thread pool that can create new threads as needed
    • Executors.newFixedThreadPool(n): Create a thread pool with a fixed number of reusable threads
    • Executors.newSingleThreadPool(): Create a thread pool with only one thread
    • Executors.newScheduledThreadPool(n): Creates a thread pool that can be scheduled to run commands after a given delay or periodically
public class ThreadPool {
    public static void main(String[] args) {
        // 1、提供指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        // 2、执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
        service.execute(new NumberThread()); // 适用于实现Runnable接口的线程
        service.submit(new NumberThread1()); // 适用于实现Callable接口的线
        // 3、关闭连接池
        service.shutdown();
    }
}

7. Additional: operations on locks

7.1. The operation of releasing the lock

  • The execution of the synchronization method and synchronization code block of the current thread ends
  • The current thread encounters break and return in the synchronization code block and synchronization method to terminate the execution of the code block and the method.
  • The current thread has an unhandled Error or Exception in the synchronization code block or synchronization method, resulting in an abnormal end
  • The current thread executes the wait() method of the thread object in the synchronization code block and synchronization method, the current thread pauses and releases the lock

7.2. Operations that do not release locks

  • When a thread executes a synchronization code block or a synchronization method, the program calls the Thread.sleep() and Thread.yield() methods to suspend the execution of the current thread
  • When a thread executes a synchronization code block, other threads call the suspend() method of the thread to suspend the thread, and the thread will not release the lock (synchronization monitor)
    • Should try to avoid using suspend () and resume () to control the thread

Two, Java common classes

Guess you like

Origin blog.csdn.net/qq_51808107/article/details/131436162