多线程

  • 概念

    • 程序:软件程序(可执行程序.exe)

    • 进程:程序运行的实例

      每个程序至少有一个进程运行。

      一个程序执行进入内存运行时,变成一个进程。进程的资源彼此隔离,其他进程不允许访问。

      进程概念

    • 线程:是进程内执行的”任务“

      ​ 如:迅雷中三个线程参与”下载“任务。

      1. 线程是进程内的一个”基本任务“,每个线程都有自己的功能,是CPU分配与调度的基本单位。
      2. 一个进程内可以包含多个线程,反之一个线程只能隶属于一个进程。
      3. 进程内至少拥有一个线程,这个线程叫”主线程“,主线程消亡即该进程结束。
    • CPU、进程、线程的关系

      CPU、进程、线程的关系

    • Java中进程与线程

      一个Java进程至少包含一个main主线程和一个垃圾收集线程(GC垃圾回收机制)。

      Java中进程与线程

  • 创建多线程三种方式

    • 继承Thread类创建线程(不推荐)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      // 通过继承Thread类重写run方法的方式
      class Runner extends Thread{
      @Override
      public void run(){

      }
      }

      public void start(){
      Runner runner1 = new Runner();
      runner1.setName("参数1");
      runner1.start();
      }
    • 实现Runnable接口创建线程(不推荐)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      class Runner implements Runnable{
      @Override
      public void run(){

      }
      }

      public void start(){
      Runner runner1 = new Runner();
      Thread t1 = new Thread(runner1);
      t1.setName("参数1");
      t1.start();
      }
    • 实现Callable接口创建线程(推荐)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      class Runner implements Callable<Integer> {
      @Override
      public Integer call() throws Exception {
      return 0;
      }
      }

      public void start() throws ExecutionException, InterruptedException {
      // 线程池
      ExecutorService executorService = Executors.newFixedThreadPool(3);
      Runner runner1 = new Runner();
      runner1.name = "参数1";
      // 返回值
      Future<Integer> r1 = executorService.submit(runner1);
      // 所有线程执行结束后关闭线程池,释放资源
      executorService.shutdown();
      }
    • 三种创建方式对比

      1. 继承Thread,Java对继承不友好,不推荐使用
      2. 实现Runnable接口,Java编程友好,但无法返回执行后的数据。
      3. 实现Callable接口,Java编程友好,可以返回多线程执行结果,稍显复杂,需要线程池。
  • 线程同步

    synchronized(同步锁)关键字的作用就是利用一个特定的对象设置一个锁lock(钥匙),在多线程(多名游客)并发访问的时候,同时只允许一个线程(一个游客)可以获得这个锁,执行特定的代码(如进入参观)。执行后释放锁,继续由其他线程争抢。

    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
    class Printer {
    // 定义锁对象
    Object lock = new Object();
    public void print() {
    synchronized (lock) {
    try {
    Thread.sleep(500);
    System.out.print("荷");
    Thread.sleep(500);
    System.out.print("塘");
    Thread.sleep(500);
    System.out.print("月");
    Thread.sleep(500);
    System.out.print("色");
    System.out.println();
    } catch (InterruptedException e) {
    throw new RuntimeException(e);
    }
    }
    }
    }
    class PrintTask implements Runnable{
    Printer printer;
    @Override
    public void run() {
    printer.print();
    }
    }

    public void start(){
    Printer printer = new Printer();
    // 创建5个线程
    for (int i = 0; i < 5; i++) {
    PrintTask task = new PrintTask();
    task.printer = printer;
    Thread thread = new Thread(task);
    thread.start();
    }
    }
    • Synchronized锁对象

      1. synchronized代码块 - 任意对象即可

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        // synchronized代码块演示
        class Printer {
        // 定义锁对象
        Object lock = new Object();
        public void print() {
        synchronized (lock) {

        }
        }
        }
      2. synchronized方法 - this当前对象

        1
        2
        3
        4
        5
        6
        7
        // synchronized方法演示
        // this关键字
        class Printer {
        public synchronized void print() {

        }
        }
      3. synchronized静态方法 - 该类的字节码对象

        1
        2
        3
        4
        5
        6
        7
        // synchronized静态方法演示
        // Printer.class字节码对象
        class Printer {
        public static synchronized void print() {

        }
        }
  • 线程安全问题与解决方式

    问题:如商城超售情况。

    解决:使用synchronized锁机制解决。

  • 线程池

    包路径:java.util.concurrent(简称:JUC

    概念:并发是伴随着多核处理器的诞生而产生的,为了充分利用硬件资源,诞生了多线程技术。但是多线程又存在资源竞争的问题,引发了同步互斥的问题,JDK1.5推出的java.util.concurrent(并发工具包)来解决这些问题。

    • Runnable接口弊端

      1. Runnable新建线程,性能差。
      2. 线程缺乏统一管理,可能无限制新建线程,相互竞争,严重时将占用过多系统资源从而导致死机或内存溢出问题。
    • 线程池 - 线程复用

      • ThreadPool线程池(重用、可控、额外功能)

        1. 重用存在的线程,减少线程对象创建、消亡的开销。
        2. 线程总数可控,提高资源利用率。
        3. 提供额外功能:定时执行、定期执行、监控等。
      • JUC支持的线程池种类

        在JUC中,提供了工具类Executors(调度器)对象来创建线程池,可创建以下4种线程池:

        1. FixedThreadPool - 定长线程池

          特点:固定线程总数,有空闲则执行,没有空闲则等待。

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          ExecutorService executorService = Executors.newFixedThreadPool(10);
          for (int i = 0; i < 500; i++) {
          int index = i;
          executorService.execute(new Runnable() {
          @Override
          public void run() {
          System.out.println(Thread.currentThread().getName() + ":" + index);
          }
          });
          }
          executorService.shutdown();
        2. CachedThreadPool - 可缓存线程池

          特点:无限大,有空闲线程则利用起来。没有闲置则创建新的线程。

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          ExecutorService executorService = Executors.newCacheThreadPool();
          for (int i = 0; i < 500; i++) {
          int index = i;
          executorService.execute(new Runnable() {
          @Override
          public void run() {
          System.out.println(Thread.currentThread().getName() + ":" + index);
          }
          });
          }
          executorService.shutdown();
        3. SingleThreadExecutor - 单线程池

          特点:只有一个线程,通过排队执行。

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          ExecutorService executorService = Executors.newSingleThreadPool();
          for (int i = 0; i < 500; i++) {
          int index = i;
          executorService.execute(new Runnable() {
          @Override
          public void run() {
          System.out.println(Thread.currentThread().getName() + ":" + index);
          }
          });
          }
          executorService.shutdown();
        4. ScheduledThreadPool - 调度线程池

          特点:根据一定频率,时长定时执行某一任务。

          1
          2
          3
          4
          5
          6
          7
          ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
          scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
          @Override
          public void run() {
          System.out.println(new Date() + "延迟1秒执行,间隔3秒执行一次");
          }
          }, 1, 3, TimeUnit.SECONDS);