JAVA-线程

线程

  1. 主线程(单线程)

    • Person类

      package cn.xiaoge.day17.demo01;
      
      public class Person {
      
          private String name;
      
          public void run() {
              // 定义循环, 执行5次
              for (int i = 0; i < 5; i++) {
                  System.out.println(name + "--->" + i);
              }
          }
      
          public Person() {
          }
      
          public Person(String name) {
              this.name = name;
          }
      
          public String getName() {
              return name;
          }
      
          public void setName(String name) {
              this.name = name;
          }
      }
      
      
    • 执行路口

      package cn.xiaoge.day17.demo01;
      
      /*
          主线程: 执行主(main)方法的线程
      
          单线程程序: java程序中只有一个线程
          执行从main方法开始, 从上到下依次执行
      
          JVM执行main方法, main方法会进入到栈内存
          JVM会找操作系统开辟一条main方法通过cpu的执行路径
          而这个路径有一个名字, 叫main(主)线程
       */
      
      public class Demo01MainThread {
      
          public static void main(String[] args) {
              Person p1 = new Person("小强");
              p1.run();
      
              Person p2 = new Person("旺财");
              p2.run();
          }
      
      }
      
      // 运行结果
      小强--->0
      小强--->1
      小强--->2
      小强--->3
      小强--->4
      旺财--->0
      旺财--->1
      旺财--->2
      旺财--->3
      旺财--->4
      
  2. 创建多线程的第一种方式

    • MyThread类

      package cn.xiaoge.day17.demo02;
      
      // 1. 创建一个Thread类的子类
      
      public class MyThread extends Thread {
          // 2. 在Thread类的子类中重写Thread类中的run方法, 设置线程任务(开启线程要做什么?)
          @Override
          public void run() {
              for (int i = 0; i < 5; i++) {
                  System.out.println("run" + i);
              }
          }
      
      }
      
      
    • 执行路口

      package cn.xiaoge.day17.demo02;
      
      /*
          创建多线程程序的第一种方式: 创建Thread类的子类
          java.lang.Thread类: 是描述线程的类, 我们想实现多线程程序, 就必须继承Thread类
      
          实现步骤:
              1. 创建一个Thread类的子类
              2. 在Thread类的子类中重写Thread类中的run方法, 设置线程任务(开启线程要做什么?)
              3. 创建Thread类的子类对象
              4. 调用Thread类中的方法start方法, 开启新的线程, 执行run方法
                  void start() 使该线程开始执行: Java虚拟机调用该线程的run方法.
                  结果是两个线程并发地运行; 当线程(main线程)和另一个线程(创建的新线程, 执行其run方法)
                  多次启动一个线程是非法的. 特别是当线程已经结束执行后, 不能再重新启动.
      
          java程序属于抢占式调度, 那个线程的优先级高, 那个线程优先执行, 用一个优先级, 随机选择一个执行
       */
      
      public class Demo01Thread {
      
          public static void main(String[] args) {
              //  3. 创建Thread类的子类对象
              MyThread myThread = new MyThread();
              // 4. 调用Thread类中的方法start方法, 开启新的线程, 执行run方法
              myThread.start();
      
              for (int i = 0; i < 5; i++) {
                  System.out.println("main" + i);
              }
          }
      
      }
      
      // 运行结果
      main0
      main1
      run0
      run1
      run2
      run3
      run4
      main2
      main3
      main4
      
  3. 获取线程名称

    • MyThread类

      package cn.xiaoge.day17.demo03;
      
      /*
          获取线程的名称:
              1. 使用Thread类中的方法getName()
                  String getName() 返回该线程的名称.
              2. 可以先获取到当前正在执行的线程, 使用线程中的方法getName()获取线程的名称
                  static Thread currentThread() 返回对当前正在执行的线程对象的引用
       */
      
      // 定义一个Thread类的子类
      public class MyThread extends Thread {
      
          // 重写Tread类中的run方法, 设置线程任务
          @Override
          public void run() {
              // 获取线程名称--方法getName
              String name = this.getName();
              System.out.println(name);
          }
      
      
      }
      
      
    • 执行路口

      package cn.xiaoge.day17.demo03;
      
      /*
          线程名称:
              主线程: main
              新线程: Thread-0
       */
      
      public class Demo01GetThreadName {
      
          public static void main(String[] args) {
              // 创建Thread的子类对象
              MyThread mt = new MyThread();
              // 调用start方法, 开启新线程, 执行run方法
              mt.start(); // Thread-0
      
              MyThread mt2 = new MyThread();
              mt2.start(); // Thread-1
      
              // 返回对当前正在执行的线程对象的引用--方法currentThread
              Thread name = MyThread.currentThread();
              System.out.println(name.getName()); // main
          }
      
      }
      
      // 运行结果
      Thread-0
      Thread-1
      main
      
  4. 设置线程名称

    • package cn.xiaoge.day17.demo04;
      
      /*
          设置线程的名称: (了解)
              1. 使用Thread类中的方法setName(名字)
                  void setName(String name) 改变线程名称. 使之与参数 name 相同
              2. 创建一个带参数的构造方法, 参数传递线程的名称, 调用父类的带参构造方法, 把线程名称传递给父类, 让父类(Thread)给子线程起一个名字
                  Thread(String name) 分配新的 Thread 对象.
       */
      
      public class MyThread extends Thread {
      
          public MyThread() {
          }
      
          public MyThread(String name) {
              super(name);
          }
      
          @Override
          public void run() {
              // 获取线程的名称
              System.out.println(Thread.currentThread().getName());
          }
      
      }
      
      
    • 执行路口

      package cn.xiaoge.day17.demo04;
      
      public class Demo01SetThreadName {
      
          public static void main(String[] args) {
              // 开启多线程
              MyThread mt = new MyThread();
              mt.setName("小强"); // 设置线程名称
              mt.start();
      
              // 使用构造方法设置线程名称
              MyThread mt2 = new MyThread("旺财");
              mt2.start();
          }
      
      }
      
      // 运行结果
      旺财
      小强
      
  5. 线程睡眠

    • 执行路口

      package cn.xiaoge.day17.demo05;
      
      /*
          public static void sleep(long millis): 使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行).
          毫秒数结束之后, 线程继续执行
       */
      
      public class Demo01Sleep {
      
          public static void main(String[] args) {
              // 模拟秒表
              for (int i = 1; i <= 60; i++) {
                  System.out.println(i);
      
                  try{
                      // 使用Thread类的sleep方法让程序睡眠1秒钟
                      Thread.sleep(1000);
                  }catch (Exception e) {
                      e.printStackTrace();
                  }
      
              }
      
          }
      
      }
      
      // 运行结果 
      1-60 打印时间间隔1
  6. 创建多线程的第二种方式

    • RunnableImpl类

      package cn.xiaoge.day17.demo06;
      // 1. 创建一个Runnable接口的实现类
      public class RunnableImpl implements Runnable {
      
          // 2. 在实现类中重写Runnable接口的run方法, 设置线程任务
          @Override
          public void run() {
              for (int i = 0; i < 5; i++) {
                  System.out.println(Thread.currentThread().getName() + i);
              }
          }
      
      }
      
      
    • RunnableImpl2类

      package cn.xiaoge.day17.demo06;
      // 1. 创建一个Runnable接口的实现类
      public class RunnableImpl2 implements Runnable {
      
          // 2. 在实现类中重写Runnable接口的run方法, 设置线程任务
          @Override
          public void run() {
              for (int i = 0; i < 5; i++) {
                  System.out.println("Hello World" + i);
              }
          }
      
      }
      
      
    • 执行路口

      package cn.xiaoge.day17.demo06;
      
      /*
          创建多线程程序的第二种方式: 实现Runnable接口
          java.lang.Runnable
              Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现, 类必须定义一个称为 run 的无参数方法.
          java.lang.Thread类的构造方法
              Thread(Runnable target) 分配新的 Thread 对象.
              Thread(Runnable target, String name) 分配新的 Thread 对象.
      
          实现步骤:
              1. 创建一个Runnable接口的实现类
              2. 在实现类中重写Runnable接口的run方法, 设置线程任务
              3. 创建一个Runnable接口的实现对象
              4. 创建Thread类对象, 构造方法中传递Runnable接口的实现类对象
              5. 调用Thread类中的start方法, 让他开启新的线程执行run方法.
      
          实现Runnable接口创建多线程的好处:
              1. 避免了单继承的局限性
                  一个类只能继承一个类(一个人只能有一个爹), 类继承了Thread类就不能继承其他的类
                  实现了Runnable接口, 还可以继承其他的类, 实现其他的接口
              2. 增强了程序的扩展性, 降低了程序的耦合性(解耦)
                  实现Runnable接口的方式, 把设置线程任务和开启新线程进行了分离(解耦)
                  实现类中, 重写了run方法: 用来设置线程任务
                  创建Thread类对象, 调用start方法: 用来开启新的线程
       */
      
      public class Demo01Runnable {
      
          public static void main(String[] args) {
              // 3. 创建一个Runnable接口的实现对象
              RunnableImpl runnableImpl = new RunnableImpl();
      
              // 4. 创建Thread类对象, 构造方法中传递Runnable接口的实现类对象
              // Thread thread = new Thread(runnableImpl); // 打印线程名称
              Thread thread = new Thread(new RunnableImpl2()); // 打印HelloWorld
      
              // 5. 调用Thread类中的start方法, 让他开启新的线程执行run方法.
              thread.start();
      
              for (int i = 0; i < 5; i++) {
                  System.out.println(Thread.currentThread().getName() + i);
              }
          }
      
      }
      
      // 运行结果
      main0
      Hello World0
      Hello World1
      Hello World2
      Hello World3
      Hello World4
      main1
      main2
      main3
      main4
      
  7. 内部类线程

    • 执行路口

      package cn.xiaoge.day17.demo07;
      
      /*
          匿名内部类方式实现线程的创建
      
          匿名: 没有名字
          内部类: 写在其他类内部的类
      
          匿名内部类作用: 简化代码
              把子类继承父类, 重写父类的方法, 创建子类对象合成一步完成
              把实现类实现接口, 重写接口中的方法, 创建实现类对象合成一步完成
          匿名内部类的最终产物: 子类/实现类对象, 而这个类没有名字
      
          格式:
              new 父类/接口(){
                  重复父类/接口中的方法
              };
       */
      
      public class Demo01InnerClassThread {
      
          public static void main(String[] args) {
              // 线程的父类Thread
              // new MyThread().start();
      
              new Thread() {
                  // 重写run方法, 设置线程任务
                  @Override
                  public void run() {
                      for (int i = 0; i < 5; i++) {
                          System.out.println(Thread.currentThread().getName() + "--->" + "潇哥");
                      }
                  }
              }.start();
      
      
              // 线程的接口Runnable
              // Runnable r = new RunnableImpl(); // 多态
              new Thread(new Runnable(){
                  @Override
                  public void run() {
                      for (int i = 0; i < 5; i++) {
                          System.out.println(Thread.currentThread().getName() + "--->" + "程序员");
                      }
                  }
              }).start();
      
          }
      }
      
      // 运行结果
      Thread-0--->潇哥
      Thread-0--->潇哥
      Thread-0--->潇哥
      Thread-0--->潇哥
      Thread-0--->潇哥
      Thread-1--->程序员
      Thread-1--->程序员
      Thread-1--->程序员
      Thread-1--->程序员
      Thread-1--->程序员
      
  8. 同时开启3个线程卖票

    • RunnableImpl类

      package cn.xiaoge.day17.demo08;
      
      /*
         实现卖票案例
       */
      
      public class RunnableImpl implements Runnable {
          // 定义一个多线程共享的票源
          private int ticket = 100;
      
          // 设置线程任务: 卖票
          @Override
          public void run() {
              // 使用死循环, 让买票操作重复执行
              while(true) {
                  // 先判断是否存在
                  if(ticket > 0) {
                      // 提高安全问题出现的概率, 让程序睡眠
                      try {
                          Thread.sleep(10);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
      
                      // 票存在, 卖票 ticket--
                      System.out.println(Thread.currentThread().getName() + "-->正在买第: " + ticket + "张票");
                      ticket--;
                  }
              }
          }
      }
      
      
    • 执行路口

      package cn.xiaoge.day17.demo08;
      
      /*
          模拟卖票案例
          创建3个线程, 同时开启, 对共享的票进行出售
       */
      
      public class Demo01Ticket {
      
          public static void main(String[] args) {
              // 创建Runnable接口的实现类对象
              RunnableImpl run = new RunnableImpl();
              // 创建Thread类对象, 构造方法中传递Runnable接口的实现类对象
              Thread t0 = new Thread(run);
              Thread t1 = new Thread(run);
              Thread t2 = new Thread(run);
      
              // 调用start开启多线程
              t0.start();
              t1.start();
              t2.start();
          }
      
      }
      
      // 运行结果
      Thread-0-->正在买第: 100张票
      Thread-2-->正在买第: 99张票
      Thread-1-->正在买第: 98张票
      Thread-2-->正在买第: 97张票
      Thread-1-->正在买第: 97张票
      Thread-0-->正在买第: 97张票
      Thread-1-->正在买第: 94张票
      Thread-2-->正在买第: 94张票
      Thread-0-->正在买第: 92张票
      .....
      卖出去了重复票
      
  9. 使用同步技术—synchronized

    • RunnableImpl类

      package cn.xiaoge.day17.demo09;
      
      /*
         卖票案例出现了线程安全问题
         卖出了不存在的票和重复的票
      
         解决线程安全问题的一种方案: 使用同步代码块
         格式:
              synchronized(锁对象) {
                  可能会出现线程安全问题的代码(访问了共享数据的代码)
              }
         注意:
              1. 通过代码块中的锁对象, 可以使用任意的对象
              2. 但是必须保证多个线程使用的锁对象是同一个
              3. 锁对象作用:
                  把同步代码块锁住, 只让一个线程在同步代码块中执行
       */
      
      public class RunnableImpl implements Runnable {
          // 定义一个多线程共享的票源
          private int ticket = 100;
      
          // 创建锁对象
          Object obj = new Object();
      
          // 设置线程任务: 卖票
          @Override
          public void run() {
              // 使用死循环, 让买票操作重复执行
              while(true) {
                  synchronized(obj) {
                      // 先判断是否存在
                      if(ticket > 0) {
                          // 提高安全问题出现的概率, 让程序睡眠
                          try {
                              Thread.sleep(10);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
      
                          // 票存在, 卖票 ticket--
                          System.out.println(Thread.currentThread().getName() + "-->正在买第: " + ticket + "张票");
                          ticket--;
                      }
                  }
              }
      
          }
      }
      
      
    • 执行路口

      package cn.xiaoge.day17.demo09;
      
      /*
          模拟卖票案例
          创建3个线程, 同时开启, 对共享的票进行出售
       */
      
      public class Demo01Ticket {
      
          public static void main(String[] args) {
              // 创建Runnable接口的实现类对象
              RunnableImpl run = new RunnableImpl();
              // 创建Thread类对象, 构造方法中传递Runnable接口的实现类对象
              Thread t0 = new Thread(run);
              Thread t1 = new Thread(run);
              Thread t2 = new Thread(run);
      
              // 调用start开启多线程
              t0.start();
              t1.start();
              t2.start();
          }
      
      }
      
      // 运行结果
      Thread-0-->正在买第: 100张票
      Thread-0-->正在买第: 99张票
      Thread-0-->正在买第: 98张票
      Thread-0-->正在买第: 97张票
      Thread-0-->正在买第: 96张票
      Thread-0-->正在买第: 95张票
      Thread-0-->正在买第: 94张票
      Thread-0-->正在买第: 93张票
      Thread-0-->正在买第: 92张票
      ...
      没有重复票
      
  10. 静态同步方法

    • RunnableImpl类

      package cn.xiaoge.day17.demo10;
      
      /*
         卖票案例出现了线程安全问题
         卖出了不存在的票和重复的票
      
         解决线程安全问题的二种方案: 使用同步方法
         使用步骤:
              1. 把访问了共享数据的代码抽取出来, 放到一个方法中
              2. 在方法上添加synchronized修饰符
      
         格式: 定义方法的格式
         修饰符 synchronized 返回值类型 方法名称(参数列表) {
              可能会出现线程安全问题的代码(访问了共享数据的代码)
         }
       */
      
      public class RunnableImpl implements Runnable {
          // 定义一个多线程共享的票源
          private static int ticket = 100;
      
          // 创建锁对象
          Object obj = new Object();
      
          // 设置线程任务: 卖票
          @Override
          public void run() {
              // 使用死循环, 让买票操作重复执行
              while(true) {
                  payTicket();
              }
      
          }
      
          /*
              静态的同步方法
              锁对象是谁?
              不能是this
              this是创建对象之后产生的, 静态方法优先于对象
              静态方法的锁对象是本类的class属性-->class文件对象(反射)
           */
          public static synchronized void payTicketStatic() {
                  // 先判断是否存在
                  if(ticket > 0) {  // 静态访问变量也必须是静态变量
                      // 提高安全问题出现的概率, 让程序睡眠
                      try {
                          Thread.sleep(10);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
      
                      // 票存在, 卖票 ticket--
                      System.out.println(Thread.currentThread().getName() + "-->正在买第: " + ticket + "张票");
                      ticket--;
                  }
          }
      
      
          /*
              定义一个同步方法
              同步方法也会把方法内部的代码锁住
              只让一个线程执行
              同步方法的锁对象是谁?
              就是实现类对象 new RunnableImpl()
              也就是this
           */
          public /*synchronized*/ void payTicket() {
              synchronized(this) {
                  // 先判断是否存在
                  if(ticket > 0) {
                      // 提高安全问题出现的概率, 让程序睡眠
                      try {
                          Thread.sleep(10);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
      
                      // 票存在, 卖票 ticket--
                      System.out.println(Thread.currentThread().getName() + "-->正在买第: " + ticket + "张票");
                      ticket--;
                  }
              }
          }
      
          /*public synchronized void payTicket() {
                  // 先判断是否存在
                  if(ticket > 0) {
                      // 提高安全问题出现的概率, 让程序睡眠
                      try {
                          Thread.sleep(10);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
      
                      // 票存在, 卖票 ticket--
                      System.out.println(Thread.currentThread().getName() + "-->正在买第: " + ticket + "张票");
                      ticket--;
                  }
          }*/
      }
      
      
    • 执行路口

      package cn.xiaoge.day17.demo10;
      
      /*
          模拟卖票案例
          创建3个线程, 同时开启, 对共享的票进行出售
       */
      
      public class Demo01Ticket {
      
          public static void main(String[] args) {
              // 创建Runnable接口的实现类对象
              RunnableImpl run = new RunnableImpl();
              // 创建Thread类对象, 构造方法中传递Runnable接口的实现类对象
              Thread t0 = new Thread(run);
              Thread t1 = new Thread(run);
              Thread t2 = new Thread(run);
      
              // 调用start开启多线程
              t0.start();
              t1.start();
              t2.start();
          }
      
      }
      
      // 运行结果
      Thread-0-->正在买第: 100张票
      Thread-0-->正在买第: 99张票
      Thread-0-->正在买第: 98张票
      Thread-0-->正在买第: 97张票
      Thread-0-->正在买第: 96张票
      Thread-0-->正在买第: 95张票
      Thread-0-->正在买第: 94张票
      Thread-0-->正在买第: 93张票
      Thread-0-->正在买第: 92张票
      ...
      没有重复票
      
  11. 解决线程安全问题—Lock

    • RunnableImpl类

      package cn.xiaoge.day17.demo11;
      
      /*
         卖票案例出现了线程安全问题
         卖出了不存在的票和重复的票
      
         解决线程安全问题的三种方案: 使用Lock锁
         java.util.concurrent.locks.Lock接口
         Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作.
         Lock接口中的方法:
              void lock() 获取锁
              void unlock() 释放锁
      
         java.util.concurrent.locks.ReentrantLock implements Lock接口
      
         使用步骤:
              1. 在成员位置创建一个ReentrantLock对象
              2. 在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
              3. 在可能出现安全问题的代码后调用Lock接口中的方法unlock释放锁
       */
      
      import java.util.concurrent.locks.Lock;
      import java.util.concurrent.locks.ReentrantLock;
      
      public class RunnableImpl implements Runnable {
          // 定义一个多线程共享的票源
          private int ticket = 100;
      
          // 1. 在成员位置创建一个ReentrantLock对象
          Lock l = new ReentrantLock();
      
          // 创建锁对象
          Object obj = new Object();
      
          // 设置线程任务: 卖票
          @Override
          public void run() {
              // 使用死循环, 让买票操作重复执行
              while(true) {
                  // 2. 在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
                  l.lock();
                  payTicket(l);
              }
      
          }
      
          public void payTicket(Lock l) {
                  // 先判断是否存在
                  if(ticket > 0) {
                      // 提高安全问题出现的概率, 让程序睡眠
                      try {
                          Thread.sleep(10);
                          // 票存在, 卖票 ticket--
                          System.out.println(Thread.currentThread().getName() + "-->正在买第: " + ticket + "张票");
                          ticket--;
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      } finally {
                          //  3. 在可能出现安全问题的代码后调用Lock接口中的方法unlock释放锁
                          l.unlock(); // 无论程序是否异常, 都会释放锁
                      }
      
      
                  }
          }
      }
      
      
    • 执行路口

      package cn.xiaoge.day17.demo11;
      
      /*
          模拟卖票案例
          创建3个线程, 同时开启, 对共享的票进行出售
       */
      
      public class Demo01Ticket {
      
          public static void main(String[] args) {
              // 创建Runnable接口的实现类对象
              RunnableImpl run = new RunnableImpl();
              // 创建Thread类对象, 构造方法中传递Runnable接口的实现类对象
              Thread t0 = new Thread(run);
              Thread t1 = new Thread(run);
              Thread t2 = new Thread(run);
      
              // 调用start开启多线程
              t0.start();
              t1.start();
              t2.start();
          }
      
      }
      
      // 运行结果
      // 运行结果
      Thread-0-->正在买第: 100张票
      Thread-0-->正在买第: 99张票
      Thread-0-->正在买第: 98张票
      Thread-0-->正在买第: 97张票
      Thread-0-->正在买第: 96张票
      Thread-0-->正在买第: 95张票
      Thread-0-->正在买第: 94张票
      Thread-0-->正在买第: 93张票
      Thread-0-->正在买第: 92张票
      ...
      没有重复票
      
  12. Object类中的wait(等待)和notify(唤醒)和notifyAll(唤醒所有等待线程)

    • 执行路口

      package cn.xiaoge.day17.demo12;
      
      /*
          等待唤醒实例: 线程之间的通信
              创建一个顾客线程(消费者): 告知老板要的包子的种类和数量, 调用wait方法, 放弃cpu的执行, 进入到WAITING状态(无线等待)
              创建一个老板线程(生产者): 花了5秒做包子, 做好了包子之后, 调用notify方法, 唤醒顾客吃包子
      
          注意:
              顾客和老板线程必须使用同步代码块包裹起来, 保证等待和唤醒只能有一个在执行
              同步使用的锁对象必须保证唯一
              只有锁对象才能调用wait和notify方法
      
          Object类中的方法:
              void wait() 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前, 导致当前线程等待.
              void notify() 唤醒在此对象监视器上等待的单个线程. 会继续执行wait方法之后的代码
       */
      
      public class Demo01WaitAndNotify {
      
          public static void main(String[] args) {
              // 创建锁对象, 保证唯一
              Object obj = new Object();
      
              // 创建一个顾客线程(消费者)
              new Thread() {
                  @Override
                  public void run() {
                      // 保证等待和唤醒的线程只能有一个执行, 需要使用同步技术
                      synchronized(obj) {
                          System.out.println("告知老板要的包子的种类和数量");
                          try {
                              // 调用wait方法, 放弃cpu的执行, 进入到WAITING状态(无线等待)
                              obj.wait();
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                          // 唤醒之后执行的代码
                          System.out.println("包子已经做好了, 开吃!");
                      }
                  }
              }.start();
      
              // 创建老板的线程(生产者)
              new Thread() {
                  @Override
                  public void run() {
                      try {
                          // 花5秒做包子
                          Thread.sleep(5000);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
      
                      // 保证等待和唤醒的线程只能有一个执行, 需要使用同步技术
                      synchronized(obj) {
                          System.out.println("老板5秒钟之后做好包子, 告知顾客, 可以吃包子了");
                          // 做好包子之后, 调用notify方法, 唤醒顾客吃包子
                          obj.notify();
                      }
                  }
              }.start();
          }
      
      }
      
      // 运行结果
      告知老板要的包子的种类和数量
      // ---等待了5秒---
      老板5秒钟之后做好包子, 告知顾客, 可以吃包子了
      包子已经做好了, 开吃!
      
    • 执行路口

      package cn.xiaoge.day17.demo12;
      
      /*
          进入到TimeWaiting(计时等待)有两种方法
              1. 使用sleep(long m)方法, 在毫秒值结束之后, 线程睡醒进入到Runnable/Blocked状态
              2. 使用wait(long m)方法, wait方法如果在毫秒值结束之后, 还没有被notify唤醒, 就会自动醒来,
                  线程睡醒进入到Runnable/Blocked状态
      
          唤醒的方法:
              void notify() 唤醒在此对象监视器上等待的单个线程.
              void notifyAll() 唤醒在此对象监视器上等待的所有线程.
       */
      
      public class Demo02WaitAndNotify {
      
          public static void main(String[] args) {
      
              Object obj = new Object();
      
              // 创建一个顾客线程(消费者)
              new Thread() {
                  @Override
                  public void run() {
                      // 保证等待和唤醒的线程只能有一个执行, 需要使用同步技术
                      synchronized(obj) {
                          System.out.println("顾客1告知老板要的包子的种类和数量");
                          try {
                              // 调用wait方法, 放弃cpu的执行, 进入到WAITING状态(无线等待)
                              obj.wait();
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                          // 唤醒之后执行的代码
                          System.out.println("包子已经做好了, 顾客1开吃!");
                      }
                  }
              }.start();
      
              // 创建一个顾客线程(消费者)
              new Thread() {
                  @Override
                  public void run() {
                      // 保证等待和唤醒的线程只能有一个执行, 需要使用同步技术
                      synchronized(obj) {
                          System.out.println("顾客2告知老板要的包子的种类和数量");
                          try {
                              // 调用wait方法, 放弃cpu的执行, 进入到WAITING状态(无线等待)
                              obj.wait();
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                          // 唤醒之后执行的代码
                          System.out.println("包子已经做好了, 顾客2开吃!");
                      }
                  }
              }.start();
      
              // 创建老板的线程(生产者)
              new Thread() {
                  @Override
                  public void run() {
                      try {
                          // 花5秒做包子
                          Thread.sleep(5000);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
      
                      // 保证等待和唤醒的线程只能有一个执行, 需要使用同步技术
                      synchronized(obj) {
                          System.out.println("老板5秒钟之后做好包子, 告知顾客, 可以吃包子了");
                          // 做好包子之后, 调用notify方法, 唤醒顾客吃包子
                          // obj.notify(); 如果有多个等待线程, 随机唤醒一个
                          obj.notifyAll(); // 唤醒所有等待的线程
                      }
                  }
              }.start();
          }
      
      }
      
      // 运行结果
      顾客1告知老板要的包子的种类和数量
      顾客2告知老板要的包子的种类和数量
      老板5秒钟之后做好包子, 告知顾客, 可以吃包子了
      包子已经做好了, 顾客2开吃!
      包子已经做好了, 顾客1开吃!
      
  13. 案列

    • BaoZi类

      package cn.xiaoge.day17.demo13;
      
      /*
          资源类: 包子类
          设置包子的属性
              皮
              陷
              包子的状态: 有 true, 没有 false
       */
      
      public class BaoZi {
      
          // 皮
          String pi;
          // 陷
          String xian;
          // 包子状态: 有 true, 没有 false
          boolean flag = false;
      
      }
      
      
    • BaoZiPu类

      package cn.xiaoge.day17.demo13;
      
      /*
          生产者(包子铺)类: 是一个线程类, 可以继承Thread
          设置线程任务(run): 生产包子
          对包子的状态进行判断
      
          true: 有包子
              包子铺调用wait方法进入等待状态
          false: 没有包子
              包子铺生产包子
              增加一些趣味性: 交替生产两种包子
                  有两种状态(i % 2 == 0)
              包子铺生产好了包子
              修改包子的状态为true 有
              唤醒吃货线程, 让吃货线程吃包子
      
          注意:
              包子铺线程和包子线程关系-->通信(互斥)
              必须使用同步技术保证两个线程只能有一个在执行
              锁对象必须保证唯一, 可以使用包子对象作为锁对象
              包子铺和吃货这个类就需要把包子对象作为参数传递进来
                  1. 需要在成员位置创建一个包子变量
                  2. 使用带参数构造方法, 为这个包子变量赋值
       */
      
      public class BaoZiPu extends Thread {
      
          // 锁对象包子
          private BaoZi baoZi;
      
          public BaoZi getBoZi() {
              return baoZi;
          }
      
          public void setBoZi(BaoZi baoZi) {
              this.baoZi = baoZi;
          }
      
          public BaoZiPu(BaoZi baoZi) {
      
              this.baoZi = baoZi;
          }
      
          @Override
          public void run() {
              // 定义一个变量
              int count = 0;
      
              // 让包子铺一直生产包子
              while(true){
                  // 必须使用同步技术保证两个线程只能有一个在执行
                  synchronized (this.baoZi) {
                      // 对包子的状态进行判断
                      if (this.baoZi.flag) {
                          // 有包子
                          try {
                              // 唤醒吃货线程
                              this.baoZi.wait();
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                      }
      
                      // 被唤醒之后执行, 包子铺生产包子
                      // 增加一些趣味性: 交替生产两种包子
                      if (count % 2 == 0) {
                          // 生产 薄皮三鲜馅包子
                          this.baoZi.pi = "薄皮";
                          this.baoZi.xian = "三鲜馅";
                      } else {
                          // 生产 冰皮 牛肉大葱馅
                          this.baoZi.pi = "冰皮";
                          this.baoZi.xian = "牛肉大葱馅";
                      }
      
                      count++;
      
                      System.out.println("包子铺正在生产: " + this.baoZi.pi + this.baoZi.xian + "包子");
                      // 生产包子需要3秒钟
      
                      try {
                          Thread.sleep(3000);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
      
                      // 包子铺生产好了包子
                      // 修改包子的状态为true 有
                      this.baoZi.flag = true;
      
                      // 唤醒吃货线程, 让吃货线程吃包子
                      this.baoZi.notify();
                      System.out.println("包子铺已经生产好了: " + this.baoZi.pi + this.baoZi.xian + "包子, 吃货可以开始吃了");
                  }
              }
      
          }
      }
      
      
    • ChiHuo类

      package cn.xiaoge.day17.demo13;
      
      /*
          消费者(吃货)类: 是一个线程类, 可以继承Thread
          设置线程任务(run): 吃包子
          对包子的状态进行判断
          false: 没有包子
              吃货调用wait方法进入等待状态
          true: 有包子
              吃货吃包子
              吃货吃完包子
              修改包子的状态为false
              吃货唤醒包子铺线程, 生产包子
       */
      
      public class ChiHuo extends Thread {
      
          // 锁对象 包子
          private BaoZi baoZi;
      
          public ChiHuo(BaoZi baoZi) {
              this.baoZi = baoZi;
          }
      
          public BaoZi getBoZi() {
              return baoZi;
          }
      
          public void setBoZi(BaoZi baoZi) {
              this.baoZi = baoZi;
          }
      
          @Override
          public void run() {
              // 使用死循环, 让吃货一直吃包子
              while(true){
                  // 必须使用同步技术保证两个线程只能有一个在执行
                  synchronized (this.baoZi) {
                      // 对包子的状态进行判断
                      if (this.baoZi.flag) {
                          // 被唤醒之后执行的代码, 吃包子
                          System.out.println("吃货正在吃: " + this.baoZi.pi + this.baoZi.xian + "的包子");
                          // 有包子, 吃包子
                          // 修改包子的状态为false
                          this.baoZi.flag = false;
                          // 吃货唤醒包子铺线程, 生产包子
                          this.baoZi.notify();
                          System.out.println("吃货已经把: " + this.baoZi.pi + this.baoZi.xian + "的包子吃完了, 包子铺开始生产包子");
                      }
      
                      // 没有包子 吃货调用wait方法进入等待状态
                      try {
                          this.baoZi.wait();
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                      System.out.println("===================================================");
                  }
              }
          }
      }
      
      
    • 执行路口

      package cn.xiaoge.day17.demo13;
      
      /*
          测试类:
              包含main方法, 程序执行入口, 启动程序
              创建包子对象;
              创建包子铺线程, 开启, 生产包子;
              创建吃货线程, 开启, 吃包子;
       */
      
      public class Demo {
      
          public static void main(String[] args) {
              // 创建包子对象
              BaoZi baoZi = new BaoZi();
      
              new BaoZiPu(baoZi).start();
      
              new ChiHuo(baoZi).start();
      
          }
      
      }
      
      // 运行结果
      包子铺正在生产: 薄皮三鲜馅包子
      包子铺已经生产好了: 薄皮三鲜馅包子, 吃货可以开始吃了
      吃货正在吃: 薄皮三鲜馅的包子
      吃货已经把: 薄皮三鲜馅的包子吃完了, 包子铺开始生产包子
      包子铺正在生产: 冰皮牛肉大葱馅包子
      包子铺已经生产好了: 冰皮牛肉大葱馅包子, 吃货可以开始吃了
      ===================================================
      吃货正在吃: 冰皮牛肉大葱馅的包子
      吃货已经把: 冰皮牛肉大葱馅的包子吃完了, 包子铺开始生产包子
      包子铺正在生产: 薄皮三鲜馅包子
      .....
      
  14. 线程池

    • package cn.xiaoge.day17.demo14;
      
      public class RunnableImpl implements Runnable {
      
          @Override
          public void run() {
              System.out.println(Thread.currentThread().getName() + "创建了一个新的线程执行");
          }
      }
      
      
    • 执行路口

      package cn.xiaoge.day17.demo14;
      
      /*
          线程池: JDK1.5之后提供的
          java.util.concurrent.Executors: 线程池的工厂类, 用来生成线程池
      
          Executors类中的静态方法:
              static ExecutorService newFixedThreadPool(int nThreads) 创建一个可重用固定线程数的线程池
              参数:
                  int nThreads: 创建线程池中包含的线程数量
              返回值:
                  ExecutorService接口, 返回的是ExecutorService接口的实现类对象, 我们可以使用ExecutorService接口接收(面向接口编程)
      
          java.util.concurrent.ExecutorService: 线程池接口
              用来从线程池中获取线程, 调用start方法, 执行线程任务
                  submit(Runnable task) 提交一个 Runnable 任务用于执行
              关闭/销毁线程池的方法
                  void shutdown()
      
          线程池的使用步骤:
              1. 使用线程池的工厂类Executors里边提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
              2. 创建一个类, 实现Runnable接口, 重写run方法, 设置线程任务
              3. 调用ExecutorService中的方法submit, 传递线程任务(实现类), 开启线程, 执行run方法
              4. 调用ExecutorService中的方法shutdown销毁线程池(不建议使用)
       */
      
      import java.util.concurrent.ExecutorService;
      import java.util.concurrent.Executors;
      
      public class Demo01ThreadPool {
      
          public static void main(String[] args) {
              // 1. 使用线程池的工厂类Executors里边提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
              ExecutorService es = Executors.newFixedThreadPool(2);
      
              // 3. 调用ExecutorService中的方法submit, 传递线程任务(实现类), 开启线程, 执行run方法
              es.submit(new RunnableImpl()); // pool-1-thread-2创建了一个新的线程执行
              es.submit(new RunnableImpl()); // pool-1-thread-1创建了一个新的线程执行
              // 线程池会一直开启, 使用完了线程, 会自动把线程归还给线程池, 线程可以继续使用
              es.submit(new RunnableImpl()); // pool-1-thread-2创建了一个新的线程执行
      
              // 4. 调用ExecutorService中的方法shutdown销毁线程池(不建议使用)
              es.shutdown();
      
              // es.submit(new RunnableImpl()); // 抛异常RejectedExecutionException, 线程池都没有了, 就不能获取线程了
          }
      
      }
      
      // 运行结果
      pool-1-thread-2创建了一个新的线程执行
      pool-1-thread-1创建了一个新的线程执行
      pool-1-thread-1创建了一个新的线程执行
      
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

只因为你温柔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值