Java线程


目录
  • 创建和运行线程
    • 使用Thread
    • 使用 Runnable 配合 Thread
    • Thread 与 Runnable 的关系
    • FutureTask 配合 Thread
  • 查看进程线程的方法
    • Windows
    • Linux
    • Java
  • 线程运行原理
  • start 与 run
  • sleep 与 yield
    • sleep
    • yield
    • 线程优先级
  • join
  • interrupt
  • 两阶段终止模式
  • 打断park线程
  • 主线程与守护线程
  • 线程状态
    • 五种状态
    • 六种状态

创建和运行线程

使用Thread

//构造方法的参数是给线程指定名字
    Thread t1 = new Thread("t1"){
      @Override
      public void run() {
        //要执行的任务
        logger.debug("hh");
      }
    };
    //启动线程
    t1.start();

输出:

14:16:23.215 [t1] DEBUG com.fly.n1.Test1 - hh

使用 Runnable 配合 Thread

  1. Thread 代表线程
  2. Runnable 可运行的任务(线程要执行的代码)
 Runnable runnable = () -> {
      //要执行的任务
      logger.debug("Runnable");
    };
    //创建线程对象
    Thread t2 = new Thread(runnable,"t2");
    //启动线程
    t2.start();

输出:

14:31:02.613 [t2] DEBUG com.fly.n1.Test1 - Runnable

Thread 与 Runnable 的关系

小结:

  1. 方法1 是把线程和任务合并在了一起,方法2 是把线程和任务分开了
  2. 用 Runnable 更容易与线程池等高级 API 配合
  3. 用 Runnable 让任务类脱离了 Thread 继承体系,更灵活

FutureTask 配合 Thread

//创建任务对象
    FutureTask task = new FutureTask<>(() -> {
      logger.debug("task");
      return 10;
    });
    Thread t3 = new Thread(task, "t3");
    t3.start();
    // 主线程阻塞,同步等待 task 执行完毕的结果
    Integer result = task.get();
    logger.debug("结果是:{}",result);

输出:

14:48:33.946 [t3] DEBUG com.fly.n1.Test1 - task
14:48:33.951 [main] DEBUG com.fly.n1.Test1 - 结果是:10

查看进程线程的方法

Windows

  1. 任务管理器可以查看进程和线程数,也可以用来杀死进程
  2. tasklist 查看进程
  3. taskkill 杀死进程

Linux

  1. ps -fe 查看所有进程
  2. ps -fT -p 查看某个进程(PID)的所有线程
  3. kill 杀死进程
  4. top 按大写 H 切换是否显示线程
  5. top -H -p 查看某个进程(PID)的所有线程

Java

  1. jps 命令查看所有 Java 进程
  2. jstack 查看某个 Java 进程(PID)的所有线程状态
  3. jconsole 来查看某个 Java 进程中线程的运行情况(图形界面)

线程运行原理

  1. 每个线程启动后,虚拟机就会为其分配一块栈内存
    1.1 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
    1.2 每个线程只能有一个活动栈帧,对应着当前正在执行的方法
  2. 因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程的代码
    2.1 线程的 cpu 时间片用完
    2.2 垃圾回收
    2.3 有更高优先级的线程需要运行
    2.4 线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法
  3. 当 Context Switch 发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念就是程序计数器(Program Counter Register),它的作用是记住下一条 jvm 指令的执行地址,是线程私有的

start 与 run

  1. 直接调用 run 是在主线程中执行了 run,没有启动新的线程
  2. 使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码

sleep 与 yield

sleep

  1. 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
  2. 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
  3. 睡眠结束后的线程未必会立刻得到执行
  4. 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性

yield

  1. 调用 yield 会让当前线程从 Running 进入 Runnable
  2. 具体的实现依赖于操作系统的任务调度器

线程优先级

  1. 线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它
  2. 如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用

join

等待这个线程结束

 private static int num = 10;

  private static void method4() throws InterruptedException {
    Thread t1 = new Thread(() -> {
      try {
        Thread.sleep(1000);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      num = 20;
    }, "t1");
    t1.start();
    t1.join();
    num++;
    logger.debug("结果:{}",num);
  }

输出:

18:24:19.095 [main] DEBUG com.fly.n1.Test1 - 结果:21

interrupt

  1. 打断 sleep,wait,join 的线程.这几个方法都会让线程进入阻塞状态,打断 sleep 的线程, 会清空打断状态
  2. 打断正常运行的线程, 不会清空打断状态

两阶段终止模式

class TPTInterrupt {

  private Thread thread;
  private static final Logger log = LoggerFactory.getLogger(Test2.class);

  public void start() {
    thread = new Thread(() -> {
      while (true) {
        Thread current = Thread.currentThread();
        if (current.isInterrupted()) {
          log.debug("后续操作...");
          break;
        }
        try {
          Thread.sleep(1000);//此时被打断进入catch块,会把interrupt标记置为 false
          log.debug("监控记录...");//正常流程
        } catch (InterruptedException e) {
          current.interrupt();
        }
      }
    },"监控线程");
    thread.start();
  }

  public void stop() {
    thread.interrupt();
  }

}
  TPTInterrupt t = new TPTInterrupt();
    t.start();
    Thread.sleep(2500);
    t.stop();

输出:

18:00:23.033 [监控线程] DEBUG com.fly.n1.Test2 - 保存结果...
18:00:24.037 [监控线程] DEBUG com.fly.n1.Test2 - 保存结果...
18:00:24.531 [监控线程] DEBUG com.fly.n1.Test2 - 保存结果...
18:00:24.531 [监控线程] DEBUG com.fly.n1.Test2 - 后续操作...

打断park线程

打断 park 线程, 不会清空打断状态,如果打断标记已经是 true, 则 park 会失效
输出:

19:24:59.188 [t1] DEBUG com.fly.n1.Test3 - park...
19:24:59.685 [t1] DEBUG com.fly.n1.Test3 - unpark...
19:24:59.685 [t1] DEBUG com.fly.n1.Test3 - 打断状态:true
19:24:59.688 [t1] DEBUG com.fly.n1.Test3 - unpark...

主线程与守护线程

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

private static final Logger logger = LoggerFactory.getLogger(Test4.class);

  public static void main(String[] args) throws InterruptedException {
    logger.debug("开始运行");
    Thread t1 = new Thread(() -> {
      logger.debug("开始运行");
      try {
        Thread.sleep(1500);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      logger.debug("运行结束");
    }, "daemon");
    //设置该线程为守护线程
    t1.setDaemon(true);
    t1.start();
    Thread.sleep(1000);
    logger.debug("运行结束");
  }

输出:

19:38:15.830 [main] DEBUG com.fly.n1.Test4 - 开始运行
19:38:15.836 [daemon] DEBUG com.fly.n1.Test4 - 开始运行
19:38:16.835 [main] DEBUG com.fly.n1.Test4 - 运行结束

线程状态

五种状态

  1. 这是从 操作系统 层面来描述的:初始状态、可运行状态(就绪状态)、运行状态、阻塞状态、终止状态
  2. 初始状态:仅是在语言层面创建了线程对象,还未与操作系统线程关联
  3. 可运行状态:指获取了 CPU 时间片运行中的状态,当 CPU 时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换
  4. 阻塞状态:
    4.1 如果调用了阻塞 API,如 BIO 读写文件,这时该线程实际不会用到 CPU,会导致线程上下文切换,进入 阻塞状态
    4.2 等 BIO 操作完毕,会由操作系统唤醒阻塞的线程,转换至 可运行状态
    4.3 与 可运行状态 的区别是,对 阻塞状态 的线程来说只要它们一直不唤醒,调度器就一直不会考虑调度它们
  5. 终止状态:表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态

六种状态

  1. 这是从 Java API 层面来描述的根据 Thread.State 枚举,分为六种状态
public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
        NEW,

        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
        RUNNABLE,

        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
        BLOCKED,

        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * 
    *
  • {@link Object#wait() Object.wait} with no timeout
  • *
  • {@link #join() Thread.join} with no timeout
  • *
  • {@link LockSupport#park() LockSupport.park}
  • *
* *

A thread in the waiting state is waiting for another thread to * perform a particular action. * * For example, a thread that has called {@code Object.wait()} * on an object is waiting for another thread to call * {@code Object.notify()} or {@code Object.notifyAll()} on * that object. A thread that has called {@code Thread.join()} * is waiting for a specified thread to terminate. */ WAITING, /** * Thread state for a waiting thread with a specified waiting time. * A thread is in the timed waiting state due to calling one of * the following methods with a specified positive waiting time: *

    *
  • {@link #sleep Thread.sleep}
  • *
  • {@link Object#wait(long) Object.wait} with timeout
  • *
  • {@link #join(long) Thread.join} with timeout
  • *
  • {@link LockSupport#parkNanos LockSupport.parkNanos}
  • *
  • {@link LockSupport#parkUntil LockSupport.parkUntil}
  • *
*/ TIMED_WAITING, /** * Thread state for a terminated thread. * The thread has completed execution. */ TERMINATED; }
  1. NEW:线程刚被创建,但是还没有调用 start() 方法
  2. RUNNABLE:当调用了 start() 方法之后,注意,Java API 层面的 RUNNABLE 状态涵盖了 操作系统 层面的 可运行状态、运行状态 和 阻塞状态(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为是可运行)
  3. BLOCKED , WAITING , TIMED_WAITING 都是 Java API 层面对 阻塞状态 的细分
  4. TERMINATED:当线程代码运行结束
JUC