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. 【JAVA中的线程世界】两种方式的区别
    • 面向对象角度:一种是基于继承实现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. 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.出现没有捕获的异常,意外终止。

    推荐阅读