IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 开发工具 -> 并发一-线程 -> 正文阅读

[开发工具]并发一-线程

作者:token annotation punctuation

并发

1.进程与线程

1.1 进程

  • 程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至 CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理 IO 的。
  • 当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程。
  • 进程就可以视为程序的一个实例。大部分程序可以同时运行多个实例进程(例如记事本、画图、浏览器 等),也有的程序只能启动一个实例进程(例如网易云音乐、360 安全卫士等)

1.2 线程

  • 一个进程之内可以分为一到多个线程。
  • 一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给 CPU 执行 。
  • Java 中,线程作为小调度单位,进程作为资源分配的最小单位。 在 windows 中进程是不活动的,只是作 为线程的容器

1.3 两者对比

  • 进程基本上相互独立的,而线程存在于进程内,是进程的一个子集

  • 进程拥有共享的资源,如内存空间等,供其内部的线程共享

  • 进程间通信较为复杂

    • 同一台计算机的进程通信称为 IPC(Inter-process communication)

    • 不同计算机之间的进程通信,需要通过网络,并遵守共同的协议,例如 HTTP

  • 线程通信相对简单,因为它们共享进程内的内存,一个例子是多个线程可以访问同一个共享变量

  • 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低

2. 并行和并发

2.1 并发

  • image-20210728105349998

  • 单核 cpu 下,线程实际还是 串行执行 的。操作系统中有一个组件叫做任务调度器,将 cpu 的时间片(windows 下时间片最小约为 15 毫秒)分给不同的程序使用,只是由于 cpu 在线程间(时间片很短)的切换非常快,人类感 觉是 同时运行的 。总结为一句话就是: 微观串行,宏观并行 , 一般会将这种 线程轮流使用 CPU 的做法称为并发, concurrent

  • CPU时间片一时间片二时间片三时间片四
    core线程一线程二线程三线程四
  • 共享变量+读写操作+多线程=临界区

2.2并行

  • image-20210728105847140

  • 多核 cpu下,每个 核(core) 都可以调度运行线程,这时候线程可以是并行的。

2.3 应用(异步)

  • 以调用方角度来讲,如果

  • 需要等待结果返回,才能继续运行就是同步

  • 不需要等待结果返回,就能继续运行就是异步

  • 设计

    • 多线程可以让方法执行变为异步的(即不要巴巴干等着)比如说读取磁盘文件时,假设读取操作花费了 5 秒钟,如 果没有线程调度机制,这 5 秒 cpu 什么都做不了,其它代码都得暂停…
  • 结论

    • 比如在项目中,视频文件需要转换格式等操作比较费时,这时开一个新线程处理视频转换,避免阻塞主线程
    • tomcat 的异步 servlet 也是类似的目的,让用户线程处理耗时较长的操作,避免阻塞 tomcat 的工作线程
    • ui 程序中,开线程进行其他操作,避免阻塞 ui 线程
  • 提高效率

    • 单核 cpu 下,多线程不能实际提高程序运行效率,只是为了能够在不同的任务之间切换,不同线程轮流使用 cpu ,不至于一个线程总占用 cpu,别的线程没法干活
    • 多核 cpu 可以并行跑多个线程,但能否提高程序运行效率还是要分情况的 有些任务,经过精心设计,将任务拆分,并行执行,当然可以提高程序的运行效率。但不是所有计算任 务都能拆分(参考后文的【阿姆达尔定律】) 也不是所有任务都需要拆分,任务的目的如果不同,谈拆分和效率没啥意
    • IO 操作不占用 cpu,只是我们一般拷贝文件使用的是【阻塞 IO】,这时相当于线程虽然不用 cpu,但需要一 直等待 IO 结束,没能充分利用线程。所以才有后面的【非阻塞 IO】和【异步 IO】优化,(这种情况虽然不占用CPU,但是会使线程一直阻塞,使线程利用率降低,而非阻塞和异步可以有效地提升线程的利用率);CPU只需要通知DMA就可以,然后等到数据准备好之后触发中断,告知CPU数据准备好了

3. JAVA线程

3.1 创建和运行线程

方法一:直接使用Thread

  • 直接使用Thread

    • package com.sunyang.concurrentstudy;
      
      import lombok.extern.slf4j.Slf4j;
      
      /**
       * @Author: sunyang
       * @Date: 2021/7/28
       * @Description:
       */
      @Slf4j(topic = "c.CreateThreadDemo")
      public class CreateThreadDemo {
          public static void main(String[] args) {
      
              Thread thread = new Thread(() -> log.debug("running"));
              thread.setName("thread-0");
              thread.start();
              log.debug("running");
      
          }
      
      }
      

方法二:使用Runnable

  • 线程任务(要执行的代码)分开

  • Thread代表线程

  • Runnable可运行的任务(线程要执行的代码)

    • package com.sunyang.concurrentstudy;
      
      import lombok.extern.slf4j.Slf4j;
      
      /**
       * @program: ConcurrentStudy
       * @description: Demo
       * @author: SunYang
       * @create: 2021-07-28 19:18
       **/
      @Slf4j(topic = "c.RunnableDemo")
      public class RunnableDemo {
          public static void main(String[] args) {
              Runnable runnable = () -> log.debug("runnable-running");
              Thread thread = new Thread(runnable, "thread-runnable");
              thread.start();
              log.debug("main-running");
          }
      }
      

Thread和Runnable联系

  • image-20210728193714438

  • image-20210728193637257

  • 如果通过Runnable方法创建的线程,实际上是调用的Thread中的run()

  • new Thread()是通过创建子类实现父类Thread的run() 方法

  • Runnable属于组合方式,new Thread属于继承方式 组合优于继承

  • 方法1 是把线程和任务合并在了一起,方法2 是把线程和任务分开了

  • 用 Runnable 更容易与线程池等高级 API 配合

  • 用 Runnable 让任务类脱离了 Thread 继承体系,更灵活

方法三:FutureTask

  • package com.sunyang.concurrentstudy;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
    
    /**
     * @program: ConcurrentStudy
     * @description: Demo
     * @author: SunYang
     * @create: 2021-07-28 19:48
     **/
    @Slf4j(topic = "c.FutureTaskDemo")
    public class FutureTaskDemo {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    log.debug("running-thread");
                    Thread.sleep(2000);
                    return 100;
                }
            });
    
            Thread thread = new Thread(task, "task");
            thread.start();
            log.debug("{}", task.get());
        }
    }
    
  • // 输出
    Connected to the target VM, address: '127.0.0.1:59755', transport: 'socket'
    19:53:51 [task] c.FutureTaskDemo - running-thread
    19:53:53 [main] c.FutureTaskDemo - 100
    Disconnected from the target VM, address: '127.0.0.1:59755', transport: 'socket'
    
    Process finished with exit code 0
    

3.2 线程运行原理

线程上下文切换

  • 因为一下一些原因导致CPU不再执行当前的线程,转而执行另一个线程的代码

    • 线程的CPU时间片用完了
    • 垃圾回收(看垃圾回收器,正常时FullGC)
    • 有更高优先级的线程需要执行
    • 线程自己调用了sleep, yield, wait, join, park, synchronized,lock等方法
  • 当Context Switch发生时,需要操作系统保存当前线程的状态(保护现场),并恢复另一个线程的状态,Java中对应的概念就是程序计数器,他的作用是记录下一条JVM指令的执行地址,是线程私有的

  • 线程状态包括:程序计数器,虚拟机栈中的每个栈帧的信息,如局部变量,操作数栈,返回地址,动态链接等

  • Context Switch 频繁发生会影响性能

3.3 常见方法

  • image-20210728203259767

3.3.1 start与run

  • 直接调用run() 就是调用的普通方法

  • package com.sunyang.concurrentstudy;
    
    import lombok.extern.slf4j.Slf4j;
    
    /**
     * @program: ConcurrentStudy
     * @description: Demo
     * @author: SunYang
     * @create: 2021-07-28 20:40
     **/
    @Slf4j(topic = "c.RunDemo")
    public class RunDemo {
        public static void main(String[] args) {
            Thread thread = new Thread(){
                @Override
                public void run() {
                    log.debug("run");
                }
            };
            thread.run();
        }
    }
    
  • 结果

  • 20:45:16 [main] c.RunDemo - run   // 是mian线程执行的run
    
    Process finished with exit code 0
    

3.3.2 sleep与yield

sleep
  • 调用sleep会让当前线程从Running 状态进入到Timed Waiting(有时限的等待)状态

    • package com.sunyang.concurrentstudy;
      
      import lombok.extern.slf4j.Slf4j;
      
      /**
       * @program: ConcurrentStudy
       * @description: Dmeo
       * @author: SunYang
       * @create: 2021-07-28 21:05
       **/
      @Slf4j(topic = "c.Demo")
      public class ThreadSleepDemo {
          public static void main(String[] args) {
              Thread thread = new Thread("t1"){
                  @Override
                  public void run() {
                      try {
                          Thread.sleep(2000);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                  }
              };
              log.debug("{}", thread.getState()); //  21:12:07 [main] c.Demo - NEW
              thread.start();
              log.debug("{}", thread.getState());  //	21:12:07 [main] c.Demo - RUNNABLE
              try {
                  Thread.sleep(1000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              log.debug("{}", thread.getState());  //	21:12:08 [main] c.Demo - TIMED_WAITING
          }
      }
      
      
      //   输出
      //  21:12:07 [main] c.Demo - NEW
      //	21:12:07 [main] c.Demo - RUNNABLE
      //	21:12:08 [main] c.Demo - TIMED_WAITING
      
  • 其他线程可以使用interrupt方法打断正在睡眠的线程,这时sleep方法会抛出InterruptedException

    • package com.sunyang.concurrentstudy;
      
      import lombok.extern.slf4j.Slf4j;
      
      /**
       * @program: ConcurrentStudy
       * @description: Demo
       * @author: SunYang
       * @create: 2021-07-28 21:15
       **/
      @Slf4j(topic = "c.Demo")
      public class SleepInterruptDemo {
          public static void main(String[] args) throws InterruptedException {
           Thread t1 = new Thread(() -> {
               log.debug("enter sleep");
               try {
                   Thread.sleep(2000);
               } catch (InterruptedException e) {
                   log.debug("wake up ....");
                   e.printStackTrace();
               }
           }, "t1"
           );
      
           t1.start();
           Thread.sleep(1000);
           log.debug("interrupt..");
           t1.interrupt();
      
          }
      }
      
    • 输出

    • 21:20:41 [t1] c.Demo - enter sleep
      21:20:42 [main] c.Demo - interrupt..
      21:20:42 [t1] c.Demo - wake up ....
      java.lang.InterruptedException: sleep interrupted
      	at java.lang.Thread.sleep(Native Method)
      	at com.sunyang.concurrentstudy.SleepInterruptDemo.lambda$main$0(SleepInterruptDemo.java:17)
      	at java.lang.Thread.run(Thread.java:748)
      
      Process finished with exit code 0
      
  • 睡眠结束后的线程未必会立刻得到执行

  • 建议用TimeUnit的sleep来代替Thread的sleep来获得更好的可读性

    • TimeUnit.SECONDS.sleep(1);
      
yield
  • 调用yeild会让当前线程从Running(运行)转为Runnable(就绪)状态,然后调度其他线程
  • 具体的实现依赖于操作系统的任务调度器(如果当前没有别的线程,那么还会继续执行这个线程,或者他刚让出CPU的使用权,立刻就又获得了使用权,那么还会继续执行)
区别
  • sleep(阻塞状态)不能获得CPU时间片,分配时间片时不会考虑阻塞状态
  • yeild(就绪状态)有机会获得CPU时间片
线程优先级
  • 线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽视它
  • 如果CPU比较忙,那么优先级高的线程可能会获得更多的时间片,但CPU闲时,优先级几乎没用
  • yeild也是如此。
sleep使用
  • 在没有利用CPU来计算时,不要让while(true)空转浪费CPU,这时可以使用yeild和sleep来让出CPU的使用权给其他程序(防止CPU占用100%,单核情况下)
  • 可以用wait或条件变量达到类似效果,
  • 不同的是,后两种都需要加锁,并且需要相应的唤醒操作,一般适用于要进行同步的场景
  • sleep适用于无锁同步的场景

3.3.3 join

  • package com.sunyang.concurrentstudy;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.TimeUnit;
    
    /**
     * @program: ConcurrentStudy
     * @description: Demo
     * @author: SunYang
     * @create: 2021-07-28 22:05
     **/
    @Slf4j(topic = "c.Demo")
    public class JoinDemo {
        static int r = 0;
        public static void main(String[] args) {
            Thread thread = new Thread(() -> {
                log.debug("开始");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                r = 10;
                log.debug("结束");
            });
            thread.start();
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("结果{}", r);
        }
    }
    
  • image-20210728221001598

应用之同步
  • 需要等待结果返回,才能继续运行就是i同步
  • 不需要等待结果返回,就能继续运行,就是异步
有时效的join
  • 如果等够时间还没返回,则继续执行

  • package com.sunyang.concurrentstudy;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.TimeUnit;
    
    /**
     * @program: ConcurrentStudy
     * @description: Demo
     * @author: SunYang
     * @create: 2021-07-28 22:05
     **/
    @Slf4j(topic = "c.Demo")
    public class JoinDemo {
        static int r = 0;
        public static void main(String[] args) {
            Thread thread = new Thread(() -> {
                log.debug("开始");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                r = 10;
                log.debug("结束");
            });
            thread.start();
            try {
                thread.join(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("结果{}", r); //  22:17:18 [main] c.Demo - 结果0
        }
    }
    
  • 如果线程提前结束,那么会直接返回结果,不用等待足够的秒数

    • thread.join(3000); // 两秒后返回结果了,就继续向下运行,不用等到3秒再继续

3.3.4 interrupt

  • 除了可以打断处于阻塞状态的线程,其他正在运行的线程也可以打断
打断阻塞的线程
  • 打断sleep,wait,join的线程

  • 打断sleep的线程,会清空打断状态,以sleep为例

  • package com.sunyang.concurrentstudy;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.TimeUnit;
    
    /**
     * @program: ConcurrentStudy
     * @description: Dmeo
     * @author: SunYang
     * @create: 2021-07-28 22:27
     **/
    @Slf4j(topic = "c.Demo")
    public class SleepInterruptDemoT {
        public static void main(String[] args) {
            Thread thread = new Thread(() -> {
                log.debug("sleep..");
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, "t1");
    
            thread.start();
            log.debug("interrupt...");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            thread.interrupt();
            log.debug("打断标记被清除--{}", thread.isInterrupted());
        }
    }
    
  • 输出

  • 22:31:30 [main] c.Demo - interrupt...
    22:31:30 [t1] c.Demo - sleep..
    java.lang.InterruptedException: sleep interrupted
    	at java.lang.Thread.sleep(Native Method)
    	at java.lang.Thread.sleep(Thread.java:340)
    	at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
    	at com.sunyang.concurrentstudy.SleepInterruptDemoT.lambda$main$0(SleepInterruptDemoT.java:19)
    	at java.lang.Thread.run(Thread.java:748)
    22:31:31 [main] c.Demo - 打断标记被清除--false
    Disconnected from the target VM, address: '127.0.0.1:62851', transport: 'socket'
    
    Process finished with exit code 0
    
    
  • 处于Running状态的线程被打断后,不会直接停止运行,而是由被打断线程自己决定,是否停止自己的线程,让出cpu,可以优雅的停止线程,在停止线程之前可以做一些善后工作。

  • package com.sunyang.concurrentstudy;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.TimeUnit;
    
    /**
     * @program: ConcurrentStudy
     * @description: Dmeo
     * @author: SunYang
     * @create: 2021-07-28 22:27
     **/
    @Slf4j(topic = "c.Demo")
    public class SleepInterruptDemoT {
        public static void main(String[] args) throws InterruptedException {
            Thread thread  = new Thread(() -> {
                while (true){
                    if (Thread.currentThread().isInterrupted()) {
                        log.debug("被打断了");
                        break;
                    }
                   log.debug("虽然interrupt我了,但是我还能执行。。");
                }
            }, "t1");
            thread.start();
            TimeUnit.SECONDS.sleep(1);
            log.debug("interrupt..");
            thread.interrupt();
            log.debug("打断状态--{}", thread.isInterrupted());
        }
    }
    
  • 22:44:30 [t1] c.Demo - 虽然interrupt我了,但是我还能执行。。
    22:44:30 [t1] c.Demo - 虽然interrupt我了,但是我还能执行。。
    22:44:30 [t1] c.Demo - 虽然interrupt我了,但是我还能执行。。
    22:44:30 [t1] c.Demo - 虽然interrupt我了,但是我还能执行。。
    22:44:30 [t1] c.Demo - 虽然interrupt我了,但是我还能执行。。
    22:44:30 [t1] c.Demo - 虽然interrupt我了,但是我还能执行。。
    22:44:30 [t1] c.Demo - 虽然interrupt我了,但是我还能执行。。
    22:44:30 [t1] c.Demo - 虽然interrupt我了,但是我还能执行。。
    22:44:30 [t1] c.Demo - 虽然interrupt我了,但是我还能执行。。
    22:44:30 [main] c.Demo - interrupt..
    22:44:30 [t1] c.Demo - 虽然interrupt我了,但是我还能执行。。
    22:44:30 [t1] c.Demo - 被打断了
    22:44:30 [main] c.Demo - 打断状态--true
    
模式之两阶段终止模式
  • 使用线程对象的stop()方法停止线程(废弃)

    • stop方法会真正杀死线程,如果这时线程锁住了共享资源,那么当它被杀死后就再也没有机会释放锁,其他线程就永远无法获取锁吗,就会造成死锁。
  • 使用System.exit(int)方法停止线程

    • 目的仅是停止一个线程,但这种做法会让整个程序都停止
两阶段终止
  • image-20210729102056314

  • 场景:记录电脑健康状态CPU使用率等等,搞一个后台的监控线程,while(true)循环,每两秒记录一下,并且要有一个打断按钮,不想监控时,可以停下。

  • 如果在睡眠状态被打断,会抛出异常,但是打断标记会被清除设置为false,所以要自己手动设置打断标记。

  • package com.sunyang.concurrentstudy;
    
    import ch.qos.logback.core.util.TimeUtil;
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.TimeUnit;
    
    /**
     * @Author: sunyang
     * @Date: 2021/7/29
     * @Description:
     */
    @Slf4j(topic = "c.Demo")
    public class TwoPhaseTerminationDemo {
        public static void main(String[] args) {
            TwoPhaseTermination twoPhaseTermination = new TwoPhaseTermination();
            twoPhaseTermination.start();
            try {
                TimeUnit.SECONDS.sleep(3);
                twoPhaseTermination.stop();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    @Slf4j(topic = "c.TwoDemo")
    class TwoPhaseTermination {
    
        private Thread monitor;
    
        public void start() {
            monitor = new Thread(() -> {
                while (true) {
                    Thread current = Thread.currentThread();
                    if (current.isInterrupted()) {
                        log.debug("善后工作!");
                        break;
                    }
                    try {
                        TimeUnit.SECONDS.sleep(1);
                        log.debug("执行监控记录");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        current.interrupt();
                    }
                }
            });
            monitor.start();
        }
    
        public void stop() {
            monitor.interrupt();
        }
    
    }
    
  • 11:02:32 [Thread-0] c.TwoDemo - 执行监控记录
    11:02:33 [Thread-0] c.TwoDemo - 执行监控记录
    java.lang.InterruptedException: sleep interrupted
    	at java.lang.Thread.sleep(Native Method)
    	at java.lang.Thread.sleep(Thread.java:340)
    	at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
    	at com.sunyang.concurrentstudy.TwoPhaseTermination.lambda$start$0(TwoPhaseTerminationDemo.java:41)
    	at java.lang.Thread.run(Thread.java:748)
    11:02:34 [Thread-0] c.TwoDemo - 善后工作!
    
    
打断park线程
  • 打断park线程,不会清空打断状态(不是Thread中的方法,是LockSupport.park()(锁的支持类中的方法))

  • package com.sunyang.concurrentstudy;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.LockSupport;
    
    /**
     * @Author: sunyang
     * @Date: 2021/7/29
     * @Description:
     */
    @Slf4j(topic = "c.Demo")
    public class ParkDemo {
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(() -> {
                log.debug("park。。。。");
                LockSupport.park();
                log.debug("unpark...");
                log.debug("打断状态:{}", Thread.currentThread().isInterrupted());
    
                LockSupport.park();
                log.debug("isInterrupted为true情况下执行了park()方法,但是依然继续执行,park()方法未生效");
            }, "t1");
            thread.start();
            TimeUnit.SECONDS.sleep(1);
            log.debug("thread状态:{}", thread.getState());
            thread.interrupt();
        }
    
    }
    
  • 11:26:58 [t1] c.Demo - park。。。。
    11:26:59 [main] c.Demo - thread状态:WAITING
    11:26:59 [t1] c.Demo - unpark...
    11:26:59 [t1] c.Demo - 打断状态:true
    11:26:59 [t1] c.Demo - isInterrupted为true情况下执行了park()方法,但是依然继续执行,park()方法未生效
    

3.4 主线程和守护线程

  • 默认情况下,java进程需要等待所有的线程都运行结束,才会结束,但是有一种特殊的线程,叫做守护线程,只要其他非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。

  • package com.sunyang.concurrentstudy;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.TimeUnit;
    
    /**
     * @Author: sunyang
     * @Date: 2021/7/29
     * @Description:
     */
    @Slf4j(topic = "c.Demo")
    public class DaemonThreadDemo {
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(() -> {
                while(true) {
                    if (Thread.currentThread().isInterrupted()){
                        break;
                    }
                }
                log.debug("结束");
            }, "t1");
            thread.setDaemon(true);
            thread.start();
            TimeUnit.SECONDS.sleep(1);
            log.debug("结束");
        }
    }
    
  • 13:38:56 [main] c.Demo - 结束
    
    Process finished with exit code 0
    
  • 垃圾回收器就是一种守护线程

  • Tomcat中的Acceptor和Poller线程都是守护线程,所以Tomcat接收到shutdown命令后,不会等待他们处理完请求。

3.5 五种状态

  • 操作系统层面来描述

    • image-20210729135208104
  • 【初始状态】

    • 仅是在语言层面创建了线程对象,还未与操作系统线程关联
  • 【可运行状态】(就绪状态)

    • 指该线程已被创建(与操作系统线程关联),可以由CPU调度执行,但还未被cpu分配时间片,还未真正在cpu上运行
  • 【运行状态】

  • 指获取了CPU时间片,运行中的状态

  • 当CPU时间片用完,会从**【运行状态】转换至【可运行状态(就绪状态)】**,会导致线程的上下文切换

  • 【阻塞状态】

    • 如果调用了阻塞API或者sleep之类的线程方法,如BIO读写文件,这时该线程实际不会用到CPU,会导致线程上下文切换,进入**【阻塞状态】**
    • 等BIO操作完毕,会由操作程序唤醒(应该是中断打断cpu)阻塞的线程,转换至**【可运行状态】**
    • 与可运行状态的区别是,对**【阻塞状态】**的线程来说,只要他们一直不被唤醒,调度器就一直不会考虑调度他们,也就是不会考虑为他们分配cpu时间片
  • 【终止状态】

    • 表示线程已经执行完毕,生命周期已经结束,不会再转换为其他状态

3.6 六种状态

  • 从Java API 层面来描述
  • 根据Thread.State枚举,分为六种状态

API

/**
 *  线程状态。 线程可以处于以下状态之一:
	NEW 尚未启动的线程处于此状态。
	RUNNABLE 在 Java 虚拟机中执行的线程处于这种状态。
	BLOCKED 被阻塞等待监视器锁的线程处于这种状态。
	WAITING 无限期等待另一个线程执行特定操作的线程处于此状态。
	TIMED_WAITING 等待另一个线程执行操作达指定等待时间的线程处于此状态。
	TERMINATED 已退出的线程处于此状态。
	一个线程在给定的时间点只能处于一种状态。 这些状态是不反映任何操作系统线程状态的虚拟机状态
 */
public enum State {
    /**
     * 尚未启动的线程的线程状态
     */
    NEW,

    /**
     * 可运行线程的线程状态。 处于可运行状态的线程正在 Java 虚拟机中执行,但它可能正在等待来自操作系统的其他资源,例如处理器
     */
    RUNNABLE,

    /**
     * 线程阻塞等待监视器锁的线程状态。 处于阻塞状态的线程正在等待监视器锁进入同步块/方法或在调用Object.wait后重新进入同步块/方法。
     */
    BLOCKED,

    /**
     *等待线程的线程状态。 由于调用以下方法之一,线程处于等待状态:
		Object.wait没有超时
		Thread.join没有超时
		LockSupport.park
		处于等待状态的线程正在等待另一个线程执行特定操作。 例如,在对象上调用Object.wait()的线程正在等待另一个线程在该对象上调用Object.notify()或			Object.notifyAll() 。 调用Thread.join() 的线程正在等待指定的线程终止。
     */
    WAITING,

    /**
     * 具有指定等待时间的等待线程的线程状态。 由于使用指定的正等待时间调用以下方法之一,线程处于定时等待状态:
		Thread.sleep
		Object.wait超时
		Thread.join超时
		LockSupport.parkNanos
		LockSupport.parkUntil
     */
    TIMED_WAITING,

    /**
     * 终止线程的线程状态。 线程已完成执行
     */
    TERMINATED;
}
  • image-20210729160852102

  • 初始,可运行,阻塞,等待,超时等待,终止

  • 【NEW】

    • 线程刚被创建,但是还没有调用start()方法
  • 【RUNNABLE】

    • 当调用了start()方法之后,注意,Java API 层面的RUNNABLE状态涵盖了操作系统层面的**【可运行状态】,【运行状态】和【阻塞状态】**
    • 由于BIO导致的线程阻塞,再JAVA里无法区分,仍然认为是可运行
    • **【BLOCKED】,【WAITING】, 【TIMED_WAITING】都是Java API 层面对【阻塞状态】**的细分。
  • 【TERMINATED】

  • 当线程代码结束运行

  • 【BLOCKED】

  • 【WAITING】

    • join()
  • 【TIMED_WAITING】

  • sleep

  • package com.sunyang.concurrentstudy;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.sql.Time;
    import java.util.concurrent.TimeUnit;
    
    /**
     * @Author: sunyang
     * @Date: 2021/7/29
     * @Description:
     */
    @Slf4j(topic = "c.Demo")
    public class ThreadSixStateDemo {
        public static void main(String[] args) {
    
            Thread t1 = new Thread(() -> {
                log.debug("running.....");      // NEW
            }, "t1");
    
            Thread t2 = new Thread(() -> {
                while (true){
    
                }
            });
            t2.start();    // Runnable
    
            Thread t3 = new Thread(() -> {
                log.debug("running.....");
            });
            t3.start();   //  TERMINATED
    
            Thread t4 = new Thread(() -> {
                synchronized (ThreadSixStateDemo.class){
                    try {
                        TimeUnit.SECONDS.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            t4.start();    // TIMED_WAITING
    
            Thread t5 = new Thread(() -> {
                try {
                    t2.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            t5.start();   // WAITING
    
            Thread t6 = new Thread(() -> {
                synchronized (ThreadSixStateDemo.class){
                    try {
                        TimeUnit.SECONDS.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            t6.start();  // BLOCKED
    
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            log.debug("t1 state {}", t1.getState());
            log.debug("t2 state {}", t2.getState());
            log.debug("t3 state {}", t3.getState());
            log.debug("t4 state {}", t4.getState());
            log.debug("t5 state {}", t5.getState());
            log.debug("t6 state {}", t6.getState());
    
        }
    
    }
    
  • 16:56:11 [Thread-1] c.Demo - running.....
    16:56:12 [main] c.Demo - t1 state NEW
    16:56:12 [main] c.Demo - t2 state RUNNABLE
    16:56:12 [main] c.Demo - t3 state TERMINATED
    16:56:12 [main] c.Demo - t4 state TIMED_WAITING
    16:56:12 [main] c.Demo - t5 state WAITING
    16:56:12 [main] c.Demo - t6 state BLOCKED
    
    
  开发工具 最新文章
Postman接口测试之Mock快速入门
ASCII码空格替换查表_最全ASCII码对照表0-2
如何使用 ssh 建立 socks 代理
Typora配合PicGo阿里云图床配置
SoapUI、Jmeter、Postman三种接口测试工具的
github用相对路径显示图片_GitHub 中 readm
Windows编译g2o及其g2o viewer
解决jupyter notebook无法连接/ jupyter连接
Git恢复到之前版本
VScode常用快捷键
上一篇文章      下一篇文章      查看所有文章
加:2021-09-07 11:01:45  更:2021-09-07 11:03:25 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/23 4:18:06-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计