【JavaEE】Thread类的基本用法手术刀剖析

文章目录

  • Thread类简介
  • 一、线程创造
  • 二、线程中断
  • 三、线程等待
  • 四、获取当前线程引用
  • 五、休眠当前线程
  • 最后

Thread类简介

  Thread类是JVM用来管理线程的一个类,换句话说,每个线程都有一个唯一的Thread对象与之关联。

  在我们创建线程的时候会遇到run方法与start方法,这里大家先做一个区分,它们的区别如下:
【JavaEE】Thread类的基本用法手术刀剖析_第1张图片

一、线程创造

  在这里介绍五种创造线程的方法

  第一种:

继承 Thread, 重写 run:

class MyThread extends Thread{
    @Override
    public void run(){
        System.out.println("Hello thread");
    }
}

public class Demo1 {
    public static void main(String[] args) {
        Thread t = new MyThread();
        t.start();//启动线程
    }
}


  第二种:

实现Runnable,重写run

class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("hhhh");
    }
}

public class Demo3 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyThread());
        t.start();;
    }
}


  第三种:

继承Thread,重写run,使用匿名内部类

  创建一个匿名内部类,继承自Thread类,同时重写run方法,同时再new出这个匿名内部类的实例

public class Demo4 {
    public static void main(String[] args) {
        Thread t = new Thread(){
            @Override
            public void run() {
                System.out.println("hell");
            }
        };
        t.start();
    }
}

  第四种:

实现 Runnable, 重写 run, 使用匿名内部类

public class Demo5 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("xixixixi");
            }
        });
        t.start();
    }
}

【JavaEE】Thread类的基本用法手术刀剖析_第2张图片
  第五种

使用lambda表达式

public class Demo6 {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            System.out.println("33333");
        });
        t.start();
    }
}

  这五种创建线程的表达式很常见,我们应该要熟练掌握。

    
  
  通过上面五种的方式,创建线程对象,不知大家有没发现,都有一个t.strat();表达式在里面。

  为什么呢? 这是因为线程被创建出来并不意味着线程就开始运行了。就比如说,我们要去旅游,我们准备好了衣物这些出行物件,不代表我们已经出发了。只有我们坐上了车,出发了,才叫出发。

  所以说,只有调用了start();方法,线程才真正执行起来。

  上面我们也应该注意到,我们都覆写了run方法,覆写 run 方法是提供给线程要做的事情的指令清单。

二、线程中断

线程中断就是让一个线程停下来

  线程停下来的关键,是要让对应的run方法执行完 。

  对于main这个线程,则是得先让main方法执行完。

  因此,让线程中断,一般有以下几种方法。

  
  方法一:

手动的设置一个标志位,来控制线程是否要执行

  这个标志位实际上是我们自己创建的一个Boolean型变量

public class Demo10 {

    private static boolean isQuit = false;

    public static void main(String[] args) {
        Thread t = new Thread(()->{
           while(!isQuit) {
               System.out.println("hello world!");
               try {
                   Thread.sleep(5000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
        });
        t.start();
        //isQuit 为true,则此时循环退出,进一步run就执行完了,进一步则线程执行结束
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        isQuit = true;
        System.out.println("终止t线程");
    }
}

【JavaEE】Thread类的基本用法手术刀剖析_第3张图片  可以看到,当while里面的条件不满足之后,循环结束了,而t线程也跟着结束了,之后main线程也跟着结束了,进程随之退出。

【JavaEE】Thread类的基本用法手术刀剖析_第4张图片

  上面的这种让线程中断的写法其实是不严谨的,因为它仅仅满足于刚刚的特定情况。更好的方法我们下面继续介绍。

  方法二:

使用Thread中内置的标志位来进行判断

  通过以下两种形式:

  1. Thread.interrupted();
  2. Thread.currentThread().isInterrupted();

  二者区别在于前者是一个静态方法,后者是一个实例方法,其中currentThread能获取到当前线程的实例。更推荐使用后者,下面我们用第二种方法来演示。

public class Demo11 {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            //如果这个标志被设置了,它是被中断的状态 ,则返回true,中断 线程
            while (!Thread.currentThread().isInterrupted()){
                System.out.println("hello world!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();

        //在主线程中,条用interrupt方法,来中断这个线程
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //在主线程中,条用interrupt方法,来中断这个线程
        //t.interupt的意思就是让t线程被中断
        t.interrupt();
    }
}

【JavaEE】Thread类的基本用法手术刀剖析_第5张图片  通过控制台输出,我们发现这里出现异常之后仍旧继续打印。

  为什么会产生这种情况呢?要弄明白这个东西,我们得先来知道调用interrupt方法可能会产生的两种情况:

  1. 如果线程t是处在就绪状态的,那么就是设置线程的标志位为true
  2. 如果t线程是处在阻塞状态(sleep休眠了),那么就会触发一个interruptException异常。

  回到我们的程序当中,我们分析:
【JavaEE】Thread类的基本用法手术刀剖析_第6张图片
  完成这样的工作之后,发生异常之后,程序就结束了。

【JavaEE】Thread类的基本用法手术刀剖析_第7张图片
  因此:

  1. Thread.interrupted()这个方法判定的标志位是Thread的static成员(一个程序只有一个标志位)。
  2. Thread.currentThread().isInterrupted(),这个方法判定的标志位是Thread的普通成员变量,每个示例都有自己的标志位。
  3. 在判断线程终止的情况下,一般直接使用第二种方法就可以了

三、线程等待

  多个线程之间的调度顺序是不确定的,因为我们线程之间的执行是按照调度器来安排的,这个过程可以视为是“无序的,随机的”。

  但有时我们需要它是有序的,我们希望能够控制线程之间的顺序,而线程等待就是我们控制线程执行顺序的一种手段。这里的线程等待,主要控制的是线程结束的先后顺序。

  我们这里一般使用的是join方法:

方法 说明
public void join() 等待线程结束
public void join(long millis) 等待线程结束,最多等 millis 毫秒
public void join(long millis, int nanos) 同理,但可以更高精度

  在调用join方法的时候,哪个线程调用的join,那个线程就会阻塞等待,直到对应的线程执行完毕为止(也就是对应线程的run执行完)。

public class Demo12 {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            for (int i = 0; i < 5; i++) {
                System.out.println("hello hhh");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        //在主线程中使用一个等待操作,来等待t线程执行结束
        try {
            t.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

【JavaEE】Thread类的基本用法手术刀剖析_第8张图片
  这样,通过线程等待,就可以控制让t线程先结束,main线程后结束,这在一定程度上干预了这两个线程的执行顺序。

  可是,这里有一个问题,就是我们的join操作在默认情况下,是死等操作,也就是说,不见不散的那种。那么这种形式的等待,在我们的程序运行当中,实际上是不合理的。

  因此,这里join提供了另外一种版本,就是可以指定等待时间,当时间过了,就不等了。它的写法就是在join里面加一个参数。
【JavaEE】Thread类的基本用法手术刀剖析_第9张图片

四、获取当前线程引用

方法 说明
public static Thread currentThread(); 返回当前线程对象的引用

  哪个线程调用这个currentThread,就获取的是哪个线程的实例

public class Demo13 {
    public static void main(String[] args) {
        Thread t = new Thread(){
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());//获取到线程的名字
            }
        };
        t.start();
    }
}

【JavaEE】Thread类的基本用法手术刀剖析_第10张图片

public class Demo13 {
    public static void main(String[] args) {
        Thread t = new Thread(){
            @Override
            public void run() {
               // System.out.println(Thread.currentThread().getName());//获取到线程的名字
            }
        };
        t.start();
        //这个操作是在main线程中调用的,因此拿到的就是main这个线程的实例
        System.out.println(Thread.currentThread().getName());
    }
}

【JavaEE】Thread类的基本用法手术刀剖析_第11张图片

  因此,哪个线程调用这个currentThread,就获取的是哪个线程的实例。

  此外,我们还可使用this关键字。

【JavaEE】Thread类的基本用法手术刀剖析_第12张图片
  如果是用Runnable方法创建线程的话,则是会报错
【JavaEE】Thread类的基本用法手术刀剖析_第13张图片【JavaEE】Thread类的基本用法手术刀剖析_第14张图片

五、休眠当前线程

  这里我们采用的sleep方法

方法 说明
public static void sleep(long millis) throws InterruptedException 休眠当前线程 millis毫秒
public static void sleep(long millis, int nanos) throws InterruptedException 可以更高精度的休眠

  【注意】因为线程的调度是不可控的,所以,这个方法只能保证实
际休眠时间是大于等于参数设置的休眠时间的。

  我们的线程是通过PCB(Process Control Block)描述的,通过双向链表进行组织。以上说法是针对我们只有一个一个线程的进程描述的。

  如果一个进程有多个线程,此时每个线程都有一个PCB。换句话说,就是一个进程对应的是一组PCB,PCB上有一个字段——tgroupId,这个字段就相当于进程的id,同一个进程里面的若干个线程的tgroupId是相同的。

  这里可能有人会有疑问,Process Control Block进程控制块和线程有啥关系?

  实际上Linux内核不区分进程和线程,进程线程是程序猿写应用程序代码搞出来的词.实际上Linux内核只认PCB !!在内核里Linux把线程称为轻量级进程。

【JavaEE】Thread类的基本用法手术刀剖析_第15张图片

最后

  行百步者半九十,我感觉到,自己可能到了这个阶段了,尽管我可能连五十步都还没走到。茫然,真茫然,自己学不到家,学得太烂。我知道自己面对的是怎样的境地,无论如何,我都必须得走下去了,凡能以人的主观意志达成的事,我绝不能就此放弃,只要我还是我,我会走下去…

你可能感兴趣的:(JavaEE,多线程,java-ee,java,后端)