Java Notes-Multithreading

Multithreading

1. Understand multithreading

Multi-process : Each program is a process. The operating system can execute multiple programs at the same time. The purpose of multi-process is to effectively use CPU resources. Every time a process is opened, the system must allocate the system resources (memory resources) it needs for this process.

Multithreading : A thread is a smaller execution unit inside a process. Each thread completes a task. Each process contains multiple threads. Each thread does its own thing. All threads in the process share the program resources.

Main thread : There is at least one main thread in the process, and other sub-threads are started by the main thread. The main thread does not necessarily end after other threads end, but may end before other threads end. The main thread in JAVA is the main function

Concurrency: Refers to multiple programs competing for the CPU in a preemptive manner

Parallelism: multiple programs are executed at the same time

Synchronization: one program can execute another process

2. Java implements multi-threaded operations

2.1 Inherit the Thread class to implement multi-threading

After inheriting the java.lang.Thread class, this class is a thread class, which is an independent execution unit. The thread code must be written in the run()method. run()The method is defined by the Thread class. The thread we write must rewrite run()the method. .

run()The method is a thread method, which does not allow manual calls.
If run()the method is called manually, it becomes a method called inside the thread, and the method is automatically called by the calling thread.

To start a thread, you need to call start()the method, which will automatically call a new thread and automatically execute the content in the run method

thread class

/**
 * 当一个类继承了Thread类,该类就成了一个线程类
 * 该类必须重写Thread中的run()方法
 */
public class Demo01 extends  Thread{
    
    
    /**
     * run方法不允许手动调用
     * 当开启一个线程后,该方法会自动执行
     */
    @Override
    public void run() {
    
    
        for (int i=0;i<10;i++){
    
    
            System.out.println(Thread.currentThread().getName()+"0:"+i);
        }
    }
}

main

public class Test {
    
    
    public static void main(String[] args) {
    
    
        Demo01 demo01=new Demo01();
        //run方法为线程方法,该方法不允许手动调用
        //如果手动调用run()方法就成了线程内部调用一个方法,手动调用该方法就会被自动归入到调用线程
//        demo01.run();
        //开启线程,开启后自动执行run()方法
        demo01.start();
        for (int i=0;i<10;i++){
    
    
            System.out.println(Thread.currentThread().getName()+"1:"+i);
        }
    }
}

insert image description here

2.2 Implement the Runable interface to realize multi-threading

When a class implements the Runable interface, the class is a thread task class

The thread task class only defines the tasks to be performed by the thread

Thread tasks need to submit thread tasks to thread objects for execution

/**
 * 当一个类实现了Runable接口该类就是一个线程任务类
 * 线程任务类只定义线程要执行的任务
 * 线程任务需要将线程任务提交给线程对象执行
 */
public class Demo01 implements Runnable {
    
    

    @Override
    public void run() {
    
    
        for (int i=0;i<10;i++){
    
    
            System.out.println(Thread.currentThread().getName()+"1:"+i);
        }
    }
public class Test {
    
    
    public static void main(String[] args) {
    
    
        //创建线程任务对象
        Demo01 demo01=new Demo01();
        //将线程任务对象交给线程对象,并启动线程
        new Thread(demo01).start();

        for (int i=0;i<10;i++){
    
    
            System.out.println(Thread.currentThread().getName()+"1:"+i);
        }
    }
}

The implementation of the Runable interface is generally used in this way

/**
 * 一般使用
 */
public class Test01 {
    
    
    public static void main(String[] args) {
    
    
        //创建一个线程类对象并启动线程
        new Thread(() -> {
    
    
            for (int i=0;i<10;i++){
    
    
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        },"T0").start();
        new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                for (int i=0;i<10;i++){
    
    
                    System.out.println(Thread.currentThread().getName()+":"+i);
                }
            }
        },"T1").start();
        new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                for (int i=0;i<10;i++){
    
    
                    System.out.println(Thread.currentThread().getName()+":"+i);
                }
            }
        },"T2").start();

        for (int i=0;i<10;i++){
    
    
            System.out.println(Thread.currentThread().getName()+"1:"+i);
        }
    }
}

2.3 Realize multithreading by implementing the Callable interface

Both Callable and Runable are used to create thread tasks

difference

run()① Runnable creates a thread task through a method, and the result cannot be returned after the method is executed. If you need to get the result, you need to declare a global variable to obtain it.

​ Callable call()creates a thread task through a method, which can directly return a result of the type specified by the generic type after execution

② The method in Runnable run()will not throw an exception

Methods in Callable call()can throw exceptions

③Runnable can execute thread tasks directly through the Thread object

Callable defines thread tasks and requires FutureTask (future task class) or ExecutorService to execute thread tasks

FutureTask class

  • FutureTask is a class implemented by the Future interface

  • Future represents the future result of asynchronous calculation (processing | control of the thread). After the thread processing is completed, the result is finally stored in the Future. The Future interface is an ideal choice for asynchronous processing of long-running methods. This allows us to perform some other stuff while waiting for the task encapsulated by the Future to complete.

    • Five methods are provided in Future

      cancel(boolean mayInterruptIfRunning): used to stop thread execution, if the task can be stopped normally, return true, if the task has been completed or cannot be stopped, return false; ②: judge
      whether isCancelled()the task is cancelled, if it is canceled before completion, return true, Callable thread task, although submitted to FutureTask task, but starting and executing the thread still depends on the Thread object
      get(): Obtain the result of the thread task execution (for the Callable task), if it is not completed, it will enter the blocked state;
      the thread enters the dead state
      get(long timeout,TimeUnit unit): Obtain the task result within the specified time ;
      isDone(): Determine whether the task is completed, and return true if completed;

      /**
       * 线程任务类
       */
      public class Demo01 implements Callable<Integer> {
              
              
          @Override
          public Integer call() throws Exception {
              
              
              int sum=0;
              for (int i=0;i<100;i++){
              
              
                  sum+=i;
                  System.out.println(Thread.currentThread().getName()+":"+i);
                  Thread.sleep(1000);
              }
              return sum;
          }
      }
      
      public class Demo01Test  {
              
              
          public static void main(String[] args) throws InterruptedException {
              
              
              //创建demo01对象
              Demo01 demo01=new Demo01();
              //创建FutureTask对象,并将callableTask对象传入
              FutureTask  futureTask=new FutureTask<>(demo01);
              //执行线程任务
              Thread t1=new Thread(futureTask);
              t1.start();//启动现场
              /**
               * 循环输出主线程内容,并在适合位置取消线程任务
               */
              for (int i=0;i<100;i++){
              
              
                  if (i==20){
              
              
                      futureTask.cancel(true);//取消线程任务
                      break;
                  }
                  System.out.println(Thread.currentThread().getName()+":"+i);
                  try {
              
              
                      Thread.sleep(100);
                  } catch (InterruptedException e) {
              
              
                      e.printStackTrace();
                  }
              }
              if (futureTask.isCancelled()){
              
              //判断线程是否取消
                  System.out.println("任务已经取消...");
              }
              while (!futureTask.isDone()){
              
              
                  System.out.println("任务执行完成...");
                  try {
              
              
                      //获得线程任务执行结果,未完成进入堵塞状态
                      System.out.println("结果为:"+futureTask.get());
                  } catch (Exception e) {
              
              
                      e.printStackTrace();
                  }
                  System.out.println("任务结束...");
                  Thread.sleep(100);
              }
          }
      }
      

PS: To cancel the thread, only the execution of the thread code is completed

The method of execution end:

  • Normal end: Execution of all codes in the thread is completed
  • Abnormal end: Find a way to let the code in the thread "interrupt" the code in the thread, InterruptedException (interrupted exception)
    • The cancel in FutureTask is to cancel the interruption in the thread through abnormal interruption
    • Let the code in the thread end when a certain point is reached

Method of the Thread class:
interrupt()send an interrupt signal
isInterrupted()to determine whether the current thread is interrupted

2.4 Implement multithreading through thread pool

The thread pool is a container for loading threads (Map collection implementation)

  • When the thread pool is initialized, several threads (thread initialization size <core thread>) objects are automatically created and put into the thread pool for use

  • When a program needs to use a thread, it does not need to create a thread object separately, but obtains an idle thread object from the thread pool to use

  • If there is no idle thread in the thread pool, the thread pool will automatically generate new threads for the program to use according to the defined rules. When a thread is executed, the thread pool will mark the thread as idle and wait for other programs to use.

  • If the created thread object reaches the upper limit of the thread pool, the newly requested thread task will be stored in a waiting queue, waiting for the thread in the thread pool to be free.

  • There are core threads and non-core threads in the thread pool. Core threads will not be released after use, while non-core threads will be used after use

Java obtains the thread pool object through the Executors class

newSingleThreadExecutor(): Create a single-threaded thread object

  • which contains only one thread
public class SingleThreadDemo01 {
    
    
    public static void main(String[] args) {
    
    
        /**
         * 创建线程对象并返回一个ExecutorService对象
         * ExecutorService用于管理线程池,并向线程池提交任务
         * ExecutorService常用方法:
         *     ① shutdown():关闭线程池,平滑关闭,该方法执行后不再接受新线程任务,将已提交的线程任务结束后关闭线程池
         *     ② shutdownNow():立即关闭线程池,该方法执行后不再接受新线程任务,如果当前已提交线程任务未执行完,也立即关闭
         *     ③ submit():向线程池中提交线程任务,
         *     当一个线程任务被提交到线程池中,线程池会找到一个空闲线程执行该任务
         *     如果不存在空闲线程则等待
         */
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        //提交线程任务
        executorService.submit(new Callable<Integer>() {
    
    
            @Override
            public Integer call() throws Exception {
    
    
                for (int i = 0; i < 100; i++) {
    
    
                    System.out.println(Thread.currentThread().getName()+":"+i);
                }
                return null;
            }
        });
        executorService.shutdown();
    }
}

newFixedThreadPool(int nThreads): Create a thread pool object with a fixed number of threads

  • The thread pool will create a certain number of thread objects when it is initialized, and the thread objects will not increase or decrease during use
  • Applicable to scenarios with a relatively fixed number of threads
public class FixedThreadDemo01 {
    
    
    public static void main(String[] args) {
    
    
        /**
         * 创建线程对象并返回一个ExecutorService对象
         * ExecutorService用于管理线程池,并向线程池提交任务
         * ExecutorService常用方法:
         *     ① shutdown():关闭线程池,平滑关闭,该方法执行后不再接受新线程任务,将已提交的线程任务结束后关闭线程池
         *     ② shutdownNow():立即关闭线程池,该方法执行后不再接受新线程任务,如果当前已提交线程任务未执行完,也立即关闭
         *     ③ submit():向线程池中提交线程任务,
         *     当一个线程任务被提交到线程池中,线程池会找到一个空闲线程执行该任务
         *     如果不存在空闲线程则等待
         */
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 5; i++) {
    
    
        //提交线程任务
        executorService.submit(new Callable<Integer>() {
    
    
            @Override
            public Integer call() throws Exception {
    
    
                for (int i = 0; i < 100; i++) {
    
    
                    System.out.println(Thread.currentThread().getName()+":"+i);
                }
                return null;
            }
        });
        }
        executorService.shutdown();
    }
}

newCachedThreadPool(): Create a thread pool with a buffer

  • Create a certain number of thread objects when the thread pool is initialized, and dynamically increase or decrease the number of threads in the thread pool during use
  • Adapt to scene –> None (never use)
  • The thread pool with a buffer supports a maximum number of threads of 2.1 billion threads (easy to exhaust resources)
public class CachedThreadDemo01 {
    
    
    public static void main(String[] args) {
    
    
        /**
         * 创建线程对象并返回一个ExecutorService对象
         * ExecutorService用于管理线程池,并向线程池提交任务
         * ExecutorService常用方法:
         *     ① shutdown():关闭线程池,平滑关闭,该方法执行后不再接受新线程任务,将已提交的线程任务结束后关闭线程池
         *     ② shutdownNow():立即关闭线程池,该方法执行后不再接受新线程任务,如果当前已提交线程任务未执行完,也立即关闭
         *     ③ submit():向线程池中提交线程任务,
         *     当一个线程任务被提交到线程池中,线程池会找到一个空闲线程执行该任务
         *     如果不存在空闲线程则等待
         *
         *  初始化线程对象数为0
         *  最大线程数:int类型最大值21亿多
         */
        ExecutorService executorService = Executors.newCachedThreadPool();
        //提交线程任务
        for (int i = 0; i < 50; i++) {
    
    
            executorService.submit(new Callable<Integer>() {
    
    
                @Override
                public Integer call() throws Exception {
    
    
                    for (int i = 0; i < 100; i++) {
    
    
                        System.out.println(Thread.currentThread().getName() + ":" + i);
                    }
                    return null;
                }
            });
        }
        executorService.shutdown();
    }
}

④Create newScheduledThreadPool(int corePoolSize):a periodic thread pool

  • The thread pool is mainly used to execute periodic tasks (similar to timers, executed at regular intervals)

    public class ScheduledThreadDemo01 {
          
          
        public static void main(String[] args) {
          
          
            /**
             * 创建线程对象并返回一个ExecutorService对象
             * ExecutorService用于管理线程池,并向线程池提交任务
             * ExecutorService常用方法:
             *     ① shutdown():关闭线程池,平滑关闭,该方法执行后不再接受新线程任务,将已提交的线程任务结束后关闭线程池
             *     ② shutdownNow():立即关闭线程池,该方法执行后不再接受新线程任务,如果当前已提交线程任务未执行完,也立即关闭
             *     ③ submit():向线程池中提交线程任务,
             *     当一个线程任务被提交到线程池中,线程池会找到一个空闲线程执行该任务
             *     如果不存在空闲线程则等待
             */
            ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
            //提交线程任务
            /**
             * 执行周期性任务线程
             * 参数1:线程任务(Runnable任务)不能执行Callable任务
             * 参数2:延迟时间,负数表示不延迟
             * 参数3:间隔时间量
             * 参数4:时间单位,该时间单位用于设置延迟时间和间隔时间的单位
             */
                scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
          
          
                    @Override
                    public void run() {
          
          
                        System.out.println("执行周期性任务...");
                    }
                },0,1000, TimeUnit.MILLISECONDS);
        }
    }
    

Custom thread pool: recommended

public class 自定义ThreadPool {
    
    
    public static void main(String[] args) {
    
    
        /**
         * 使用自定义线程池对象
         * 参数1:初始化线程数(核心线程数,不会被销毁)
         * 参数2:最大线程数
         * 参数3:空闲时间,当一个非核心线程允许空闲的最大时间,如果空闲时间大于该时间则自动销毁
         * 参数4:空闲时间时间单位
         * 参数5:等待队列(阻塞队列)
         * 参数6:设置拒绝策略,当线程池无法接受新提交线程任务时,应该采取的措施
         *      拒绝策略(4种):
         *    new ThreadPoolExecutor.AbortPolicy()异常策略,当有新线程任务提交后会报拒绝异常(默认)
         *    new ThreadPoolExecutor.CallerRunsPolicy()谁提交谁执行策略,线程池已满,该策略将线          * 程给提交者执行
         *    new ThreadPoolExecutor.DiscardPolicy()丢弃策略,当新任务提交后,线程池无法满足,直          *接将新提交任务丢弃
         *     new ThreadPoolExecutor.DiscardOldestPolicy()丢弃策略,丢弃等待时间最久的任务
         */
        //创建一个等待队列对象
        ArrayBlockingQueue queue = new ArrayBlockingQueue<>(2);
        ExecutorService executorService = new ThreadPoolExecutor(3,
                5,
                1000L,
                TimeUnit.MILLISECONDS, queue);
        for (int i = 0; i < 5; i++) {
    
    
            executorService.submit(new Callable<Object>() {
    
    
                @Override
                public Object call() throws Exception {
    
    
                    for (int i = 0; i < 100; i++) {
    
    
                        System.out.println(Thread.currentThread().getName() + ":" + i);
                    }
                    return null;
                }
            });
        }
        executorService.shutdown();
    }
}

Question: When the number of threads in the thread pool has reached the maximum number of threads and the waiting queue is full, what should I do if I want to submit the thread?

If the number of threads has reached the maximum number of threads and the waiting area is full, if there is no measure to deal with it, a new thread submission will report an exception RejectedExecutionException

This situation can be handled by setting a "deny policy"

Rejection policy (4 types):
new ThreadPoolExecutor.AbortPolicy() exception policy, when a new thread task is submitted, a rejection exception will be reported (default)
new ThreadPoolExecutor.CallerRunsPolicy() who submits the policy, the thread pool is full, the policy will The thread executes the new ThreadPoolExecutor.DiscardPolicy() discard policy for the submitter
. When the new task is submitted, the thread pool cannot satisfy it, and the newly submitted task is directly discarded. The
new ThreadPoolExecutor.DiscardOldestPolicy() replacement policy replaces the task with the longest waiting time

3 thread state

3.1 Operating system defines thread state

  1. New : When an object of the Thread class or its subclass is created, the new thread object is in the newly created state. At this point, it already has the corresponding memory space and other resources, and the thread will not be executed in the newly created state;

  2. Ready : When the thread object is created, other threads call his start()method, and the thread enters the ready state, and the thread in this state is located in the runnable pool (thread pool) , waiting for the right to use the CPU;

  3. Blocking : Five reasons Blocking: The current thread gives up the CPU and lets other threads get

    • Blocked state in the object waiting pool: When the thread is running, if a method of an object is executed wait(), the JVM will put the thread in the waiting pool

    • Blocked state in the object lock pool: When a thread is running and tries to acquire a synchronization lock of an object, if the synchronization lock of the object is called by other threads, the JVM will put the thread into the object lock pool

    • Issue an I/O request

    • join()Call methods from other threads

    • execution sleep(int millsecond)method

  4. Running : the thread in this state occupies the CPU and executes the program code;

  5. Death : Enter this state after the execution of the thread program ends;

start()
获得CPU资源
时间片完
阻塞解除,重新进入就绪队列,抢占CPU资源
阻塞事件的发生
代码运行完毕,线程结束
新建
就绪
执行
阻塞
死亡

3.2 Thread state defined by JVM

  1. New state (NEW): The thread object has been created, but start()the method has not been executed;

  2. Runnable state (RUNNABLE): It has been running in the JVM, but has not obtained CPU resources;

  3. Blocked state-blocked state waiting for object lock (BLOCKED): When a running thread tries to obtain a resource locked by other threads, it is placed in the object lock pool;

    Blocking state - waiting for blocking state in the pool (WAITTING): when a thread calls a wait()method, it automatically enters this state;

    Blocking state - time-limited blocking state (TIMED_WAITING): Threads in this state will automatically return to RUNNABLE after the waiting time is up, sleep(1000), wait(100);

  4. End state TERMINATED: enter this state after the execution of the thread program ends;

start()
阻塞事件的发生
阻塞事件的解除
代码运行完毕,线程结束
新建NEW
可运行状态RUNNABLE
阻塞状态BLOCKED,WAITING,TIMRD_WAITIND
死亡TERMINATED

4 thread scheduling

Thread scheduling refers to allowing the executing thread to give up the CPU through certain operations and enter the blocked state

Scheduling of the operating system: scheduling according to certain scheduling rules, the randomness is too high, uncertain, I/O requests

  1. wait(): thread waiting (the thread will be put into the waiting pool, waiting to wake up);

  2. wait(lang time): Wait for a limited time, if it is not woken up within the limited time, it will automatically wake up

  3. notify(): Wake up a waiting thread

  4. notifyAll(): Wake up all waiting threads

  5. sleep(): Thread sleep, let the specified thread sleep for a certain period of time (in milliseconds) and then wake up automatically. Once this method is executed, the thread will automatically give up the CPU and enter the blocked state until it enters the ready state after sleeping and waits for CPU resources.

    public class sleepDemo {
          
          
     public static void main(String[] args) {
          
          
         new Thread(new Runnable() {
          
          
             @Override
             public void run() {
          
          
                 for (int i=0;i<100;i++){
          
          
                     System.out.println(Thread.currentThread().getName()+":"+i);
                     try {
          
          
                         Thread.sleep(10);//休眠10ms
                     } catch (InterruptedException e) {
          
          
                         throw new RuntimeException(e);
                     }
                 }
             }
         }).start();
         for (int i=0;i<100;i++){
          
          
             System.out.println(Thread.currentThread().getName()+":"+i);
         }
      }
    }
    
  6. join(): Thread connection, waiting thread, after calling the join method of another thread in one thread, the thread will automatically enter the waiting state until the execution of the other thread ends

public class joinDemo {
    
    
    public static void main(String[] args) {
    
    
        Thread t1=new Thread(() -> {
    
    
            for (int i=0;i<100;i++){
    
    
                System.out.println(Thread.currentThread().getName()+":"+i);
                try {
    
    
                    Thread.sleep(10);
                } catch (InterruptedException e) {
    
    
                    throw new RuntimeException(e);
                }
            }
        },"t1");
        Thread t2=new Thread(() -> {
    
    
            for (int i=0;i<100;i++){
    
    
                System.out.println(Thread.currentThread().getName()+":"+i);
                if (i==50){
    
    //当i=50将t2连接到t1后
                    try {
    
    
                        t1.join();
                    } catch (InterruptedException e) {
    
    
                        throw new RuntimeException(e);
                    }
                }
                try {
    
    
                    Thread.sleep(10);
                } catch (InterruptedException e) {
    
    
                    throw new RuntimeException(e);
                }
            }
        },"t2");
        t1.start();
        t2.start();
    }
}
  1. yeild(): Thread concession, suspend the running of this thread, let the currently executing thread give up the CPU and return to the ready state to continue to seize the CPU, in the Thread static method: this method will not make the current process enter the blocked state

    public class yieldDemo {
          
          
        public static void main(String[] args) {
          
          
            new Thread(() -> {
          
          
                for (int i = 0; i < 1000; i++) {
          
          
                    System.out.println(Thread.currentThread().getName() + ":" + i);
                    if (i>900){
          
          
                        Thread.yield();//线程让步
                    }
                }
            }, "t1").start();
            new Thread(() -> {
          
          
                for (int i = 0; i < 1000; i++) {
          
          
                    System.out.println(Thread.currentThread().getName() + ":" + i);
                }
            }, "t2").start();
        }
    }
    
  2. Set the thread priority : We can increase the probability of thread execution by setting the thread priority. The higher the priority, the higher the probability of obtaining the CPU, but it is not necessarily possible to obtain the CPU every time.

    Java thread priority level 1-10, 10 is the highest level, 1 is the lowest level, the default is 5

    You can set the priority of the thread through the setPriorityThread.MAX_PRIORITY method of the thread. There are three field constants in the Thread class that represent the highest, lowest, and normal levels; , Thread.MIN_PRIORITY,Thread.NORM_PRIORITY

    public class priorityDemo {
          
          
        public static void main(String[] args) {
          
          
            Thread t1 = new Thread(() -> {
          
          
                for (int i = 0; i < 100; i++) {
          
          
                    System.out.println(Thread.currentThread().getName() + ":" + i);
                }
            }, "t1");
            Thread t2 = new Thread(() -> {
          
          
                for (int i = 0; i < 100; i++) {
          
          
                    System.out.println(Thread.currentThread().getName() + ":" + i);
                }
            }, "t2");
    //        t1.setPriority(10);
    //        t2.setPriority(5);
            t1.setPriority(Thread.MAX_PRIORITY);
            t2.setPriority(Thread.MIN_PRIORITY);
            System.out.println("t1线程优先级"+t1.getPriority());
            System.out.println("t2线程优先级"+t2.getPriority());
            t1.start();
            t2.start();
        }
    }
    

PS: daemon process

Among all threads, the daemon thread is the last to end; generally, the daemon thread is used for monitoring statistics and other functions. When other threads are executed, the daemon thread will automatically end. If it is not executed, it will be forcibly terminated by the JVM.

5 threads synchronously

5.1 Basic concept of thread synchronization

  • Why use thread synchronization :

    In a concurrent environment, deadlock or data confusion may occur when multiple threads access a shared resource at the same time. At this time, we use thread synchronization to solve these problems. Thread synchronization refers to the mutual coordination and control of multiple threads when accessing shared resources to avoid data anomalies.

  • The purpose of thread synchronization : to achieve orderly and controllable access to multi-threaded shared resources , to ensure the security of shared resource data, to avoid deadlocks, and to enable the entire system to run normally

  • Ways to achieve thread synchronization : ①Mutual exclusion synchronization (synchronization lock implementation) ②Cooperative synchronization

PS : In parallel programming, the more shared resources, the more complex the programming, so the use of shared resources should be reduced as much as possible

5.2 Mutex synchronization of threads

When there are multiple parallel threads executing, since the CPU resources occupied and abandoned by the threads are microscopically unpredictable, the data obtained may not be correct if certain measures are not taken for operations such as insertion, deletion, and update of shared data.

  • In order to avoid this situation, java provides a synchronization mechanism to solve - mutual exclusion , using the Synchronized keyword <synchronization keyword> (locking method) to achieve.
  • Mutual exclusion refers to (critical resources) shared resources that only allow one process to access them at the same time, which is unique and exclusive, and ensures the atomicity of thread access to critical resources.
  • The role of the synchronized keyword: to distinguish that only one thread executes a specific code block at the same time, that is, to realize the atomicity of the code block.

Two ways to achieve thread synchronization using synchronized :

  • Synchronized code block
  • Synchronous methods (instance methods and class methods)
  1. Thread synchronization implementation - synchronization code block
/**synchronized(obj){
    要实现的同步代码
}
**/
  • The code surrounded by synchronized is called a synchronized code block

  • The synchronized code block needs to accept a parameter, which is an object, which can be called the lock object (also known as synchronization lock) of the code block. The lock object is a unique arbitrary object in the code block

  • Only when any thread holds the lock object can the code block locked by the lock object be executed

  • If a thread holds a lock object, other threads cannot hold the lock object at the same time, so as to realize the mutual exclusion of threads based on a specific lock object

    • PS : The thread holding the lock object does not affect other threads accessing the methods and properties of the lock object
  • Multiple threads compete for the monitor of the lock object in a competitive manner, and the thread executes the code block, that is, the monitor of the lock object is released

    public class Demo01 {
          
          
        private static int num = 0;
        private Random random = new Random();
        //创建一个唯一锁对象
        private static Object LOCK = new Object();
    
        /**
         * 为num+1
         * synchronized 同步关键字
         * 同步实现两种方式:
         *1.同步代码块:synchronized (锁对象) {同步代码块}
         *       PS:锁对象可以为任意对象,但是必须唯一,当本类的不同对象在多个线程中被访问不能是this,      * 因为this对像不唯一
         */
        public void add() {
          
          
            /**
            * 锁对象可以是任意对象但必须唯一,当同步锁对象是当前对象(this对象),并且要同步的代码块是整个         * 方法体时我们就可以使用同步方法实现,即在方法声明位置加入synchronized关键字,同步方法的同步         * 锁是this。
            * 但是如果多个线程同时访问的是本类的不同对象时,同步锁则不能使用this,因为this对象不唯一。
            **/
            synchronized (this) {
          
          
                int result = num + 1;
                //进入阻塞状态()
                try {
          
          
                    Thread.sleep(random.nextInt(5) + 1);
                } catch (InterruptedException e) {
          
          
                    e.printStackTrace();
                }
                num = result;
                System.out.println(Thread.currentThread().getName() + ":" + num);
            }
        }
    }
    
    public class Demo01Test {
          
          
        public static void main(String[] args) {
          
          
            Demo01 demo01=new Demo01();
            //Demo01 demo02=new Demo01();
            new Thread(new Runnable() {
          
          
                @Override
                public void run() {
          
          
                    for (int i=0;i<100;i++){
          
          
                        demo01.add();
                    }
                }
            }).start();
    
            new Thread(new Runnable() {
          
          
                @Override
                public void run() {
          
          
                    for (int i=0;i<100;i++){
          
          
                        demo01.add();
                    }
                }
            }).start();
        }
    }
    

    PS: The lock object can be any object but must be unique. When the synchronization lock object is the current object (this object) and the code block to be synchronized is the entire method body, we can use the synchronization method to implement it, that is, add synchronized at the method declaration position Keyword, the lock object of the synchronization method is this.
    But if multiple threads access different objects of this class at the same time, the synchronization lock cannot use this, because this object is not unique.

  1. synchronization method

    Use synchronized to modify the method declaration, the method is called a synchronized method

    • The lock object of the synchronization method is this
    • The lock object of the synchronized static method is the .class object of the current class
     public synchronized 返回类型 方法名(){
          
          同步内容}
     public static synchronized 返回类型 方法名(){
          
          同步内容}
    

    Case: Using multithreading to simulate the sale of train tickets at multiple ticket outlets

    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @NoArgsConstructor
    public class SaleTicket {
          
          
        private Integer ticket;
        private static final Object LOCK = new Object();
    
        public SaleTicket(Integer ticket) {
          
          
            this.ticket = ticket;
        }
    
        public void sale() {
          
          
            synchronized (LOCK) {
          
          
                if (ticket <= 0) {
          
          
                    System.out.println("票已售完....");
                    return;
                }
                System.out.println(Thread.currentThread().getName() + ":售出第" + ticket + "张票");
                try {
          
          
                    Thread.sleep(10);
                } catch (InterruptedException e) {
          
          
                    e.printStackTrace();
                }
                ticket--;
            }
        }
    }
    
    public class SaleTicketTest {
          
          
        public static void main(String[] args) {
          
          
            SaleTicket saleTicket = new SaleTicket(10);
    
            new Thread(new Runnable() {
          
          
                @Override
                public void run() {
          
          
                    for (int i = 0; i < 10; i++) {
          
          
                        saleTicket.sale();
                    }
                }
            }, "1号窗口").start();
    
            new Thread(new Runnable() {
          
          
                @Override
                public void run() {
          
          
                    for (int i = 0; i < 10; i++) {
          
          
                        saleTicket.sale();
                    }
                }
            }, "2号窗口").start();
        }
    }
    

5.3 Deadlock

Multiple threads seize multiple shared resources and enter a stalemate state. If there is no external influence, this state will remain, and this state is a deadlock.

Deadlock conditions : ① Mutual exclusion condition ② Request and hold ③ Non-preemptible ④ Loop waiting

  • A thread enters the monitor of obj1 and waits for the monitor of obj2, and another thread enters the monitor of obj2 and waits for the monitor of obj1, and at this time it is deadlocked.

    Deadlocks are hard to happen and hard to debug once they happen

    public class Demo {
          
          
        public static Object LOCK1 = new Object();
        public static Object LOCK2 = new Object();
    
        public void f1(){
          
          
            System.out.println(Thread.currentThread().getName()+"等待获取锁1");
            synchronized (LOCK1){
          
          
                System.out.println(Thread.currentThread().getName()+"获得锁1,等待获取锁2");
            synchronized (LOCK2){
          
          
                System.out.println(Thread.currentThread().getName()+"获得锁2");
            }
                System.out.println(Thread.currentThread().getName()+"释放锁2");
            }
            System.out.println(Thread.currentThread().getName()+"释放锁1");
        }
    
        public  void f2(){
          
          
            System.out.println(Thread.currentThread().getName()+"等待获取锁2");
            synchronized (LOCK2){
          
          
                System.out.println(Thread.currentThread().getName()+"获得锁2,等待获取锁1");
                synchronized (LOCK1){
          
          
                    System.out.println(Thread.currentThread().getName()+"获得锁1");
                }
                System.out.println(Thread.currentThread().getName()+"释放锁1");
            }
            System.out.println(Thread.currentThread().getName()+"释放锁2");
        }
    }
    
    public class Test {
          
          
        public static void main(String[] args) {
          
          
            Demo demo=new Demo();
            //线程1
            new Thread(new Runnable() {
          
          
                @Override
                public void run() {
          
          
                    demo.f1();
                }
            },"f1").start();
            //线程2
            new Thread(new Runnable() {
          
          
                @Override
                public void run() {
          
          
                    demo.f2();
                }
            },"f2").start();
        }
    }
    

5.4 Thread cooperation

  • Thread cooperation is based on multi-thread mutual exclusion synchronization, so that threads can interact and cooperate with each other in a purposeful and planned way according to certain conditions. This is a higher-level thread synchronization method.

  • The well-designed inter-thread communication mechanism in Java is the wait-notify mechanism, through which thread cooperation can be realized.

  • The wait-notify mechanism is implemented through wait(), notify(), three methodsnotifyAll()

    • These three methods are defined in Object and are final modified instance methods;
    • These three methods must be called in the synchronized code, and only the lock object can call these three methods, that is, the thread holding the lock object monitor can call the three methods of the lock object;
    • Wait or wake up a thread on a lock object.
  • wait()method

    • The thread calling this method gives up the synchronization lock and enters the waiting state (blocking state) until other threads obtain the same synchronization lock and call notify()or notifyAll()method to wake up.
  • notify()method

    • Notify a thread waiting on the same synchronization lock to end waiting and enter the ready state. That is, wake up a thread waiting for a synchronization lock (held by the current thread).
  • notifyAll()method

    • Notify all threads waiting on the same synchronization lock to end waiting and enter the ready state. That is, wake up all threads waiting for the synchronization lock (held by the current thread).

    Case: Producer Consumer Model

    /**
     * 商品类-面包
     */
    public class MianBao {
          
          
        private Integer num;//面包编号
    
        public MianBao(Integer num) {
          
          
            this.num = num;
        }
    }
    
    /**
     * 面包柜
     */
    public class GuiZi {
          
          
        private MianBao[] mianBaos;//面包容器
        private int size;//柜子中面包数量
    
        /**
         * 构造方法,初始面包数组
         * @param initSize
         */
        public GuiZi(int initSize) {
          
          
            //创建容器并设置大小
            mianBaos = new MianBao[initSize];
        }
    
        /**
         * 生产面包
         * @param mianBao
         */
        public synchronized void add(MianBao mianBao) {
          
          
    
            /**
             * 1.生产者线程继续生成,由于面包柜已满,此时size的值为10,面包师傅1线程进入等待状态
             * 2.如果此时还未消费,生产者2线程继续生成,由于面包柜已满,此时size的值为10,面包师傅2线程进入等待状态
             * 现在处于等待面包师傅线程有2个
             * 3.消费者线程消费一个商品,会唤醒所有处于等待的线程,此时生产者1和2两个线程同时被唤醒
             * 4.生产者1继续生产,生产后,size的值为10
             * 5.生产者1继续生产,由于唤醒后执行的位置是等待的位置,所以不会在检测面包柜是否已满,而此时面包柜满了,所以会报越界异常
             */
            while (size == mianBaos.length) {
          
          
                System.out.println("柜子已满....");
                try {
          
          
                    wait();
                } catch (InterruptedException e) {
          
          
                    e.printStackTrace();
                }
            }
            mianBaos[size] = mianBao;//添加一个面包对象
            size++;
            System.out.println(Thread.currentThread().getName() + ":生产第" + size + "个面包");
            try {
          
          
                Thread.sleep(10);
            } catch (InterruptedException e) {
          
          
                e.printStackTrace();
            }
            notifyAll();
        }
    
        /**
         * 消费面包
         *
         */
        public synchronized void sub() {
          
          
            while (size == 0) {
          
          
                System.out.println("柜子已空....");
                try {
          
          
                    wait();
                } catch (InterruptedException e) {
          
          
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + ":消费第" + size + "个面包");
            try {
          
          
                Thread.sleep(10);
            } catch (InterruptedException e) {
          
          
                e.printStackTrace();
            }
            size--;
            MianBao mianBao = mianBaos[size];
            notifyAll();
        }
    }
    
    public class ProAndCon {
          
          
        public static void main(String[] args) {
          
          
            GuiZi guiZi=new GuiZi(10);
    
            new Thread(new Runnable() {
          
          
                @Override
                public void run() {
          
          
                    for (int i=0;i< 100;i++){
          
          
                        MianBao mianBao=new MianBao(i+1);
                        guiZi.add(mianBao);
                    }
                }
            },"生产者1").start();
    
            new Thread(new Runnable() {
          
          
                @Override
                public void run() {
          
          
                    for (int i=0;i< 100;i++){
          
          
                        MianBao mianBao=new MianBao(i+1);
                        guiZi.add(mianBao);
                    }
                }
            },"生产者2").start();
            new Thread(new Runnable() {
          
          
                @Override
                public void run() {
          
          
                    for (int i=0;i< 100;i++){
          
          
                        guiZi.sub();
                    }
                }
            },"消费者1").start();
            new Thread(new Runnable() {
          
          
                @Override
                public void run() {
          
          
                    for (int i=0;i< 100;i++){
          
          
                        guiZi.sub();
                    }
                }
            },"消费者2").start();
        }
    }
    

  • Learning comes from Xi'an Jiazhong training

Guess you like

Origin blog.csdn.net/woschengxuyuan/article/details/127939012