JAVA中的线程世界

进程与线程

  • 进程是程序向操作系统申请资源(如内存空间和文件句柄)的基本单位.
  • 线程是进程中可独立执行的最小单位.

JAVA线程API

  1. 在Java中创建一个线程就是创建一个Thread类的实例。
  2. 每个线程都有其要执行的任务.线程任务的处理逻辑是在Thread类的run方法中实现或进行调用的,因此run方法相当于线程任务逻辑处理的入口方法,它由java虚拟机在运行相应的线程时进行调用,而不是由应用代码进行调用。
  3. Thread类的start方法是用于启动相关线程的,启动一个线程的实质是请求java虚拟机运行相应的线程,而这个线程具体什么时候运行是由线程调度器决定的.因此,虽然start方法被执行了,但是并不意味着这个线程就开始运行了,它可能稍后运行,也可能永远不运行.
  4. Thread中常用的两个构造器是:Thread()和Thread(Runnable target),这两种创建线程的方式如下:

    1. 定义Thread类子类的方式创建线程

      public class WelcomeApp {
      
        public static void main(String[] args) {
          // 创建线程
          Thread welcomeThread = new WelcomeThread();
      
          // 启动线程
          welcomeThread.start();
      
          // 输出“当前线程”的线程名称
          System.out.printf("1.Welcome! I'm %s.%n", Thread.currentThread().getName());
        }
      }
      
      // 定义Thread类的子类
      class WelcomeThread extends Thread {
      
        // 在该方法中实现线程的任务处理逻辑
        @Override
        public void run() {
          System.out.printf("2.Welcome! I'm %s.%n", Thread.currentThread().getName());
        }
      }
      
    2. 实现Runnable接口的方式创建线程

      public class WelcomeApp1 {
      
        public static void main(String[] args) {
          // 创建线程
          Thread welcomeThread = new Thread(new WelcomeTask());
      
          // 启动线程
          welcomeThread.start();
          // 输出“当前线程”的线程名称
          System.out.printf("1.Welcome! I'm %s.%n", Thread.currentThread().getName());
      
        }
      
      }
      
      class WelcomeTask implements Runnable {
        // 在该方法中实现线程的任务处理逻辑
        @Override
        public void run() {
          // 输出“当前线程”的线程名称
          System.out.printf("2.Welcome! I'm %s.%n", Thread.currentThread().getName());
        }
      
      }
  5. 不管使用以上哪种方式运行,一旦线程的run方法执行结束,相应线程的运行也就结束了,运行结束的线程所占的资源会如同其它java对象一样被java虚拟机垃圾回收。
  6. Thread的实例只能start一次,若多次调用一个实例的start()会抛出IllegalThreadStateException异常。
  7. 可以通过Thread.currentThread()获取当前线程,进而可以对其进行属性设置或获取它的相关信息,例如:

    Thread.currentThread().setName("线程A");
    Thread.currentThread().getName();
  8. 上述中,线程的run方法一般由java虚拟机调用,但是,线程也是一个Thread类的一个实例其次run方法也是由public修饰符修饰,所以run方法也能被直接调用,但是一般不会这么做,违背我们床架线程的初衷。

    public class WelcomeApp {
    
      public static void main(String[] args) {
        // 创建线程
        Thread welcomeThread = new WelcomeThread();
        // 启动线程
        welcomeThread.start();
        // 输出“当前线程”的线程名称
        System.out.printf("1.Welcome! I'm %s.%n", Thread.currentThread().getName());
        welcomeThread.run();
      }
    }
    
    // 定义Thread类的子类
    class WelcomeThread extends Thread {
    
      // 在该方法中实现线程的任务处理逻辑
      @Override
      public void run() {
        System.out.printf("2.Welcome! I'm %s.%n", Thread.currentThread().getName());
      }
    }
    ==================结果==================
    1.Welcome! I'm main.
    2.Welcome! I'm Thread-0.
    2.Welcome! I'm main.
    
  9. 线程的属性

    属性 属性类型及用途 只读 重要注意事项
    编号
    ID
    用于标识不同的线程,不同的线程拥有不同的编号 某个编号的线程运行结束后,该编号可能被后续创建的线程使用。不同线程拥有的编号虽然不同,但是这种编号的唯一性只在Java虚拟机的一次运行有效。也就是说重启一个Java虚拟机(如重启Web服务器)后,某些线程的编号可能与上次Java虚拟机运行的某个线程的编号一样,因此该属性的值不适合用作某种唯一标识,特别是作为数据库中的唯一标识(如主键)
    名称
    Name
    用于区分不同的线程。默认值与线程的编号有关,默认值的格式为:Thread-线程编号,如Thread-0 Java并不禁止我们将不同的线程的名称属性设置为相同的值。尽管如此,设置线程的名称属性有助于代码调试和问题定位
    线程类别
    Daemon
    值为true表示相应的线程为守护线程,否则表示相应的线程为用户线程。该属性的默认值与相应线程的父线程的该属性的值相同,在正常停止java程序时,当有用户线程还没执行完虚拟机不会立即停止,会等待其执行完毕,但是如果只有守护线程还没执行完则不会阻止虚拟机停止,这说明守护线程通常用于执行一些重要性不是很高的任务,例如监视其它线程的 该属性必须在相应线程启动之前设置,即对setDaemon方法的调用必须在对start方法的调用之前,否则setDaemon方法会抛出IllegalThreadStateException异常。负责一些关键任务处理的线程不适宜设置为守护线程
    优先级
    Priority
    该属性本质上是给线程调度器的提示,用于表示应用程序希望哪个线程能够优先得以运行。Java定义了1~10的10个优先级。默认值一般为5。对于具体的一个线程而言,其优先级的默认值与其父线程(创建该线程的线程)的优先级值相等 一般使用默认优先级即可。不恰当地设置该属性值可能导致严重的问题(线程饥饿)
  10. 线程方法

    方法 功能 备注
    static Thread
    currentThread()
    返回当前线程,即当前代码的执行线程(对象) 同一段代码对Thread.currentThread()的调用,其返回值可能对应着不同的线程(对象)
    void run() 用于实现线程的任务处理逻辑 该方法是由Java虚拟机直接调用的,一般情况下应用程序不应该调用该方法
    void start() 启动相应线程 该方法的返回并不代表相应的线程已经被启动。一个Thread实例的start方法只能够被调用一次,多次调用会导致异常的抛出
    void join() 等待相应线程运行结束 若线程A调用线程B的join方法,那么线程A的运行会被暂停,直到线程B运行结束
    static void yield() 使当前线程主动放弃其对处理器的占用,这可能导致当前线程被暂停 这个方法是不可靠的。该方法被调用时当前线程可能仍然继续运行(视系统当前的运行状况而定)
    static void sleep(long millis) 使当前线程休眠(暂停运行)指定的时间

Thread和Runnable的关系

  1. Thread类实现Runnable接口

    public class Thread implements Runnable{}
    
    
  2. Runnable是一个interface且其中只有一个方法,所以实现Runnable的类要重新run方法。
  3. 两种方式执行任务逻辑的过程

    • 实现Runnable接口,重写run方法

      class WelcomeTask implements Runnable {
        @Override
        public void run() {
          //任务逻辑 
        }
      }
      
      new Thread(new WelcomeTask()).start();

      WelcomeTask实例在Thread中的传递过程,它最终会被赋值给Thread中的一个target成员变量

      private Runnable target;

      具体过程如下:
      1.有参构造函数,生成一个ThreadName,例如:Thread-0

      public Thread(Runnable target) {
          init(null, target, "Thread-" + nextThreadNum(), 0);
      }

      2.进入第一个初始化函数,这里面无需关注其它参数,因为还有其它构造方法会调用init(...)

      private void init(ThreadGroup g, Runnable target, String name,long stackSize){
          init(g, target, name, stackSize, null, true);
      }

      3.第二层初始化函数,即将target赋值

      private void init(ThreadGroup g, Runnable target, String name,
                        long stackSize, AccessControlContext acc,
                        boolean inheritThreadLocals) {
          if (name == null) {
              throw new NullPointerException("name cannot be null");
          }
      
          this.name = name;
          .....
          this.target = target;
          ....
      }

      4.Thread实例调用start()方法

      public synchronized void start() {
          //threadStatus != 0代表这个Thread实例已经被start()过了,所以会抛出异常
          if (threadStatus != 0)
              throw new IllegalThreadStateException();
          ...
          try {
              //最终会调用本地方法start0()来启动这个线程
              start0();
              ...
          } finally {
              ...
          }
      }

      5.本地方法启动线程,即将线程交给虚拟机的线程调度器

      private native void start0();

      6.线程调度器执行线程任务逻辑

      //启动线程后,调度器会帮助我们运行执行线程run()方法,即任务逻辑
      //如果target != null的时候会调用WelcomeTask中实现的任务逻辑,否则什么都不会执行
      public void run() {
          if (target != null) {
              target.run();
          }
      }
      
    • 继承Thread类,重写run方法

      class WelcomeThread extends Thread {
           @Override
           public void run() {
             //任务逻辑
           }
      }
          
      new WelcomeThread().start();
      ...
      //任务调度器直接调用WelcomeThread中实现的任务逻辑
  4. 两种方式的区别

    • 面向对象角度:一种是基于继承实现extends Thread,一种是基于组合new Thread(new Runable()),组合相对与继承来说耦合性更低。
    • 对象共享的角度:一个CountingTask实例可以被多个线程共享所以可能出现资源竞争问题。

      class CountingThread extends Thread {
          int count = 0;
          @Override
          public void run() {
              for (int i = 0; i < 100; i++) {
                  i++;
              }
          }
          System.out.println("count:" + count);
      }
      
      class CountingTask implments Runnable {
          int count = 0;
          @Override
          public void run() {
              for (int i = 0; i < 100; i++) {
                  i++;
              }
          }
          System.out.println("count:" + count);
      }
      
      public static void main(String[] args) {
          Thread thread;
          CountingTask task = new CountingTask();
          for(int i = 0;i < 10;i++){
              thread = new Thread(task);
              thread.start();
          }
          for(int i = 0;i < 10;i++){
              thread = new CountingThread();
              thread.start();
          }
      }

线程的生命周期

JAVA中的线程世界_第1张图片

  1. New(新创建)
    Thread thread = new Thread();
    此时线程只是备new出来并没有开始执行。
  2. Runnable(可运行)
    thread.start();
    1.ready:还没有被分配资源 。
    2.running:正在执行 。
  3. 阻塞
    1.Blocked(阻塞) 没有获取被synchronized保护的代码的锁 。
    2.Waiting(等待)

     1.Object.wait(); 
         - o.notify()/o.notifyAll()
     2.Thread.join(); 
         - join线程结束/被中断 
     Blocked与Waiting的区别是Blocked是在等待释放某个资源,Waiting是在等待达到某个条件 。
  4. Time Waiting(计时等待) 设置了时间参数的一些阻塞 。
    1.Thread.sleep(m);

      - 时间超时/join的进程结束/被中断 。

    2.Object.wait(m)

     - 时间到/o.notify()/o.notifyAll() 

    3.Thread.join(m)

     - 时间超时/join的进程结束/被中断 。
  5. Terminated(终止)
    1.run方法正常执行完毕 。
    2.出现没有捕获的异常,意外终止。

你可能感兴趣的:(JAVA中的线程世界)