Basic Java advanced multi-threading concepts and their advantages, 2 ways to create threads, 3 ways to solve thread safety

1. Basic concepts

1.1. Program, process, thread

Program : It is a set of instructions written in a certain language to complete a specific task. It refers to a piece of static code , a static object.

Process : A running program . It is a dynamic process: it has its own process of emergence, existence and demise. --life cycle

Thread : A process can be further refined into a thread, which is an execution path within a program.

  • Threads are the unit of scheduling and execution. Each thread has an independent running stack core program counter (pc), and the overhead of thread switching is small.
image.png image.png

1.2. Single-core CPU and multi-core CPU

Single-core CPU: It is actually a kind of fake multi-threading, because only one thread can execute the task in one time unit. But because the CPU time unit is very short, it cannot be felt.

Multi-core CPU: can better utilize the efficiency of multi-threading.

In addition, 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.3. Parallelism and concurrency

Parallelism : Multiple CPUs perform multiple tasks at the same time. For example: multiple people doing different things at the same time.

Concurrency : One CPU (using time slice strategy) executes multiple tasks at the same time. For example: 618, double 11 flash sale activities; multiple people doing the same thing.

1.4. Advantages of multi-threading

  1. Improve application responsiveness. It is more meaningful for graphical interfaces and can enhance user experience.
  2. Improve computer system CPU utilization
  3. Improve program structure. Divide long and complex processes into multiple threads and run them independently to facilitate understanding and modification.

1.5. When to use multi-threading

  1. The program needs to perform two or more tasks simultaneously
  2. 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.
  3. When you need some programs running in the background.

2. Creation and use of threads

2.1. Method 1 of creating threads – inherit the Thread class

Four steps:

1.创建一个继承于 Thread类的子类
2.重写Thread类的run() --> 将此线程执行的操作声明在run()3.创建Thread类的子类的对象
4.通过此对象调用start() --> ① 启动当前线程 ② 调用当前线程的run()

for example:

method one

public class ThreadDemo {
    
    
    public static void main(String[] args) {
    
    
        MyThread1 thread1 = new Mythread1();
        thread1.start();

        Mythread2 thread2 = new Mythread2();
        thread2.start();
    }
}

// 方法一
class Mythread1 extends Thread{
    
    
    @Override
    public void run() {
    
    
        //遍历100以内的奇数
        for (int i=0;i<100;i++){
    
    
            if (i%2==0){
    
    
                System.out.println(Thread.currentThread().getName() + ":"+i);
            }
        }
    }
}

class Mythread2 extends Thread{
    
    
    @Override
    public void run() {
    
    
        //遍历100以内的偶数
        for (int i=0;i<100;i++){
    
    
            if (!(i%2==0)){
    
    
                System.out.println(Thread.currentThread().getName() + ":"+i);
            }
        }
    }
}

Method 2: Use an anonymous subclass of the thread class

public class threadDemo {
    
    
    public static void main(String[] args) {
    
    
        //方法二
        //创建thread类的匿名子类的方式
        new Thread(){
    
    
            public void run() {
    
    
                //遍历100以内的偶数
                for (int i=0;i<100;i++){
    
    
                    if (i%2==0){
    
    
                        System.out.println(Thread.currentThread().getName() + ":"+i);
                    }
                }
            }
        }.start();

        new Thread(){
    
    
            public void run() {
    
    
                //遍历100以内的奇数
                for (int i=0;i<100;i++){
    
    
                    if (!(i%2==0)){
    
    
                        System.out.println(Thread.currentThread().getName() + ":"+i);
                    }
                }
            }
        }.start();

    }
}

2.2. Method 2 of creating threads – implementing the Runnable interface

The second way to create multi-threads: implement the Runnable interface

five steps

 1.创建一个实现了Runnable接口的类
 2.实现类去实现实现Runnable中的抽象方法:run()
 3.创建实现类的对象
 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
 5.通过Thread类的对象调用start()

Example:

//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(Thread.currentThread().getName()+":"+i);
            }
        }
    }
}
public class ThreadTest1 {
    
    
    public static void main(String[] args) {
    
    
        //3.创建实现类的对象
        MThread m1 = new MThread();
        //4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
        Thread t1 = new Thread(m1);
        t1.setName("线程1");

        //5.通过Thread类的对象调用start():
        t1.start();
        //再创建一个线程,遍历100以内的偶数
        Thread t2 = new Thread(m1);
        t2.setName("线程2");
        t2.start();
    }
}

2.3. Comparison of two methods

Compare the two ways of creating threads.

Under development: Priority: the way to implement the Runnable interface

reason

  1. The implementation does not have the limitations of single inheritance of classes
  2. The implementation method is more suitable for handling situations where multiple threads share data.

联系:public class Thread implements Runnable

Similarity: Both methods need to rewrite run() and declare the logic to be executed by the thread in run().

2.2. Rename threads

2.2.1. Method 1 – setName()

HelloThread h1 = new HelloThread();
h1.setName("线程一");
h1.start();

2.2.2. Method 2 – Constructor

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

    //通过构造器的方式给线程命名
    public HelloThread(String name){
    
    
        super(name);
    }
}


public static void main(String[] args) {
    
    
        HelloThread h1 = new HelloThread("Thread: 1");
        h1.start();
        }
    }

2.3. Commonly used methods

1.start(): Start the current thread; call run() of the current thread

2.run(): Usually you need to override this method in the Thread class and declare the operations to be performed by the created thread in this method.

3.currentThread(): Static method, returns the thread that executes the code in the schedule

4.getName(): Get the name of the current thread

System.out.println(Thread.currentThread().getName());

5.setName(): Set the name of the current thread

h1.setName("线程一");
//给主线程命名
Thread.currentThread().setName("主线程");

6.yield(): Release the execution rights of the current CPU

if (i%20==0){
    
    
    this.yield();
}

7.join(): Call join() of thread b in thread a. At this time, thread a enters the blocking state. It is not until thread b finishes executing that thread a ends the blocking state.

if (i == 20){
    
    
    try {
    
    
        h1.join();
    }catch (InterruptedException e){
    
    
        e.printStackTrace();
    }
}

8. stop(): Obsolete. When this method is executed, the current thread is forced to end

9.sleep(long millitime): Let the current thread "sleep" for the specified millitime milliseconds. Within the specified millitime milliseconds, the current thread is blocked.

 //单位是 毫秒,如果睡1秒,就要写1000
sleep(1000);

10.isAlive(): Determine whether the current thread is alive

System.out.println(h1.isAlive());

2.4. Thread priority

MAX_PRIORITY = 10
MIN_PRIORITY = 1
NORM_PRIORITY = 5

2. How to get and set the priority of the current thread:

  • getPriority():获取线程的优先级
    setPriority(int p):设置线程的优先级
    

Note: High-priority threads must seize the execution rights of low-priority CPUs. But just speaking from the probability mountain, high-priority threads are executed with high probability. It does not mean that the low-priority thread will execute only after the high-priority thread has finished executing.

3. Thread life cycle

image.png image.png

4. Thread synchronization

4.1. Solution to thread safety method 1 – synchronized code block

例子:创建三个窗口卖票,总票数为100张。使用实现Runnable接口的方式
* 1.问题:卖票过程中,出现了重票、错票-->出现了线程的安全问题
* 2.问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票
* 3.如何解决: 当一个线程a在操作ticket的时候,其他线程不能参与进来。
*            直到线程a操作完ticket时,其他线程才可以开始操作ticket.
*            这种情况即使贤臣和a出现了阻塞,也不能被改变。
*4.在Java中,我们通过同步机制,来解决线程的安全问题
* 方式一:同步代码块
*  synchronized(同步监视器){
    
    
*      //需要被同步的代码
*  }
*  说明:
*  1.操作共享数据的代码,即为需要被同步的代码
*  2.共享数据:多个线程共同操作的变量。比如: ticket就是共享数据。
*  3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
*      要求:多个线程必须要共用同一把锁。

*	补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器;
	     在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器。
*
* 方式二:同步方法
*	关于同步方法的总结:
	1.同步方法仍然涉及到同步监视器,只是不需要我们显式的声明
	2.非静态的同步方法,同步监视器是 this
  	  静态的同步方法,同步监视器是:当前类本身 如:Window2.class
* 5.同步的方式,解决了线程的安全问题。---好处
*  操作同步代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低。---局限性

4.2. Solution to thread safety method 2 – synchronization method

关于同步方法的总结:
1.同步方法仍然涉及到同步监视器,只是不需要我们显式的声明
2.非静态的同步方法,同步监视器是 this
  静态的同步方法,同步监视器是:当前类本身 如:Window2.class

4.3. Thread-safe lazy singleton mode

/**
 * 将懒汉式的单例模式改为线程安全的
 */
class Bank{
    
    
    //1.私有化类的构造器
    private Bank(){
    
      }
    //2.声明当前对象,没有初始化
    //4.要求此对象也必须是static的
    private static Bank instance = null;
    //3.声明 public、static的返回当前类对象的方法
    public static Bank getInstance(){
    
    
        //方式一:效率稍差
//        synchronized (Bank.class){
    
    
//            if (instance ==null){
    
    
//                instance = new Bank();//实例化一下
//            }
//            return instance;
//        }
        //方式二: 效率更高
        if (instance == null){
    
    
            synchronized (Bank.class){
    
    
                if (instance == null){
    
    
                    instance = new Bank();
                }
            }
        }
        return instance;
    }
}

4.4. Deadlock

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

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

Solution

  • Special algorithms and principles
  • Minimize synchronization resource definitions
  • Try to avoid nested synchronization

Demonstrate the process of deadlock

package 20230424;

/**
 * @author: Arbicoral
 * @create: 2023-04-24 10:44
 * @Description: 演示线程的死锁问题
 */
public class ThreadTest {
    
    
    public static void main(String[] args) {
    
    
        //常用类,一个特别的字符串
        StringBuffer s1 = new StringBuffer();
        StringBuffer s2 = new StringBuffer();
        new Thread(){
    
    //使用Thread的匿名子类的方式创建多线程
            @Override
            public void run() {
    
    
                synchronized (s1){
    
    
                    s1.append("a");
                    s2.append("1");
                    try {
    
    
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
    
    
                        throw new RuntimeException(e);
                    }
                    synchronized(s2){
    
    
                        s1.append("b");
                        s2.append("2");

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

        new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                synchronized (s2){
    
    
                    s1.append("c");
                    s2.append("3");
                    try {
    
    
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
    
    
                        throw new RuntimeException(e);
                    }

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

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

4.5. Solution to thread safety method three – ReentrantLock lock

解决线程安全问题的方式三:Lock锁  ---JDK5.0新增

step:

1.实例化ReentrantLock
2.调用锁定方法lock()  -- 相当于同步监视器
3.调用解锁的方法 unlock()


举例:
package cs20230424;
class Window implements Runnable{
    
    
    private int ticket = 100;
    //1.实例化ReentrantLock
    private ReentrantLock lock = new ReentrantLock();//true:公平的Lock,先进先出
    @Override
    public void run() {
    
    
        while (true){
    
    
            try {
    
    

                //2.调用锁定方法lock()  -- 相当于同步监视器
                lock.lock();

                if (ticket > 0){
    
    

                    try {
    
    
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
    
    
                        throw new RuntimeException(e);
                    }
                    System.out.println(Thread.currentThread().getName()+"售票,票号为"+ticket);
                    ticket--;
                }else {
    
    
                    break;
                }
            }finally {
    
    
                //3.调用解锁的方法 unlock()
                lock.unlock();
            }
        }
    }
}

Classic interview question: What are the similarities and differences between synchronzied and Lock?

Similarity: They are all to solve thread safety issues

difference:

​ ① Lock requires manual startup of synchronization (lock()), and ending synchronization also requires manual implementation (unlock())

The synchronzied mechanism automatically releases the synchronization monitor after executing the corresponding synchronization code.

Priority order of use:

Lock --> Synchronized code block (has entered the method body and allocated corresponding resources) --> Synchronized method (outside the method body)

4.6. Thread communication

Example of thread communication

package 20230424;

/**
 * @author: Arbicoral
 * @create: 2023-04-24 15:46
 * @Description: 线程通信的例子:使用两个线程打印1-100。线程1,线程2 交替打印
 * 涉及到的三个方法:
 *          wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
 *          notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的线程
 *          notifyAll():一旦执行此方法,就会唤醒被wait的所有线程。
 *
 *
 *          说明:
 *          1.wait(), notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中
 *          2.wait(), notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器
 *               否则会报 IllegalMonitorStateException 异常
 *          3.wait(), notify(),notifyAll()三个方法是定义在java.Object类中的
 */

class Number implements Runnable{
    
    
    private int num = 1;//共享数据
    Object object = new Object();

    @Override
    public void run() {
    
    
        while (true){
    
    
            synchronized (object){
    
    //同步代码块

                this.notify();//唤醒进程
//                notifyAll();//唤醒所有的进程
                if (num <=100){
    
    

                    try {
    
    
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
    
    
                        throw new RuntimeException(e);
                    }

                    System.out.println(Thread.currentThread().getName()+":"+num);
                    num++;

                    try {
    
    
                        //使得调用如下wait()方法的线程进入阻塞状态
                        wait();
                    } catch (InterruptedException e) {
    
    
                        throw new RuntimeException(e);
                    }

                }else {
    
    
                    break;
                }
            }
        }
    }
}
public class CommunicationTest {
    
    
    public static void main(String[] args) {
    
    
        Number number = new Number();
        Thread t1 = new Thread(number);
        Thread t2 = new Thread(number);

        t1.setName("线程1");
        t2.setName("线程2");

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

Classic interview question: What are the similarities and differences between sleep() and wait() ?

1. Similarity: Once the method is executed, the current thread can enter the blocking state.

2. Differences:

  • The two methods are declared in different locations: sleep() is declared in the Thread class, and wait() is declared in the Object class.
  • The scope of the call is different: sleep() can be called in any required scenario. wait() must be used in synchronized code blocks
  • Regarding whether to release the synchronization monitor: If both methods are used in a synchronized code block or a synchronized method, sleep() will not release the lock, but wait() will release the lock (synchronization monitor)

5. New thread creation method in JDK5.0

5.1. The third way to create a thread – implement the Callable interface

-----Belongs to JKD5.0 new addition

Compared with using Runnable, Callable is more powerful

  • Compared with the run() method, call() can have a return value
  • The call() method can throw an exception and be captured by external operations to obtain exception information.
  • Callable supports generic return values
  • You need to use the FutureTask class, such as getting the return results
创建线程的方式三--实现Callable接口的步骤:
1.创建一个实现Callable的实现类
2.实现call方法,将此线程需要执行的操作声明在call()3.创建Callable接口实现类的对象
4.将此Callable接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask的对象
5.FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
6.获取Callable中call方法的返回值

```java
package 20230424;

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

/**
 * @author: Arbicoral
 * @create: 2023-04-24 18:16
 * @Description: 创建线程的方式三--实现Callable接口  -----属于JKD5.0新增
 */

//1.创建一个实现Callable的实现类
class NumThread implements Callable{
    
    
    //2.实现call方法,将此线程需要执行的操作声明在call()中
    @Override
    public Object call() throws Exception {
    
    //相当于run()
        //遍历100以内的偶数,并返回sum
        int sum=0;
        for (int i=1;i<=100;i++){
    
    
            if (i%2==0){
    
    
                System.out.println(i);
                sum += i;
            }
        }
        return sum;//sum是基本数据类型,这里包含多态,将int转为integer,再赋给Object
    }
}
public class ThreadNew {
    
    
    public static void main(String[] args) {
    
    
        //3.创建Callable接口实现类的对象
        NumThread thread = new NumThread();
        //4.将此Callable接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask的对象
        FutureTask futureTask = new FutureTask(thread);
        //5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
        new Thread(futureTask).start();

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

5.2. Method 4 of creating threads – using thread pool

Background : Resources that are frequently created and destroyed and used extremely heavily, such as threads in concurrent situations, have a great impact on performance.

Idea : Create multiple threads in advance, put them into the thread pool, obtain them directly when used, and put them back into the pool after use. It can avoid frequent creation and destruction and realize reuse. Similar to public transportation in life.

Benefits :

  1. Improved responsiveness (reduced time to create new threads)
  2. Reduce resource consumption (reuse threads in the thread pool, no need to create them every time)
  3. Facilitates thread management
  • corePoolSize:The size of the core pool
  • maximumPoolSize: Maximum number of threads
  • keepAliveTime: When the thread has no tasks, the maximum length of time it will last before terminating.

Steps to create threads using thread pool:

1.提供指定线程数量的线程池
2.执行指定的线程的操作,需要提供实现Runnable接口或Callable接口实现类
3.关闭连接池 service.shutdown()

for example

package 20230424;

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

/**
 * @author: Arbicoral
 * @create: 2023-04-24 18:41
 * @Description: 创建线程的方式四:使用线程池
 */

class NumberThread implements Runnable{
    
    

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

class NumberThread1 implements Runnable{
    
    

    @Override
    public void run() {
    
    
        for (int i=1;i<=100;i++){
    
    
            if (i%2!=0){
    
    
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
}
public class ThreadPool {
    
    
    public static void main(String[] args) {
    
    
        //1.提供指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);

        //设置线程池的属性
        //强转
        ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
        service1.setCorePoolSize(15);
//        service1.setMaximumPoolSize();

        //2.执行指定的线程的操作,需要提供实现Runnable接口或Callable接口实现类
        service.execute(new NumberThread());//适合适用于Runnable
        service.execute(new NumberThread1());//适合适用于Runnable
//        service.submit(Callable callable);//适合使用于Callable
        //3.关闭连接池
        service.shutdown();
    }
}

5.3. Thread pool related APIs

JDK 5.0 provides thread pool related APIs: ExecutorService and Executors

ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor 
 - void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行 Runnable 
 - Future submit(Callable task):执行任务,有返回值,一般又来执行 Callable 
 - void shutdown() :关闭连接池 


Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池 
 - Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池 
 - Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池 
 - Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
 - Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

Guess you like

Origin blog.csdn.net/Miss_croal/article/details/132952888