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

Thread类简介

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

  在我们创建线程的时候会遇到run方法与start方法,这里大家先做一个区分,它们的区别如下:
在这里插入图片描述

一、线程创造

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

  第一种:

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

在这里插入图片描述
  第五种

使用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线程");
    }
}

在这里插入图片描述  可以看到,当while里面的条件不满足之后,循环结束了,而t线程也跟着结束了,之后main线程也跟着结束了,进程随之退出。

在这里插入图片描述

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

  方法二:

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

在这里插入图片描述  通过控制台输出,我们发现这里出现异常之后仍旧继续打印。

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

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

  回到我们的程序当中,我们分析:
在这里插入图片描述
  完成这样的工作之后,发生异常之后,程序就结束了。

在这里插入图片描述
  因此:

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

在这里插入图片描述
  这样,通过线程等待,就可以控制让t线程先结束,main线程后结束,这在一定程度上干预了这两个线程的执行顺序。

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

  因此,这里join提供了另外一种版本,就是可以指定等待时间,当时间过了,就不等了。它的写法就是在join里面加一个参数。
在这里插入图片描述

四、获取当前线程引用

方法说明
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();
    }
}

在这里插入图片描述

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

在这里插入图片描述

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

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

在这里插入图片描述
  如果是用Runnable方法创建线程的话,则是会报错
在这里插入图片描述在这里插入图片描述

五、休眠当前线程

  这里我们采用的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把线程称为轻量级进程。

在这里插入图片描述

最后

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

  • 31
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 19
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

十叶知秋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值