java线程详解--线程状态,重要方法以及线程的创建

366 阅读7分钟

线程

概述

  1. 进程:应用程序在内存中分配的空间,也就是运行中的程序,各个进程之间互不干扰,进程是操作系统进行资源分配的基本单位,单独占有内存地址空间和其他系统资源
  2. 线程: 线程是操作系统进行调度(cpu分配时间)的基本单位, 是在进程中执行的一个任务,共享所属进程的内存地址空间和资源

jvm层面线程状态

  • 以下状态为虚拟机线程状态,并不映射任何操作系统的线程状态
  1. 新建状态(NEW): 新创建的线程,还未调用start() 方法

  2. 可运行状态(RUNNABLE) : 调用了start() 方法,线程已经进入虚拟机,在等待操作系统的其他资源,如IO阻塞,网络阻塞,或者已经在运行中

  3. 阻塞状态(BLOCKED): 线程需要等待获取锁,等待进入同步(synchronized)方法或者同步块中, 或者说当因为获取不到锁而无法进入同步块时,线程处于 BLOCKED 状态

    1. 等待状态(WAITING): 当条件不满足时,等待其他线程完成操作使得条件满足,并通知等待中的线程,调用 Object.wait(), Thread.join(),LockSupport.park() 方法会使得线程进入等待状态。当一个线程调用Object.wait() 方法时,需要等待其他线程调用 Object.notify()/Object.notifyAll() 方法来唤醒, 当一个线程被 调用 LockSupport.park() 方法时,线程进入等待状态,需要对应的LockSupport.unPark(Thread)方法来唤醒指定的线程,或者通过interrupted() 方法中断线程使得线程继续执行,使得线程继续运行
  4. 限时等待(TIMED_WAITING): 等待指定的时间,调用Thread.sleepObject.wait(long)Thread.join(long)等方法会让线程进入该状态

  5. 结束状态(TERMINATED) : 线程执行完毕或者退出

    image

操作系统层面的线程状态

  • 操作系统层面的线程状态主要围绕cpu 操作
  1. new : 新建一个线程
  2. ready: 进入就绪状态,获得了除cpu 外的其他资源
  3. running : 运行状态, 线程已在cpu 上执行
  4. waiting : 等待状态,由于I/O ,或者其他等待事件,使得线程放弃cpu资源,从而进入等待状态,在这些事件完成后,线程会从新进入ready 状态,等待cpu资源的分配
  5. terminated : 线程执行完成

image

重要方法

  1. Object.wait

    • wait方法调用前需要获得锁, 应该总是在 循环中调用,即条件判断应该放在循环的条件判断,而不是放在 if 的判断中,防止虚假幻想(幻想后任然不符合执行条件)

      synchronized (obj) {
          while (<condition does not hold>)
              obj.wait(timeout);
          ... // Perform action appropriate to condition
      }
      
    • wait 方法必须在同步方法或者同步块中synchronized ,即要先获取锁才能调用wait 方法,这是第一次enter

    • 当前线程调用 wait方法后会释放 锁,当前线程进入该锁的 等待队列中,当前线程进入等待状态WAITING

    • 当收到其他线程的 notify 或者 notifyAll 通知后,当前线程并不能立即恢复执行,因为他已经释放了锁,所以要重新执行wait方法后的代码需要重新获取 锁,再次进入同步块中 这是第二次进入(reenter)

  2. Thread.join

    • 有线程a,b , 在线程 a 中调用 b.join , 相当与让 a线程等待 b线程的完成 , 此时a 停止执行, 等b执行完了,会通知a线程,使其恢复执行
  3. Thread.sleep

    • 经常会听到说 wait方法会释放锁, sleep 方法不会释放锁,其实这句话是不准确的,sleep 方法与锁无关,执行sleep 方法前并不是一定要获取锁(执行wait 方法前一定要先获取锁), 如果执行sleep 方法时没未获取锁,也就无所谓释放不释放,如果执行sleep 方法时已经获取了锁,则不会释放锁
  4. Thread.yield()

    • 中断当前线程的执行,让出cpu, 让自己和其他线程重新竞争cup 资源并运行
  5. Thread.interrupt()

  • 如果当前线程不是在中断中,则该操作一直是被允许的

  • Thread.interrupt() 只是对线程做一个中断标记

  • 该方法不会中断一个正在运行的线程,即简单调用该方法后,线程状态任然是Runable, 只是设置线程的中断状态即通过isInterrupted() 方法查询中断状态为 true,让用户自己选择时间地点去结束线程

       @Test
        public void test() {
            Thread thread = new Thread(() -> {
                this.interruptRunning();
            });
            thread.start();
    
        }
        public void interruptRunning() {
            int i =0;
            while (i<5) {
                i ++;
                System.out.println("try interrupt");
                Thread.currentThread().interrupt();
            }
            System.out.println("中断状态:"+Thread.currentThread().isInterrupted()); // true
            System.out.println("线程状态:"+ Thread.currentThread().getState()); // runable
            System.out.println("end");
        }
    
    // 输出
    try interrupt
    try interrupt
    try interrupt
    try interrupt
    try interrupt
    中断状态:true
    线程状态:RUNNABLE
     end
    
  • 如果线程处于中断状态,然后再调用阻塞方法(比如:Thread.sleepThread.join,Object.wait等使得线程进入阻塞的方法),则会抛出InterruptedException 中断异常,并且清理中断状态,即再次调用isInterrupted()方法中断状态为false

      @Test
        public void test() {
            Thread thread = new Thread(() -> {
              this.interruptBlock();
            });
            thread.start();
        }
    
    public void interruptBlock() {
        System.out.println("try interrupt");
        Thread.currentThread().interrupt();
        if (Thread.currentThread().isInterrupted()) {
            System.out.println("block");
            try {
                Thread.sleep(300L);
                System.out.println("sleep ing");
            } catch (InterruptedException e) {
                System.out.println("InterruptedException");
                System.out.println(Thread.currentThread().isInterrupted()); // false
                System.out.println(Thread.currentThread().getState()); // RUNNABLE
            }
        }
    }
    
    // 输出结果
    try interrupt
    block
    InterruptedException
    false
     RUNNABLE
    
  1. LockSupport.park()

    • 使得当前线程停止执行进入等待状态,当调用LockSupport.unPark(Thread) 方法指定解锁该线程时,线程继续从中断处运行,或者该线程调用了interrupt()方法中断该线程时,也会使得线程结束等待状态,继续运行

      public class LockSupportTest {
          public static class ParkThread extends Thread{
              public ParkThread(String name) {
                  super(name);
              }
      
              @Override
              public void run() {
                  System.out.println(getName() + " running 00000000");
                  LockSupport.park();
                  if (Thread.currentThread().isInterrupted()) {
                      System.out.println(getName() + " interrupted ");
                  }
                  System.out.println(getName() + " running continue....");
      
              }
          }
      
          @Test
          public void parkTest() {
              ParkThread p1 = new ParkThread("p1");
              ParkThread p2 = new ParkThread("p2");
      
              p1.start();
              try {
                  Thread.sleep(1000L);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
      
              LockSupport.unpark(p1);
              p2.start();
              p2.interrupt(); 
              try {
                  Thread.sleep(1000L);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
      
              System.out.println(p2.getState()); // 如果不对p2线程进行interrupt操作或者unpart()操作,p2 线程将一直处于 WAITING 等待状态
      
          }
      }
      
      // 执行结果
      p1 running 00000000
      p1 running continue....
      p2 running 00000000
      p2 interrupted 
      p2 running continue....
      TERMINATED
      
      
    • LockSupport.park() 的作用和 Object.wait() 方法的作用类似,都是使当前线程进入等待状态,但是LockSupport.park() 操作并不需要获取锁,结束等待状态需要指定 unpark 对应的线程或者通过interrupt 对应的线程, 而通过wait 方法进入等待状态的线程,通过notify 方法只是随机唤醒一个等待队列中的线程,或者通过notifyAll 唤醒所有等待队列中的线程

线程的创建

  1. 继承Thread类,重写run 方法

    public class Demo {
        public static class MyThread extends Thread {
            @Override
            public void run() {
                System.out.println("MyThread");
            }
        }
    
        public static void main(String[] args) {
            Thread myThread = new MyThread();
            myThread.start();
        }
    }
    
    
  2. 实现 Runnable 接口, 实现 run 方法

    public class Demo {
        public static class MyThread implements Runnable {
            @Override
            public void run() {
                System.out.println("MyThread");
            }
        }
    
        public static void main(String[] args) {
            new MyThread().start();
    
            // Java 8 函数式编程,可以省略MyThread类
            new Thread(() -> {
                System.out.println("Java 8 匿名内部类");
            }).start();
        }
    }
    
  3. Callable、Future与FutureTask

    • 线程的创建不考虑 线程池,其实就是以上两种 (继承 Thread或者实现Runnable), 经常会觉得 Callable,Future,FutureTask 的组合是第三种线程的创建方式,其实不然,这种方式并不会创建新的线程,Callable,Future,FutureTask 的正常使用姿势是配合线程池,可以说是一种任务的执行方式, 通过Thread 类和 Runnable创建的线程任务执行并不会返回执行结果,因为他的run 方法是没有返回值的,而通过callable 定义的任务是有返回值的,通过Future或者FutureTask 可以获得任务的执行返回值。

      public class CallableTest {
          public static void main(String args[]) {
              callableFutureTaskTest();
          }
      
          public static void  callableFutureTaskTest() {
              MyCallable task = new MyCallable();
              FutureTask futureTask = new FutureTask<>(task);
              futureTask.run();
              System.out.println("test name:"+Thread.currentThread().getName());
              try {
                  System.out.println( futureTask.get());
              } catch (InterruptedException e) {
                  e.printStackTrace();
              } catch (ExecutionException e) {
                  e.printStackTrace();
              }
          }
      }
      //自定义Callable
      class MyCallable implements Callable<Integer> {
          @Override
          public Integer call() throws Exception {
              // 模拟计算需要一秒
              Thread.sleep(1000);
              System.out.println("callable name:"+Thread.currentThread().getName());
              return 2;
          }
      }
      
      // 输出结果 ,由此可看出执行 call() 方法的也是主线程,且FutureTask 并未提供类似于Thread 的 start() 方法
      callable name:main
      test name:main
      2
      
    • Callable、Future与FutureTask 的正确使用姿势,具体操作放在线程池中讲述

      public static void executorCallableTest() {
          // 使用
          ExecutorService executor = Executors.newCachedThreadPool();
          MyCallable task = new MyCallable();
          Future<Integer> result = executor.submit(task);
          // 注意调用get方法会阻塞当前线程,直到得到结果。
          // 所以实际编码中建议使用可以设置超时时间的重载get方法。
          try {
              System.out.println(result.get());
          } catch (InterruptedException e) {
              e.printStackTrace();
          } catch (ExecutionException e) {
              e.printStackTrace();
          }
      
          FutureTask<Integer> futureTask = new FutureTask<>(task);
          executor.submit(futureTask);
          try {
              System.out.println(futureTask.get());
          } catch (InterruptedException e) {
              e.printStackTrace();
          } catch (ExecutionException e) {
              e.printStackTrace();
          }
      }
      //自定义Callable
      class MyCallable implements Callable<Integer> {
          @Override
          public Integer call() throws Exception {
              // 模拟计算需要一秒
              Thread.sleep(1000);
              System.out.println("callable name:"+Thread.currentThread());
              return 2;
          }
      }
      

参考文章