关于java:JAVA中的线程世界

4次阅读

共计 5835 个字符,预计需要花费 15 分钟才能阅读完成。

过程与线程

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

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();}
      }

线程的生命周期

  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. 呈现没有捕捉的异样,意外终止。

正文完
 0