• javaEE -3(12000字详解多线程)


    一:单例模式

    首先啥是设计模式?

    设计模式好比象棋中的 “棋谱”. 红方当头炮, 黑方马来跳. 针对红方的一些走法, 黑方应招的时候有一些固定的套路. 按照套路来走局势就不会吃亏.

    软件开发中也有很多常见的 “问题场景”. 针对这些问题场景, 大佬们总结出了一些固定的套路. 按照这个套路来实现代码, 也不会吃亏.

    单例模式能保证某个类在程序中只存在唯一一份实例, 而不会创建出多个实例,这一点在很多场景上都需要. 比如 JDBC 中的 DataSource 实例就只需要一个。

    而单例模式具体的实现方式, 分成 “饿汉” 和 “懒汉” 两种.

    1.1 饿汉模式

    饿汉模式即:类加载的同时, 创建实例.

    class Singleton {
      private static Singleton instance = new Singleton();
      private Singleton() {}
      public static Singleton getInstance() {
        return instance;
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. 首先我们通过new创建了一个对象,但是这个对象是静态的,所以对于这整个类来说,这个对象只会存在一份

    2. 并且由于构造方法Singleton()被私有化,外部无法直接创建Singleton对象,从而保证了单例模式的实现

    我们通过 getInstance() 方法返回 instance 对象,以此提供对单例对象的访问。

    1.2懒汉模式

    1.2.1懒汉模式-单线程版

    懒汉模式即:类加载的时候不创建实例. 第一次使用的时候才创建实例:

    class Singleton {
      private static Singleton instance = null;
      private Singleton() {}
      public static Singleton getInstance() {
        if (instance == null) {
          instance = new Singleton();
       }
        return instance;
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 我们通过私有化构造函数和静态的 getInstance() 方法,保证了外部无法直接创建 Singleton 类的对象
    • 并且只有第一次调用 getInstance() 才会创建对象,非第一次调用都无法创建对象,并且通过这个方法可以返回这个对象。

    但是这段代码是线程不安全的,如果多个线程同时调用 getInstance() 方法,有可能会创建多个 Singleton 实例。

    线程安全问题发生在首次创建实例时. 如果在多个线程中同时调用 getInstance 方法, 就可能导致创建出多个实例.

    1.2.2 懒汉模式-多线程版

    加上 synchronized 可以改善这里的线程安全问题:

    class Singleton {
      private static Singleton instance = null;
      private Singleton() {}
      public synchronized static Singleton getInstance() {
        if (instance == null) {
          instance = new Singleton();
       }
        return instance;
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    1.2.3 懒汉模式-多线程版(改进)

    以下代码在加锁的基础上, 做出了进一步改动:

    • 使用双重 if 判定, 降低锁竞争的频率.
    • 给 instance 加上了 volatile.
    class Singleton {
      private static volatile Singleton instance = null;
      private Singleton() {}
      public static Singleton getInstance() {
        if (instance == null) {
          synchronized (Singleton.class) {
         if (instance == null) {
           instance = new Singleton();
           }
         }
       }
        return instance;
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    加锁 / 解锁是一件开销比较高的事情. 而懒汉模式的线程不安全只是发生在首次创建实例的时候.因此后续使用的时候, 不必再进行加锁了.

    • 所以外层的 if 就是判定下看当前是否已经把 instance 实例创建出来了.
    • 内层的 if 用来当多个线程可能同时通过外层if的判断进入同步块时,限制只有instance 为 null 的时候才实例化,当一个线程执行完之后,后续的线程都会被这个 if 条件限制了

    同时为了避免 “内存可见性” 导致读取的 instance 出现偏差, 于是在第一行补充上 volatile

    二:阻塞式队列

    阻塞队列是一种特殊的队列. 也遵守 “先进先出” 的原则,阻塞队列能是一种线程安全的数据结构, 并且具有以下特性:

    • 当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素.
    • 当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插入元素.

    阻塞队列的一个典型应用场景就是 “生产者消费者模型”. 这是一种非常典型的开发模型。

    2.1 生产者和消费者模型

    生产者和消费者模型:一个或多个生产者生成数据,并将其放入一个共享的缓冲区中,而一个或多个消费者从缓冲区中取出数据进行消费。

    举个例子:

    • 厨师制作菜品,并将其放入菜品架(缓冲区)上。食客是消费者,从菜品架上取走菜品进行消费。
    • 如果菜品架已满,厨师需要等待一段时间,直到有空的位置放置菜品。如果菜品架为空,食客需要等待一段时间,直到有菜品可供消费。

    那么这个生产者和消费者模型有什么作用呢?

    1. 阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力

    比如在 “秒杀” 场景下, 服务器同一时刻可能会收到大量的支付请求. 如果直接处理这些支付请求,服务器可能扛不住(每个支付请求的处理都需要比较复杂的流程).

    这个时候就可以把这些请求都放到一个阻塞队列中, 然后再由消费者线程慢慢的来处理每个支付请求.这样做可以有效进行 “削峰”, 防止服务器被突然到来的一波请求直接冲垮.

    1. 阻塞队列能使生产者和消费者之间解耦.

    就好比厨师就安心做自己的菜品,消费者专心选择自己喜欢的菜品,他们两个之间并没有太大关系。

    2.2 标准库中的阻塞队列

    在 Java 标准库中内置了阻塞队列. 如果我们需要在一些程序中使用阻塞队列, 直接使用标准库中的即可.

    • BlockingQueue 是一个接口. 真正实现的类是 LinkedBlockingQueue.
    • put 方法用于阻塞式的入队列, take 用于阻塞式的出队列.
    • BlockingQueue 也有 offer, poll, peek 等方法, 但是这些方法不带有阻塞特性.
    BlockingQueue<String> queue = new LinkedBlockingQueue<>();
    // 入队列
    queue.put("abc");
    // 出队列. 如果没有 put 直接 take, 就会阻塞.
    String elem = queue.take();
    
    • 1
    • 2
    • 3
    • 4
    • 5

    生产者消费者模型

    public static void main(String[] args) throws InterruptedException {
      // 创建一个阻塞队列,使用 LinkedBlockingQueue 实现
      BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<Integer>();
      
      // 创建消费者线程
      Thread customer = new Thread(() -> {
        while (true) {
          try {
            // 从阻塞队列中获取元素并打印
            int value = blockingQueue.take();
            System.out.println("消费元素: " + value);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
      }, "消费者");
      
      // 启动消费者线程
      customer.start();
      
      // 创建生产者线程
      Thread producer = new Thread(() -> {
        Random random = new Random();
        while (true) {
          try {
            // 生成一个随机数作为元素
            int num = random.nextInt(1000);
            System.out.println("生产元素: " + num);
            
            // 将元素放入阻塞队列中
            blockingQueue.put(num);
            
            // 生产者线程休眠 1 秒
            Thread.sleep(1000);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
      }, "生产者");
      
      // 启动生产者线程
      producer.start();
      
      // 等待消费者线程和生产者线程执行结束
      customer.join();
      producer.join();
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    2.3阻塞队列实现

    • 通过 “循环队列” 的方式来实现.
    • 使用 synchronized 进行加锁控制.
    • put 插入元素的时候, 判定如果队列满了, 就进行 wait.
    • take 取出元素的时候, 判定如果队列为空, 就进行 wait.
    public class BlockingQueue {
      private int[] items = new int[1000];  // 存放元素的数组
      private volatile int size = 0;  // 队列中元素的数量
      private int head = 0;  // 队列头部索引
      private int tail = 0;  // 队列尾部索引
      
      // 向队列中放入元素
      public void put(int value) throws InterruptedException {
        synchronized (this) {
          // 当队列已满时,等待
          // 此处最好使用 while,否则 notifyAll 的时候, 该线程从 wait 中被唤醒,但是紧接着并未抢占到锁. 当锁被抢占的时候, 可能又已经队列满了,就只能继续等待
          while (size == items.length) {
            wait();
          }
          items[tail] = value;  // 将元素放入队列尾部
          tail = (tail + 1) % items.length;  // 更新尾部索引
          size++;  // 元素数量加1
          notifyAll();  // 唤醒其他等待线程
        }
      }
      
      // 从队列中取出元素
      public int take() throws InterruptedException {
        int ret = 0;
        synchronized (this) {
          // 当队列为空时,等待
          while (size == 0) {
            wait();
          }
          ret = items[head];  // 取出队列头部元素
          head = (head + 1) % items.length;  // 更新头部索引
          size--;  // 元素数量减1
          notifyAll();  // 唤醒其他等待线程
        }
        return ret;
      }
      
      // 获取队列中元素的数量
      public synchronized int size() {
        return size;
      }
      
      // 测试代码
      public static void main(String[] args) throws InterruptedException {
        BlockingQueue blockingQueue = new BlockingQueue();
        
        // 定义消费者线程
        Thread customer = new Thread(() -> {
          while (true) {
            try {
              int value = blockingQueue.take();  // 从队列中取出元素
              System.out.println(value);  // 打印取出的元素值
            } catch (InterruptedException e) {
              e.printStackTrace();
            }
          }
        }, "消费者");
        
        customer.start();  // 启动消费者线程
        
        // 定义生产者线程
        Thread producer = new Thread(() -> {
          Random random = new Random();
          while (true) {
            try {
              blockingQueue.put(random.nextInt(10000));  // 向队列中放入随机生成的元素
            } catch (InterruptedException e) {
              e.printStackTrace();
            }
          }
        }, "生产者");
        
        producer.start();  // 启动生产者线程
        
        customer.join();  // 等待消费者线程结束
        producer.join();  // 等待生产者线程结束
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    该代码实现了一个阻塞队列,其中包括以下方法和功能:

    1. put(int value) 方法用于向队列中放入元素。

      • 在队列已满时,使用 wait() 方法等待。
      • 使用 notifyAll() 方法唤醒其他等待的线程。
    2. take() 方法用于从队列中取出元素。

      • 在队列为空时,使用 wait() 方法等待。
      • 使用 notifyAll() 方法唤醒其他等待的线程。
    3. size() 方法用于获取队列中元素的数量。

    4. main 方法用于测试代码:

      • 创建一个 BlockingQueue 实例。
      • 定义一个消费者线程,不断从队列中取出元素并打印。
      • 定义一个生产者线程,不断向队列中放入随机生成的元素。
      • 启动消费者线程和生产者线程。
      • 使用 join() 方法等待消费者线程和生产者线程结束。

    三:定时器

    定时器也是软件开发中的一个重要组件. 类似于一个 “闹钟”. 达到一个设定的时间之后, 就执行某个指定好的代码.

    定时器是一种实际开发中非常常用的组件,比如网络通信中, 如果对方 500ms 内没有返回数据, 则断开连接尝试重连,比如一个 Map, 希望里面的某个 key 在 3s 之后过期(自动删除),类似于这样的场景就需要用到定时器。

    3.1标准库中的定时器

    • 标准库中提供了一个 Timer 类. Timer 类的核心方法为 schedule
    • schedule 包含两个参数. 第一个参数指定即将要执行的任务代码, 第二个参数指定多长时间之后执行 (单位为毫秒).
    public void schedule(TimerTask task, Date time)
    
    • 1

    参数解释:

    • task:要被调度的任务,通常是继承自TimerTask类并实现其run方法的一个对象。
    • time:任务开始执行的时间,是一个java.util.Date对象。

    schedule方法的执行流程:

    1. 在指定的时间点time之后,Timer会创建一个新线程,并在新线程中启动任务task的执行。
    2. 在任务task执行之前,Timer会使用一个内部定时器线程来等待,直到指定的时间点到达。
    3. 任务task在指定的时间点到达后会被执行,此时Timer会调用task对象的run方法。

    示例代码如下:

    import java.util.Date;
    import java.util.Timer;
    import java.util.TimerTask;
    
    public class TimerExample {
    
        public static void main(String[] args) {
            Timer timer = new Timer();
            
            // 创建一个任务,继承自TimerTask类,并实现其run方法
            TimerTask task = new TimerTask() {
                @Override
                public void run() {
                    System.out.println("任务开始执行,当前时间:" + new Date());
                }
            };
            
            // 设定任务在5秒后开始执行
            Date startTime = new Date(System.currentTimeMillis() + 5000);
            
            // 安排任务在指定时间开始执行
            timer.schedule(task, startTime);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    以上代码创建了一个Timer对象,并定义了一个任务task,该任务在5秒后开始执行。通过schedule方法将任务安排在指定时间开始执行。在任务执行时,会打印出当前的时间。

    3.2 实现定时器

    public class Timer {
      // 内部类,表示要执行的任务
      static class Task implements Comparable<Task> {
        private Runnable command; // 要执行的任务
        private long time; // 任务执行的时间
        
        // 构造方法,初始化任务和执行时间
        public Task(Runnable command, long time) {
          this.command = command;
          // time 中存的是绝对时间, 超过这个时间的任务就应该被执行
          this.time = System.currentTimeMillis() + time;
        }
        
        // 执行任务的方法
        public void run() {
          command.run();
        }
        
        // 实现 Comparable 接口,根据任务执行时间进行排序
        @Override
        public int compareTo(Task o) {
          // 谁的时间小谁排前面
          return (int)(time - o.time);
        }
      }
      
      // 核心结构
      private PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue<Task>();
      // 存在的意义是避免 worker 线程出现忙等的情况
      private Object mailBox = new Object();
      
      // 内部类,表示工作线程
      class Worker extends Thread {
        @Override
        public void run() {
          while (true) {
            try {
              Task task = queue.take(); // 从队列中取出一个任务
              long curTime = System.currentTimeMillis();
              if (task.time > curTime) {
                // 时间还没到, 就把任务再塞回去
                queue.put(task);
                synchronized (mailBox) {
                  // 指定等待时间 wait
                  mailBox.wait(task.time - curTime);
                }
              } else {
                // 时间到了, 可以执行任务
                task.run();
              }
            } catch (InterruptedException e) {
              e.printStackTrace();
              break;
            }
          }
        }
      }
      
      // 构造方法,创建并启动工作线程
      public Timer() {
        Worker worker = new Worker();
        worker.start();
      }
      
      // 安排要执行的任务
      public void schedule(Runnable command, long after) {
        Task task = new Task(command, after);
        queue.offer(task); // 将任务插入队列
        synchronized (mailBox) {
          mailBox.notify(); // 唤醒工作线程
        }
      }
      
      // 主函数,用于测试
      public static void main(String[] args) {
        Timer timer = new Timer();
        Runnable command = new Runnable() {
          @Override
          public void run() {
            System.out.println("我来了");
            timer.schedule(this, 3000); // 3秒后再次调度任务
          }
        };
        timer.schedule(command, 3000); // 3秒后执行任务
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86

    四:线程池

    线程池的主要作用是提高程序的性能和资源利用率。它可以在系统初始化时创建一定数量的线程放入线程池中

    当任务到达时,从线程池中取出一个线程来处理任务,当线程完成任务后,再将该线程放回线程池中,等待下一个任务的到来。

    通过有效地管理线程的生命周期,线程池可以控制并发线程的数量,避免创建过多的线程导致资源耗尽。

    线程池的好处包括以下几个方面:

    1. 降低资源消耗:线程的创建和销毁是一项开销较大的操作,使用线程池可以避免频繁地创建和销毁线程,从而降低了系统的资源消耗。

    2. 提高响应速度:线程池中的线程是预先创建的,当有任务到达时,可以立即执行,提高了任务的响应速度。

    3. 提高线程的可管理性:线程池可以对线程进行统一的管理,如线程的创建、销毁、监控等,提供了更好的线程管理方式。

    4. 提供线程复用:线程池中的线程可以被重复利用,避免了频繁创建和销毁线程的开销,提高了系统的性能和资源利用率。

    4.1 标准库中的线程池

    ExecutorService 是 Java 提供的一个线程池框架,用于管理和执行多线程任务。它是 Executor 的子接口,提供了更丰富的功能和更灵活的线程管理。

    首先,我们需要使用 Executors 工厂类提供的方法来创建 ExecutorService。以下是几种常用的创建线程池的方式:

    1. newFixedThreadPool(int nThreads):创建包含固定线程数的线程池。

    代码示例:

    ExecutorService executorService = Executors.newFixedThreadPool(10);
    
    • 1
    1. newCachedThreadPool():创建线程数目动态增长的线程池。

    代码示例:

    ExecutorService executorService = Executors.newCachedThreadPool();
    
    • 1
    1. newSingleThreadExecutor():创建只包含单个线程的线程池,保证任务按顺序执行。

    代码示例:

    ExecutorService executorService = Executors.newSingleThreadExecutor();
    
    • 1
    1. newScheduledThreadPool(int corePoolSize):创建可以定期执行任务的线程池,具有延迟或周期执行任务的能力。

    代码示例:

    ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
    
    • 1

    创建了 ExecutorService 后,我们可以通过调用其 submit() 方法(在submit 中重写 run 方法)将任务提交给线程池执行。submit() 方法接受一个 Callable 或 Runnable 对象作为参数,并返回一个 Future 对象用于获取任务的执行结果。

    以下是一个简单的示例代码,演示了如何创建一个固定线程数的线程池,并提交多个任务执行:

    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Future;
    
    public class ExecutorServiceExample {
        public static void main(String[] args) {
            // 创建固定线程数的线程池
            ExecutorService executorService = Executors.newFixedThreadPool(10);
            
            // 提交任务到线程池执行
            for (int i = 0; i < 10; i++) {
                final int taskId = i;
                executorService.submit(() -> {
                    try {
                        // 模拟任务执行耗时
                        Thread.sleep(1000);
                        System.out.println("Task " + taskId + " completed.");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            }
            
            // 关闭线程池
            executorService.shutdown();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    在这个示例中,我们创建了一个包含固定线程数为 10 的线程池,并通过循环提交了 10 个任务。每个任务执行的操作是打印任务编号和完成信息。最后,我们调用 executorService.shutdown() 来关闭线程池。

    通过使用 ExecutorService,我们可以更方便地管理线程和任务,提高程序的效率和性能,Executors 本质上是 ThreadPoolExecutor 类的封装,ThreadPoolExecutor 提供了更多的可选参数, 可以进一步细化线程池行为的设定. (后面再介绍)

    4.2实现线程池

    class Worker extends Thread {
      // 创建一个阻塞队列来保存任务
      private LinkedBlockingQueue<Runnable> queue = null;
      
      // Worker 的构造函数,传入任务队列
      public Worker(LinkedBlockingQueue<Runnable> queue) {
        // 调用父类 Thread 的构造函数,给线程命名为 "worker"
        super("worker");
        this.queue = queue;
      }
      
      // 线程执行的方法
      @Override
      public void run() {
        try {
          // 循环判断线程是否被中断
          while (!Thread.interrupted()) {
            // 从任务队列中取出一个任务
            Runnable runnable = queue.take();
            // 执行任务的 run 方法
            runnable.run();
          }
        } catch (InterruptedException e) {
          // 捕获 InterruptedException 异常
        }
      }
    }
    
    public class MyThreadPool {
      // 设置线程池最大工作线程数为 10
      private int maxWorkerCount = 10;
      
      // 创建一个任务队列
      private LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue();
      
      // 提交任务的方法
      public void submit(Runnable command) {
        // 如果当前工作线程数小于最大工作线程数
        if (workerList.size() < maxWorkerCount) {
          // 继续创建新的 worker 线程
          Worker worker = new Worker(queue);
          worker.start();
        }
        
        // 将任务添加到任务队列中
        queue.put(command);
      }
      
      public static void main(String[] args) throws InterruptedException {
        // 创建一个 MyThreadPool 实例
        MyThreadPool myThreadPool = new MyThreadPool();
        
        // 执行一个任务,输出 "吃饭"
        myThreadPool.execute(new Runnable() {
          @Override
          public void run() {
            System.out.println("吃饭");
          }
        });
        
        // 线程休眠 1000 毫秒
        Thread.sleep(1000);
      }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    五:对比线程和进程

    5.1线程的优点

    1. 创建一个新线程的代价要比创建一个新进程小得多
    2. 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
    3. 线程占用的资源要比进程少很多
    4. 能充分利用多处理器的可并行数量
    5. 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
    6. 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
    7. I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。

    5.2进程与线程的区别

    1. 进程是系统进行资源分配和调度的一个独立单位,线程是程序执行的最小单位。
    2. 进程有自己的内存地址空间,线程只独享指令流执行的必要资源,如寄存器和栈。
    3. 由于同一进程的各线程间共享内存和文件资源,可以不通过内核进行直接通信。
    4. 线程的创建、切换及终止效率更高。
  • 相关阅读:
    arm学习之基本汇编指令
    【1day】复现海康威视综合安防管理平台center 接口文件上传漏洞
    缺失找不到msvcr71.dll无法执行代码,应用程序无法启动的解决方法
    cmake(1)
    IDEA安装Tomcat
    注销/撤销/吊销
    Android——service简单介绍
    C/C++教程 从入门到精通《第十二章》——MFC的基本使用
    Go sync.waitGroup
    如何配置阿里云yum源
  • 原文地址:https://blog.csdn.net/weixin_73232539/article/details/133933146