第8章 多线程

8.1 线程简介

1 、多任务

  • 现实生活中多件事一起作。

  • 在程序中是指在一个系统中可以同时进行多个进程,即有多个单独运行的任务,每一个任务对应一个进程。

  • 每一个进程都有一段专用的内存区域,即使是多次启动同一段程序产生不同的进程也是如此。

2、多线程

  • Java 给多线程编程提供了内置的支持。 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

  • 多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销。

  • 多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。

  • 主线程和子线程交替执行

3、程序、进程、线程

  • 程序

    • 程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。

  • 进程

    • 一个应用程序(1个进程是一个软件)

    • 是执行程序的一次执行过程,是一个流动的概念。是系统资源分配的单位。

    • 一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。

  • 线程

    • 一个进程中的执行场景/执行单元。

    • 线程是CPU调度和执行的单位。

    • 一个线程不能独立的存在,它必须是进程的一部分。

  • 注意:

    • 一个进程可以有多个线程

    • 线程是独立的执行路径。

    • 在程序与运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程

    • main()称为主线程,为系统的入口,用于执行整个程序;

    • 在一个进程中,如果开辟了多个进程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能认为的干预的。

    • 对于同一份资源操作,会存在资源抢夺的问题,需要加入并发控制。

    • 线程会带来额外的开销,CPU的调度时间、并发控制开销。

    • 每一个线程在自己的工作内存交互,内存控制不当会造成数据不一致。

8.2 线程创建

  • 三种创建方式:

    • Thread class 继承Thread类

    • Runnable接口 实现Runnable接口

    • Callable接口 实现Callable接口

8.2.1 Thread类

  1. 创建一个新的执行线程的方法,将一个声明为Thread的子类。

  2. 自定义线程类继承Thread类

  3. 重写run()方法,编写线程执行体

  4. 创建线程对象,调用start()方法启动线程。

  5. 注意:

    • 线程开启不一定立即执行,由CPU调度执行

    • 启动线程:子类对象.start()

    • 不建议使用,为了避免OOP单继承的局限性

  6. 实例

    package Demo032;

    /**
    * @Author: H-YONG-8
    * @DATA: 2023/4/20
    * JavaSE
    * 创建多线程 继承Thread类
    */
    public class TestThread1 extends Thread{
       @Override
       public void run() {
           for (int i = 0; i < 20; i++) {
               System.out.println("我在看代码-----"+i);
          }
      }

       public static void main(String[] args) {
           //main 主线程

           //创建一个线程对象
           TestThread1 testThread1 = new TestThread1();

           //调用start()方法开启线程
           testThread1.start();
           for (int i = 0; i < 20; i++) {
               System.out.println("我在学习多线程---"+i);
          }
      }
    }

    //多条执行路径,主线程和子线程并行交替执行

    执行结果:
    我在学习多线程---0
    我在看代码-----0
    我在学习多线程---1
    我在看代码-----1
    我在学习多线程---2
    我在看代码-----2
    我在看代码-----3
    我在学习多线程---3
    我在看代码-----4
    我在看代码-----5
    我在学习多线程---4
    我在学习多线程---5
    我在看代码-----6
    我在学习多线程---6
    我在看代码-----7
    我在学习多线程---7
    我在看代码-----8
    我在学习多线程---8
    我在看代码-----9
    我在学习多线程---9
    我在看代码-----10
    我在学习多线程---10
    我在学习多线程---11
    我在学习多线程---12
    我在学习多线程---13
    我在学习多线程---14
    我在学习多线程---15
    我在学习多线程---16
    我在学习多线程---17
    我在学习多线程---18
    我在学习多线程---19
    我在看代码-----11
    我在看代码-----12
    我在看代码-----13
    我在看代码-----14
    我在看代码-----15
    我在看代码-----16
    我在看代码-----17
    我在看代码-----18
    我在看代码-----19

8.2.2 Runnable

  1. 创建一个线程是声明实现类Runnable接口

  2. 定义MyRunable类实现Runnable接口

  3. 实现run()方法,编写线程执行体

  4. 创建线程对象,调用start()方法启动线程

  5. 注意:

    • 启动线程:传入目标对象+Thread对象.start()

    • 推荐使用,避免单继承局限性,灵活方便,方便同一个对象被多个线程使用。

  6. 实例:

    package Demo032;

    /**
    * @Author: H-YONG-8
    * @DATA: 2023/4/20
    * JavaSE
    * 实现Runnable接口线程
    */
    public class TestThread2 implements Runnable{
       
       //重写方法
       @Override
       public void run() {
           for (int i = 0; i < 20; i++) {
               System.out.println("我在看代码-----"+i);
          }
      }

       public static void main(String[] args) {
           //main 主线程

           //创建一个线程对象
           TestThread2 testThread2 = new TestThread2();
           //代理,通过线程对象来开启线程
           Thread thread = new Thread(testThread2);
           thread.start();
           for (int i = 0; i < 20; i++) {
               System.out.println("我在学习多线程---"+i);
          }
      }
    }



    执行结果:
    我在学习多线程---0
    我在学习多线程---1
    我在看代码-----0
    我在学习多线程---2
    我在看代码-----1
    我在学习多线程---3
    我在看代码-----2
    我在学习多线程---4
    我在学习多线程---5
    我在学习多线程---6
    我在看代码-----3
    我在学习多线程---7
    我在学习多线程---8
    我在学习多线程---9
    我在学习多线程---10
    我在学习多线程---11
    我在学习多线程---12
    我在学习多线程---13
    我在学习多线程---14
    我在学习多线程---15
    我在学习多线程---16
    我在学习多线程---17
    我在学习多线程---18
    我在看代码-----4
    我在学习多线程---19
    我在看代码-----5
    我在看代码-----6
    我在看代码-----7
    我在看代码-----8
    我在看代码-----9
    我在看代码-----10
    我在看代码-----11
    我在看代码-----12
    我在看代码-----13
    我在看代码-----14
    我在看代码-----15
    我在看代码-----16
    我在看代码-----17
    我在看代码-----18
    我在看代码-----19
  7. 案例:

    package Demo032;

    /**
    * @Author: H-YONG-8
    * @DATA: 2023/4/20
    * JavaSE
    *多个线程同时操作同一个对象
    * 买火车票的例子
    */
    public class TestThread4 implements Runnable{

       private int a = 10;

       @Override
       public void run() {
           while (true){
               if (a<=0){
                   break;
              }
               System.out.println(Thread.currentThread().getName()+"拿到了"+a--+"票");
          }

      }

       public static void main(String[] args) {
           TestThread4 b = new TestThread4();
           new Thread(b,"小明").start();
           new Thread(b,"老师").start();
           new Thread(b,"黄牛").start();
           new Thread(b,"警察").start();
      }
    }

    执行结果:
    小明拿到了9票
    老师拿到了8票
    警察拿到了7票
    黄牛拿到了10票
    警察拿到了4票
    警察拿到了2票
    警察拿到了1票
    老师拿到了5票
    小明拿到了6票
    黄牛拿到了3票
  8. 实例:龟兔赛跑

    package Demo032;

    /**
    * @Author: H-YONG-8
    * @DATA: 2023/4/20
    * JavaSE
    * 龟兔赛跑
    * 首先来个赛道距离,然后要离距离越来越近
    * 判断比赛是否结束
    * 打印出胜利者
    * 龟兔赛跑开始
    * 模拟兔子睡觉
    * 乌龟赢得比赛
    */
    public class TestThread3 implements Runnable{

       //胜利者
       private static String winner;

       @Override
       public void run() {

           //跑步开始
           for (int i = 0; i <= 100; i++) {

               //模拟兔子睡觉
               if(Thread.currentThread().getName().equals("兔子") && i%10 == 0){
                   try {
                       Thread.sleep(1);
                  } catch (InterruptedException e) {
                       e.printStackTrace();
                  }

              }

               //判断比赛是否结束
               boolean flag = gameOver(i);
               //如果比赛结束了,就停止程序
               if(flag){
                   break;
              }
               System.out.println(Thread.currentThread().getName()+"---->跑了"+i+"步");
          }

      }

       //判断是否完成比赛
       private boolean gameOver(int steps){
           //判断是否有胜利者
           if(winner!=null){//已经存在胜利者
               return true;
          }{
               if (steps >= 100){
                   winner = Thread.currentThread().getName();
                   System.out.println("winner ="+winner);
                   return true;
              }
          }
           return false;
      }

       public static void main(String[] args) {

           //设置赛道
           TestThread3 race = new TestThread3();

           new Thread(race,"兔子").start();
           new Thread(race,"乌龟").start();

      }
    }


    运行结果:
    乌龟---->跑了0步
    乌龟---->跑了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步
    兔子---->跑了0步
    乌龟---->跑了58步
    兔子---->跑了1步
    兔子---->跑了2步
    兔子---->跑了3步
    兔子---->跑了4步
    兔子---->跑了5步
    兔子---->跑了6步
    兔子---->跑了7步
    兔子---->跑了8步
    兔子---->跑了9步
    乌龟---->跑了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步
    乌龟---->跑了87步
    乌龟---->跑了88步
    乌龟---->跑了89步
    乌龟---->跑了90步
    乌龟---->跑了91步
    乌龟---->跑了92步
    乌龟---->跑了93步
    乌龟---->跑了94步
    乌龟---->跑了95步
    乌龟---->跑了96步
    乌龟---->跑了97步
    乌龟---->跑了98步
    兔子---->跑了10步
    兔子---->跑了11步
    兔子---->跑了12步
    兔子---->跑了13步
    兔子---->跑了14步
    兔子---->跑了15步
    兔子---->跑了16步
    兔子---->跑了17步
    兔子---->跑了18步
    兔子---->跑了19步
    乌龟---->跑了99步
    winner =乌龟

8.2.3 实现Callable接口

  1. 实现Callable接口,需要返回值类型

  2. 重写call方法,需要抛出异常

  3. 创建目标对象

  4. 创建执行服务:ExecutorService ser =Executors.newFixedThreadPool(1);

  5. 提交执行:Futre < Boolean>result1 =ser.submit(t1);

  6. 获取结果:boolean r1 = result1.get()

  7. 关闭服务:ser.shutdownNow();

  8. 实例

      public class CallableTest {
         public static void main(String[] args) throws ExecutionException, InterruptedException,TimeoutException{
             //创建一个线程池
             ExecutorService executor = Executors.newCachedThreadPool();
             Future<String> future = executor.submit(()-> {
                     TimeUnit.SECONDS.sleep(5);
                     return "CallableTest";
            });
             System.out.println(future.get());
             executor.shutdown();
        }
    }

8.3 lamda表达式和静态代理模式

8.3.1 lamda表达式

  1. 希腊字母表中排序第十一位的字母,英文名为Lambda

  2. 避免匿名内部类定义过多。

  3. 代理模式其实就是通过一个类去代替另一个类去做一些操作。

  4. 其实质属于函数式编程的概念

    (params) ->expression[表达式]
    (params) ->statement[语句]
    (params) ->{statements}
  5. 为什么要用lambda表达式

    • 避免匿名内部内定义过多

    • 代码更加简洁

    • 去掉没有意义的代码,只留下核心代码

  6. Functional Interface(函数式接口)是lamda表达式的关键。

    • 函数式接口:任何接口,如果只包含唯一一个抽象的方法,那他就是函数式接口

  7. 实例:

    package Demo032;

    /**
    * @Author: H-YONG-8
    * @DATA: 2023/4/21
    * JavaSE
    * 推导lambda表达式
    */
    public class TestThread5 {

       //3静态内部类,实现类
       static class Like2 implements Ilike{
           @Override
           public void lambda(){
               System.out.println("i like lambda2");
          }
      }

       public static void main(String[] args) {
           Ilike like = new Like();
           like.lambda();

           like=new Like2();
           like.lambda();


           //4局部内部类
           class Like3 implements Ilike{
               @Override
               public void lambda(){
                   System.out.println("i like lambda3");
              }
          }
           like=new Like3();
           like.lambda();

           //5匿名内部类
           like = new  Ilike() {
               @Override
               public void lambda() {
                   System.out.println("i like lambda4");
              }
          };
           like.lambda();

           //6用lambda简化
           like = ()->{
               System.out.println("i like lambda5");
          };
           like.lambda();

      }
    }

    //1定义一个函数式接口
    interface Ilike{
       void lambda();
    }
    //2实现类
    class Like implements Ilike{
       @Override
       public void lambda(){
           System.out.println("i like lambda");
      }
    }
  8. 总结:

    • lambda表达式只能有一行代码的情况下才能简化成一行,如果有多行,就要用代码块包裹

    • 前提是函数式接口,只有一个方法。

    • 多个参数类型也可以去掉参数类型,要去掉都要去掉。

8.3.2 静态代理模式

  1. 为其他对象提供一个代理以控制对这个对象的访问。

  2. 主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。

  3. 代理模式的元素是:共同接口、代理对象、目标对象。

  4. 代理模式的行为:由代理对象执行目标对象的方法、由代理对象扩展目标对象的方法。

  5. 代理模式的宏观特性:对客户端只暴露出接口,不暴露它以下的架构。

  6. 好处多多:中间隔离了一层,更加符合开闭原则

  7. 流程图

  8. 实例:

    package Demo032;

    /**
    * @Author: H-YONG-8
    * @DATA: 2023/4/21
    * JavaSE
    */
    public class TeatThread6 {

       public static void main(String[] args) {
           WeddingCompany weddingCompany = new WeddingCompany(new You());
           weddingCompany.HappyMarry();
      }
    }

    interface Marry{
       void HappyMarry();
    }

    //真实角色
    class You implements Marry{
       @Override
       public void HappyMarry() {
           System.out.println("小明要结婚了");
      }
    }

    //代理角色,帮助结婚
    class WeddingCompany implements Marry{

       //代理真实角色
       private Marry target;
       //构造函数
       public WeddingCompany(Marry target) {
           this.target = target;
      }

       @Override
       public void HappyMarry() {

           before();
           this.target.HappyMarry();//这就是真实对象
           after();
      }

       private void after() {
           System.out.println("结婚之后");
      }

       private void before() {
           System.out.println("结婚前");
      }
    }
  9. 总结:

    • 真实对象和代理对象都要事先同一个接口

    • 代理对象要代理真实角色,

    • 好处:

      • 代理对象可以做真实对象做不了的事情

      • 真实对象专注于自己的事情

8.4 线程状态

  • 线程五大状态

  • 线程方法

    方法说明
    setPriority(int newPriority) 更改现成的优先级
    static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠
    void join() 等待线程终止
    static void yield() 暂停当前正在执行的线程对象,并执行其他的线程
    void interrupt() 中断线程,不要用这个方法
    boolean isAliven() 测试线程是否处于活动状态

8.4.1 停止线程

  1. 不推荐使用JDK提供的stop()、destory()方法。

  2. 推荐让线程自己停下来

  3. 建议使用一个标志未进行终止变量当flag=false,则终止线程运行。

  4. 测试停止线程

    package Demo032;

    /**
    * @Author: H-YONG-8
    * @DATA: 2023/4/21
    * JavaSE
    * 测试停止线程
    * 注意死循环
    */
    public class TestStop implements Runnable{

       //1.设置一个标识位
       private boolean flag = true;

       //重写方法
       @Override
       public void run() {
           int i = 0;
           while (flag){
               System.out.println("线程正在运行"+i++);
          }
      }

       //2设置一个公开的方法,停止线程,转换标志位
       public void stop(){
           this.flag = false;
      }

       public static void main(String[] args) {

           TestStop testStop = new TestStop();
           new Thread(testStop).start();

           for (int i = 0; i < 1000; i++) {
               System.out.println("main"+i);
               if(i == 900){
                   //调用stop方法,切换标志位,让线程停止
                   testStop.stop();
                   System.out.println("线程该停止了");
              }
          }
      }
    }

8.4.2 线程休眠

  1. sleep(时间)指定当前线程阻塞的毫秒数;

  2. sleep存在异常InterruptedExcepiton;

  3. sleep时间到达后线程进入就绪状态;

  4. sleep可以模拟网络延迟,倒计时等

  5. 每一个对象都有一个锁,sleep不会释放锁

  6. 倒计时实例

    package Demo032;

    import java.text.SimpleDateFormat;
    import java.util.Date;

    /**
    * @Author: H-YONG-8
    * @DATA: 2023/4/21
    * JavaSE
    */
    public class TestSleep2 {
       public static void main(String[] args) throws InterruptedException {
           tenDwon();

       //模拟倒计时
       public static void tenDwon() throws InterruptedException {
           int num = 10;

           while (true){
               Thread.sleep(1000);
               System.out.println(num--);
               if (num<=0){
                   break;
              }
          }
      }
    }
  7. 获取当前时间

    package Demo032;

    import java.text.SimpleDateFormat;
    import java.util.Date;

    /**
    * @Author: H-YONG-8
    * @DATA: 2023/4/21
    * JavaSE
    */
    public class TestSleep2 {
       public static void main(String[] args){
           //获取系统当前时间
           Date startTime = new Date(System.currentTimeMillis());
           while (true) {
               try {
                   Thread.sleep(1000);
                   //更新系统时间
                   System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
                   startTime = new Date(System.currentTimeMillis());
              } catch (InterruptedException e) {
                   e.printStackTrace();
              }
          }

      }
    }

8.4.3 线程礼让

  1. 礼让线程,让当前正在执行的线程暂停,但不阻塞

  2. 将线程从运行状态转为就绪状态

  3. 让CPU重新调度,礼让不一定成功

  4. 实例

    package Demo032;

    /**
    * @Author: H-YONG-8
    * @DATA: 2023/4/21
    * JavaSE
    * 礼让线程
    */
    public class TestYield {

       public static void main(String[] args) {
           MyYield myYield = new MyYield();
           new Thread(myYield,"a").start();
           new Thread(myYield,"b").start();

      }
    }

    class MyYield implements Runnable{
       @Override
       public void run() {
           System.out.println(Thread.currentThread().getName()+"线程开始执行");
           Thread.yield();//礼让
           System.out.println(Thread.currentThread().getName()+"线程停止执行");
      }
    }

8.4.4 Join(插队)

  1. Join合并线程,待此线程执行完成后,在执行其他的线程,其他的线程阻塞

  2. 可以想象为插队

  3. 实例

    package Demo032;

    /**
    * @Author: H-YONG-8
    * @DATA: 2023/4/21
    * JavaSE
    * 插队
    */
    public class TestJoin implements Runnable{
       @Override
       public void run() {
           for (int i = 0; i < 1000; i++) {
               System.out.println("线程VIP来了"+i);
          }
      }

       public static void main(String[] args) throws InterruptedException {
           
           //启动线程
           TestJoin testJoin = new TestJoin();
           Thread thread =new Thread(testJoin);
           thread.start();
           
           //主线程
           for (int i = 0; i < 500; i++) {
               if(i == 200){
                   thread.join();//插队
              }
               System.out.println("main"+i);
          }
      }
    }

8.4.5 线程状态观测

  • 线程状态

  1. new:尚未启动的线程处于此状态。

  2. RUNNABLE:在JAVA虚拟机中执行的线程处于此状态。

  3. BLOCKED:被阻塞等待监视器锁定的线程处于此状态。

  4. WAITING:正在等待另一个线程执行特定动作的线程处于此状态。

  5. TIMED_WAITING:正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。

  6. TERMINATED:已退出的线程处于此状态。

  • 一个线程可以在给定的时间处于一个状态。这些状态是不反映任何操作系统线程状态的虚拟机状态。

  • 实例

    package Demo032;

    /**
    * @Author: H-YONG-8
    * @DATA: 2023/4/21
    * JavaSE
    */
    public class TestState {

       public static void main(String[] args) throws InterruptedException {
           Thread thread = new Thread(()->{
               for (int i = 0; i < 5; i++) {
                   try {
                       Thread.sleep(1000);
                  } catch (InterruptedException e) {
                       e.printStackTrace();
                  }
              }
               System.out.println("////");
          });
           
           //观察状态
           Thread.State state= thread.getState();
           System.out.println(state);//new

           //观察启动后
           thread.start();//启动线程
           state = thread.getState();
           System.out.println(state);//run

           while (state != Thread.State.TERMINATED){//只要线程不终止,就一直输出状态
               Thread.sleep(100);
               state = thread.getState();//跟新线程状态
               System.out.println(state);
          }

      }
    }


    运行结果
    NEW
    RUNNABLE
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    TIMED_WAITING
    ////
    TERMINATED

8.4.6 线程优先级

  1. Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪一个线程来执行。

  2. 线程的优先级用数字表示,范围从1~10.

    • Thread.MIN_PRIORITY = 1;

    • Thread.MAX_PRIORITY = 10;

    • Thread.NORM_PRIORITY =5;

  3. 使用以下方式改变或者获取优先级

    • getPriority().setPriority(int xxx)

  4. 实例

    package Demo032;

    /**
    * @Author: H-YONG-8
    * @DATA: 2023/4/21
    * JavaSE
    * 测试线程优先级
    */
    public class TestPriority {

       public static void main(String[] args) {
           System.out.println(Thread.currentThread().getName()+"--->"+Thread.currentThread().getPriority());

           MyPriority myPriority = new MyPriority();
           Thread t1 = new Thread(myPriority);
           Thread t2 = new Thread(myPriority);
           Thread t3 = new Thread(myPriority);
           Thread t4 = new Thread(myPriority);
           Thread t5 = new Thread(myPriority);
           Thread t6 = new Thread(myPriority);

           //先设置优先级
           t1.start();

           t2.setPriority(1);
           t2.start();

           t3.setPriority(4);
           t3.start();

           t4.setPriority(Thread.MAX_PRIORITY);
           t4.start();

           t5.setPriority(8);
           t5.start();

           t6.setPriority(3);
           t6.start();
      }

    }
    class MyPriority implements Runnable{

       @Override
       public void run() {
           System.out.println(Thread.currentThread().getName()+"--->"+Thread.currentThread().getPriority());
      }
    }

8.4.7 守护线程

  1. 线程分为用户线程和守护线程

  2. 虚拟机必须确保用户线程执行完毕

  3. 虚拟机不用等待守护线程执行完毕

  4. 实例

    package Demo032;

    /**
    * @Author: H-YONG-8
    * @DATA: 2023/4/21
    * JavaSE
    * 测试守护线程
    */
    public class TestDaemon {
       public static void main(String[] args) {
           God god = new God();
           You1 you = new You1();
           Thread thread = new Thread(god);
           thread.setDaemon(true);

           thread.start();
           new Thread(you).start();
      }

    }

    //上帝
    class God implements Runnable{
       @Override
       public void run() {
           while (true){
               System.out.println("上帝保佑着你");
          }
      }
    }


    //你
    class You1  implements Runnable{
       @Override
       public void run() {
           for (int i = 0; i < 365000; i++) {
               System.out.println("你一生都开心的活着");
          }
           System.out.println("====goodbye!word!=======");
      }
    }

8.5 线程同步

8.5.1 并发

  1. 并发:同一个对象被多个线程同时操作。

  2. 所谓并发编程是指在一台处理器上 “同时” 处理多个任务。并发是在同一实体上的多个事件。多个事件在同一时间间隔发生。

  3. 并发编程,从程序设计的角度来说,是希望通过某些机制让计算机可以在一个时间段内,执行多个任务。从计算机 CPU 硬件层面来说,是一个或多个物理 CPU 在多个程序之间多路复用,提高对计算机资源的利用率。从调度算法角度来说,当任务数量多于 CPU 的核数时,并发编程能够通过操作系统的任务调度算法,实现多个任务一起执行。

  4. 并发编程有三大特性:

    • 原子性;

    • 可见性;

    • 有序性。

8.5.2 线程同步

  1. 线程有自己的私有数据,比如栈和寄存器,同时与其它线程共享相同的虚拟内存和全局变量等资源。 在一般情况下,创建一个线程是不能提高程序的执行效率的,所以要创建多个线程。但是当多个线程同时读写同一份共享资源的时候,会引起冲突,例如在多个线程同时对同一个内存地址进行写入,由于CPU时间调度上的问题,写入数据会被多次的覆盖,所以就要使线程同步。这时候就需要引入线程同步机制使各个线程排队一个一个的对共享资源进行操作,而不是同时进行。

  2. 简单的说就是,在多线程编程里面,一些数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性。

  3. 处理多线程问题时,多线程访问同一个对象,并且某些线程还想修改这个对象。这个时候我们就需要线程同步。

  4. 线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成对列,等待前面线程使用完毕,下一个线程再使用。

  5. 由于同一进程的多个线程共享一块存储空间,在方便的同时,也带来了访问冲突,为了保证数据在方法中被访问时的正确性,再访问时加入锁机制,当一个线程获得对象的排他锁,多占资源,其他线程必须等待,使用后释放锁即可。

    • 会损失性能

    • 优先级倒置

  6. 线程不安全案例

    package Demo032;

    /**
    * @Author: H-YONG-8
    * @DATA: 2023/4/22
    * JavaSE
    */
    public class UnsafeBuyTicket {

       public static void main(String[] args) {
           BuyTicket station = new BuyTicket();
           new Thread(station,"小明").start();
           new Thread(station,"黄牛").start();
           new Thread(station,"老师").start();
           new Thread(station,"教师").start();

      }
    }

    class BuyTicket implements Runnable{

       //票
       private int tikeNums = 10;
       boolean flag = true;
       @Override
       public void run() {
           //买票
           while (flag){
               try {
                   buy();
              } catch (InterruptedException e) {
                   e.printStackTrace();
              }
          }

      }

       private void buy() throws InterruptedException {
           //判断是否有票
           if(tikeNums<=0){
               flag = false;
               return;
          }
           //模拟延时
           Thread.sleep(100);

           //买票
           System.out.println(Thread.currentThread().getName()+"拿到"+tikeNums--);
      }
    }


    执行结果:
    教师拿到10
    小明拿到7
    老师拿到8
    黄牛拿到9
    老师拿到6
    黄牛拿到4
    小明拿到5
    教师拿到6
    教师拿到2
    老师拿到3
    小明拿到1
    黄牛拿到0
    教师拿到-1
    老师拿到-2
  7. 同步方法

    • 由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是syncyhronized关键字,包括两种用法:同步方法、块

  8. 同步的弊端:

    • 方法里面需要修改的内容才需要锁,锁太多,浪费资源

  9. 同步块

    • 同步块:syncyhronized(obj){}

    • obj称为同步监视器

      • obj可以是任何对象,但是推荐使用共享资源作为同步监视器。

      • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class

    • 同步监视器的执行过程

      • 第一个线程访问,锁定同步监视器,执行其中的代码

      • 第二个线程访问,发现同步监视器被锁定,无法访问

      • 第一个线程访问完毕,解锁同步监视器

      • 第二个线程访问完毕,发现同步监视器没有锁,然后锁定并访问。

8.5.3 队列和锁

1、队列

  • 队列是一个先进先出的抽象数据结构,可类比于生活中的排队场景。通常情况下,队列有数组和链表两种实现方式。

  • 采用链表实现的队列,没有个数限制。插入元素时直接接在链表的尾部,取出元素时直接从链表的头部取出即可。

  • 采用数组实现的队列,通常是循环数组,受限于数组的大小,存在天然的个数上限。插入和取出元素时,必须采用队列头部指针和队列尾部指针进行队列满和队列空的判断。

  • Java 定义了队列的基本操作,接口类型为 java.util.Queue,接口定义如下所示。Queue 定义了两套队列操作方法:

    • add、remove、element 操作失败抛出异常;

    • offer 操作失败返回 false 或抛出异常,poll、peek 操作失败返回 null;

2、锁

  • 同步操作的实现,需要给对象关联一个互斥体,这个互斥体就可以叫做锁。

  • 死锁

    • 线程之间相互等着对方释放资源,而自己的资源又不释放给别人,这种情况就是死锁。所以,只要其中一线程释放了资源,死锁就会被解除。

    • 实例

      package Demo032;

      /**
      * @Author: H-YONG-8
      * @DATA: 2023/4/22
      * JavaSE
      * 死锁
      */
      public class DeadLock {
         public static void main(String[] args) {
             Makeup g1 = new Makeup(0,"小明");
             Makeup g2 = new Makeup(1,"小红");

             g1.start();
             g2.start();

        }
      }

      //口红
      class Lipstick{

      }

      //镜子
      class Mirror{

      }
      class Makeup extends Thread{

         //需要的资源
         static  Lipstick lipstick = new Lipstick();
         static Mirror mirror = new Mirror();

         int chose;
         String girlname;

         Makeup(int chose,String girlname){
             this.chose = chose;
             this.girlname = girlname;
            }





         @Override
         public void run() {
             try {
                 makeup();
            } catch (InterruptedException e) {
                 e.printStackTrace();
            }
        }

         //化妆
         private void makeup() throws InterruptedException {
             if (chose == 0) {
                 synchronized (lipstick){//获得口号的锁
                     System.out.println(this.girlname+"获得口号的锁");
                     Thread.sleep(1000);
                }
                 synchronized (mirror){
                     System.out.println(this.girlname+ "获得镜子的锁");
                }
            }else {
                 synchronized (mirror){//获得口号的锁
                     System.out.println(this.girlname+"获得镜子的锁");
                     Thread.sleep(2000);
                }
                 synchronized (lipstick){
                     System.out.println(this.girlname+ "获得口号的锁");
                }
            }
        }
      }

    • 产生死锁的四个必要条件

      • 互斥条件:一个资源只能被一个进程使用

      • 请求与保持条件:一个进程因请求资源而阻塞时,对以获得的资源不放手

      • 不剥夺条件:进程以获得的资源,在未使用完前,不能强行剥夺

      • 循环等待条件:若干进程之间形成一个头尾相接的循环等待资源关系。

  • 重入锁

    • 重入锁指的是,一个线程在拥有了当前资源的锁之后,可以再次拿到该锁而不被阻塞。在后面会讲到synchronized的重入锁原理。

  • 自旋锁

    • 自旋锁指的是,线程在没有获得锁时,不是被直接挂起,而是执行一个空循环(自旋)。默认是循环10次。

    • 自旋锁的目的也就是为了减少线程被挂起的几率,因为线程的挂起和唤醒也都是耗资源的操作。

    • 如果锁被另一个线程占用的时间比较长,即使自旋了之后当前线程还是会被挂起,空循环就会变成浪费系统资源的操作,反而降低整体性能。所以,自旋锁是不适应锁占用时间长的并发情况的。

  • 自适应自旋锁

    • 自适应自旋锁是对自锁锁的一种优化。当一个线程自旋后成功获得了锁,那么下次自旋的次数就会增加。因为虚拟机认为,既然上次自旋期间成功拿到了锁,那么后面的自旋会有很大几率拿到锁。相反,如果对于某个锁,很少有自旋能够成功获得的,那么后面就会减少自旋次数,甚至省略掉自旋过程,以免浪费处理器资源。这种锁是默认开启的。

  • 锁消除

    • 锁消除指的是,在编译期间利用“逃逸分析技术”分析出那些不存在竞争却加了锁的代码的锁失效。这样就减少了锁的请求与释放操作,因为锁的请求与释放都会消耗系统资源。

3、两者关系

  • 队列和锁在一起解决线程安全性

8.5.4 Lock(锁)

  • 显示加锁,可以知道位置

  • 代码实例:

    package dead;

    import java.util.concurrent.locks.ReentrantLock;

    public class TestLock {
       public static void main(String[] args) {
           TestLock2 testLock2=new TestLock2();
           new Thread(testLock2).start();
           new Thread(testLock2).start();
           new Thread(testLock2).start();

      }
    }
    //定义Lock

    //买票的线程
    class TestLock2 implements Runnable{
       //定义Lock
       private final ReentrantLock lock= new ReentrantLock();
       int num=10;
       @Override
       public void run() {
           while (true){
               try {
                   lock.lock();
                   if (num>0){
                       try {
                           Thread.sleep(1000);
                      } catch (InterruptedException e) {
                           e.printStackTrace();
                      }
                       System.out.println(num--);
                  }
                   else {
                       break;
                  }

              } finally {
                   lock.unlock();
              }


          }

      }
    }

     

 

 

8.6 线程通信问题

 

  • 实例

    /**
    * 测试:生产者消费者模型-->利用缓冲区解决:管程法
    */
    public class Demo33_ThreadPC {
       public static void main(String[] args) {
           SynContainer synContainer = new SynContainer();
           new Producer(synContainer).start();
           new Consumer(synContainer).start();
      }
    }

    //生产者
    class Producer extends Thread {
       //容缓冲区
       SynContainer container;

       public Producer(SynContainer container) {
           this.container = container;
      }

       //生产
       @Override
       public void run() {
           for (int i = 0; i < 100; i++) {
               container.push(new Product(i));
               System.out.println("生产了" + i + "件产品");
          }
      }
    }

    //消费者
    class Consumer extends Thread {
       //容缓冲区
       SynContainer container;

       public Consumer(SynContainer container) {
           this.container = container;
      }

       //消费
       @Override
       public void run() {
           for (int i = 0; i < 100; i++) {
               System.out.println("消费了-->" + container.pop().id + "件产品");
          }
      }
    }

    //产品
    class Product {
       int id;//产品编号

       public Product(int id) {
           this.id = id;
      }
    }

    //缓冲区
    class SynContainer {
       //需要一个容器大小
       Product[] products = new Product[10];
       //容器计数器
       int count = 0;

       //生产者放入产品
       public synchronized void push(Product product) {
           //如果容器满了,需要等待消费者消费
           /*如果是if的话,假如消费者1消费了最后一个,这是index变成0此时释放锁被消费者2拿到而不是生产者拿到,这时消费者的wait是在if里所以它就直接去消费index-1下标越界,如果是while就会再去判断一下index得值是不是变成0了*/
           while (count == products.length) {
               //通知消费者消费,等待生产
               try {
                   this.wait();
              } catch (InterruptedException e) {
                   e.printStackTrace();
              }
          }
           //如果没有满,需要丢入产品
           products[count] = product;
           count++;
           //通知消费者消费
           this.notifyAll();
      }

       //消费者消费产品
       public synchronized Product pop() {
           //判断是否能消费
           while (count <= 0) {
               //等待生产者生产
               try {
                   this.wait();
              } catch (InterruptedException e) {
                   e.printStackTrace();
              }
          }
           //如果可以消费
           count--;
           Product product = products[count];
           //吃完了 通知生产者生产
           this.notifyAll();
           return product;
      }
    }

     

8.7 线程池

//测试线程池
public class Demo35_ThreadPool {
   public static void main(String[] args) {
       // 1. 创建服务,擦行间线程池
       // newFixedThreadPool(线程池大小)
       ExecutorService service = Executors.newFixedThreadPool(10);
       //执行
       service.execute(new MyThread());
       service.execute(new MyThread());
       service.execute(new MyThread());
       service.execute(new MyThread());
       service.execute(new MyThread());
       service.execute(new MyThread());
       //关闭连接
       service.shutdown();
  }
}

class MyThread implements Runnable {
   @Override
   public void run() {
       System.out.println(Thread.currentThread().getName());
  }
}

热门相关:无量真仙   仗剑高歌   天启预报   薄先生,情不由己   寂静王冠