章节8-线程

112 阅读4分钟

进程和线程

  • 进程

    • 一个程序代表一个进程
  • 线程

    • 一个进程中需要多个线程共同配合, 使程序正常运行.
    • 程序执行流的最小单元
  • 多线程的作用

    • 需求: 打印0-100的数字, 不需要考虑顺序

    • /**
       * 若不要考虑打印顺序,
       *      一个线程打印0-100,这一个线程需要打印100个数才算执行结束
       *
       *      若是两个线程,则可以分工,两个线程分别打印奇数与偶数.
       *      如此,若两个线程同时执行与结束,单个线程只需要打印50个数字,效率要高的多  
       */
      
    • 使用一个线程打印0-100

    • //使用一个线程打印0-100
      @Test
      public void test01() { //main作为程序的启动, 使其可以调度thread线程对象执行
          //创建线程对象
          Thread thread = new Thread(() -> {
              for (int i = 0; i <= 100; i++) {
                  System.out.println(i);
              }
          });
      
          //启动线程
          thread.start();
      }
      
    • 使用两个个线程打印0-100

    • //使用两个个线程打印0-100
      @Test
      public void test02() { //main作为程序的启动, 使其可以调度两个线程对象执行
          //创建线程对象thread1, 使其打印奇数
          Thread thread1 = new Thread(() -> {
              for (int i = 1; i <= 100; i+=2) {
                  System.out.println(i);
              }
          });
      
          //启动线程
          thread1.start();
      
          //创建线程对象thread2, 使其打印偶数
          Thread thread2 = new Thread(() -> {
              for (int i = 0; i <= 100; i+=2) {
                  System.out.println(i);
              }
          });
      
          //启动线程
          thread2.start();
      }
      

Java开启线程的方式

  • 继承Thread类

  • 实现Runnable接口 (主推)

    • //创建线程对象
          Thread thread = new Thread(() -> {
              for (int i = 0; i <= 100; i++) {
                  System.out.println(i);
              }
          });
      
          //启动线程
          thread.start();
      }
      
  • 实现Callable接口

    • 线程任务有返回值

线程相关方法

  • 方法名称说明
    String getName()返回此线程的名称
    void setName(String name)设置线程的名字(构造方法也可以设置名字)
    static Thread currentThread()获取当前线程的对象
    static void sleep(long time)让线程休眠指定的时间,单位为毫秒

线程安全和同步

  • 线程安全问题出现的条件

    • 是多线程环境
    • 有共享数据
    • 有多条语句操作共享数据
  • 解决方案: 加锁(同步) --> 将共享数据进行封锁, 当多条线程同时执行时, 只放行一条线程处理, 将其他线程进行阻塞, 当前一条线程处理完后, 让所有线程重新抢夺执行权, 每次只放行一条线程, 即可以避免线程安全问题

    • 同步代码块

      • 锁对象可以是任意对象,但是需要保证多条线程的锁对象,是同一把锁, 一般使用当前线程对象的字节码对象(类只加载一次)

      • //买票场景
        synchronized (TicketsTask.class) {
            if (ticket == 0) {
                break;
            }
            System.out.println(Thread.currentThread().getName() + "卖出第:" + ticket + "张票");
            ticket--;
        }
        
    • 同步方法

      • 在方法的返回值类型前面加入 synchronized 关键字

      • public synchronized void method() {}
        
      • 面试题:

        • 静态方法的锁对象是字节码对象,非静态方法的锁对象是 this
    • Lock锁

      • Lock 是接口,无法直接创建对象

      • Lock lock= new ReentrantLock();
        
      • 成员方法说明
        void lock()加锁
        void unlock();释放锁

线程通讯 (等待唤醒机制)

  • 作用:

    • 确保线程能够按照预定的顺序执行并且能够安全地访问共享资源
    • 使多条线程更好的进行协同工作
  • Object类中的方法

    • void wait()			使当前线程等待
      
      void notify()		随机唤醒单个等待的线程
      
      void notifyAll()	唤醒所有等待的线程
      
  • ReentrantLock接口 : 监视器

    • 创建方式

      • Condition condition = lock.newCondition();
        
    • 成员方法说明
      void await()指定线程等待
      void signal();指定唤醒单个等待的线程
    • 代码示例 :

    • class Printer{
          int flag = 1;
      
          Lock lock = new ReentrantLock();
      
          //获取监视器
          Condition c1 = lock.newCondition();
          Condition c2 = lock.newCondition();
          Condition c3 = lock.newCondition();
      
      
          public void print1() throws InterruptedException {
              lock.lock();
              if (flag != 1) {
                  c1.await();
              }
      
              System.out.println("黑马程序员");
      
              flag = 2;
              c2.signal();
      
              lock.unlock();
          }
      
          public void print2() throws InterruptedException {
              lock.lock();
              if (flag != 2) {
                  c2.await();
              }
      
              System.out.println("传智教育");
      
              flag = 3;
              c3.signal();
              lock.unlock();
          }
      
          public void print3() throws InterruptedException {
              lock.lock();
              if (flag != 3) {
                  c3.await();
              }
      
              System.out.println("传智大学");
              
              flag = 1;
              c1.signal();
      
              lock.unlock();
          }
      }
      

线程生命周期

  • image-20241218210553343.png

线程池

  • 概述

    • 系统创建一个线程的成本是比较高的,因为它涉及到与操作系统交互
    • 当程序中需要创建大量生存期很短暂的线程时,频繁的创建和销毁线程,就会严重浪费系统资源
  • 自定义线程池

    • 代码示例:

    • public class MyPool {
          public static void main(String[] args) {
              ThreadPoolExecutor pool = new ThreadPoolExecutor(
                      2,         //核心线程数量
                      5,                    //总线程数量(当线程对象与大于核心线程数 + 任务队列数, 开始创建临时线程)
                      60,                  //临时变量空闲时间后销毁
                      TimeUnit.SECONDS,   //时间单位
                      new LinkedBlockingDeque<>(10),    //任务队列
                      Executors.defaultThreadFactory(),        //线程对象工厂(默认)
                      new ThreadPoolExecutor.AbortPolicy()    //拒绝策略(当线程对象大于总线程数 + 任务队列数量时执行)
              );
      
              for (int i = 0; i < 10; i++) {
                  pool.submit(new Runnable() {
                      @Override
                      public void run() {
                          System.out.println(Thread.currentThread().getName()+"提交了线程任务..");
                      }
                  });
              }
          }
      }
      
    • 参数概述

      • image-20241218211139018.png
    • 拒绝策略

      • 策略选项说明
        ThreadPoolExecutor.AbortPolicy丢弃任务并抛出RejectedExecutionException异常 (默认)
        ThreadPoolExecutor.DiscardPolicy丢弃任务,但是不抛出异常 这是不推荐的做法
        ThreadPoolExecutor.DiscardOldestPolicy抛弃队列中等待最久的任务 然后把当前任务加入队列中
        ThreadPoolExecutor.CallerRunsPolicy调用任务的run()方法, 绕过线程池直接执行