【多线程编程】Thread方法汇总

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

🚀欢迎大家来到我的博客⭐

🚀本人是双非软工专业的一名大二学生,让我们共同努力,冲击校招!!!⭐

🚀本章博客介绍的是关于Thread的常用方法⭐

🚀一下是我的QQ号,欢迎大家来进行技术交流!!⭐

🚀QQ:2641566921⭐

🚀以后会更新一些笔试有关的题目,请大家多多关注⭐

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

目录

Thread到底是个啥??

Thread的构造方法

1、Thread()

2、Thread(Runnable target)

3、Thread(String name)

4、Thread(Runnable target,String name)

Thread的几个常见的属性

1、getId() 

2、getName()

3、getState()

4、isAlive()

5、getPriority()

6、isDaemon()

线程的终止与中断

线程的终止

线程的中断

Interrupt()

Interrupt介绍

Thread.currentThread.isInterrupted()

1、Thread.currentThread()

2、isInterrupted()

3、Interrupt()

代码演示

等待线程 join方法

join方法介绍

join中存在几个值得注意的问题:

join的实际小用法


Thread到底是个啥??

Thread在英文中翻译为线,线程,在我前面的博客【多线程2】多线程的创建_sekiro&mikasa的博客-CSDN博客 中较为详细的介绍了Thread类的使用方法。

线程是操作系统进行调度的基本单位,每一个运行中的程序都是一个线程,进程之中包含着一个或者多个线程,线程之间可以通过操作系统的调度进入cpu执行自己的程序,每一个线程都有一个自己的执行流。

———————————————————————————————————————————

我们在Java程序中开启一个线程,我们程序员是如何对这些线程进行操作的呢,这就需要我们的Thread类对象来封装一些关于操作系统的api来为我们程序员操作线程提供支持。

Thread的构造方法

Thread的构造方法有很多种,我们就挑其中最重要的几种来进行说明

1、Thread()

这个构造方法没有指定任何的参数,它仅仅是创建了一个线程的对象(Thread对象),是个无参构造。

Thread thread = new Thread();

这个方法会报告一个警告,提示你没有重写Thread里面的run方法。

2、Thread(Runnable target)

这个方法其实就是通过传递一个Runnable接口进行线程的创建,在Runnable里面应该把run方法重写。

class Myrun implements Runnable{
    @Override
    public void run() {
        System.out.println("hello");
    }
}
public class Demo1 {
    public static void main(String[] args) {
        Thread thread = new Thread(new Myrun());
        thread.start();
    }
}

3、Thread(String name)

这个方法可以给指定的线程赋予一个单独的名字

 Thread thread1 = new Thread("name");

我们给thread1起了个名字叫name的线程 。

4、Thread(Runnable target,String name)

这个很好理解,不过是将2、3的代码进行结合,就会得到一个通过Runnable接口实现run方法,还可以进行重命名的进程。

Thread的几个常见的属性

属性对应的获取方法
idgetId()
名称getName()
状态getState()
是否存活isAlive()
优先级getPriority()
是否被中断isInterrupt()
是否后台线程isDaemon()

1、getId() 

一个线程会有自己的专属的身份标识,我们可以通过调用这个方法来获得这个线程的身份标识。

他的返回值是long。

2、getName()

这个方法可以进行线程名称的,在构造方法中我们有个Thread(String name)的方法,其中构造方法中设置的名称就是现在getName调用的名字。他的返回值是String。

3、getState()

我们之前的文章中提到过线程的状态有运行态,阻塞态和就绪态。返回值是Thread.State

4、isAlive()

它的返回值是boolean类型的变量。这个方法可以判断线程是否存活,在多线程代码中,每个线程的存活时间是有差异的,通常来说,当我们的Thread的实例对象调用了start之后,才会在操作系统中创建一个PCB,这个时候我们认为这个线程是存活的,返回值是true,存活状态会一直保持到线程被销毁,那么什么时候进程会被销毁呢?当我们的重写的run方法结束的时候,我们的线程就会被销毁,此时再调用isAlive就会显示false。

5、getPriority()

这个方法可以用来返回线程的优先级,返回的是一个int类型的值,他的值越大就说明他的优先级别越高,当然他还可以设置优先级,不过设置的优先级并没有什么用处,因为决定他的优先级的还是操作系统。

6、isDaemon()

这个方法会判断这个线程是不是守护线程。

那么什么是守护线程呢?

其实守护线程也可以称之为后台线程,我们先来区分一下前台线程和后台线程,前台线程是指一个进程当中,会影响进程结束的线程,当这个线程运行的时候进程不会结束,会一直执行线程。

后台线程与前台线程相反,它不会影响进程的结束。称之为守护线程。

包括main方法的主线程在内,代码创建出来的线程都是属于前台线程,如果我们想创建一个后台线程,就需要使用setDaemon()方法来进行线程的设置。

 我们可以看到,我们创建的一个线程被死循环的执行,我们创建的线程是一个前台线程,会阻止进程的结束。

我们通过setDaemon()方法将前台线程更改成为后台进程。

 我们可以看到虽然我们创建的线程没有执行和完毕,但是进程可以正常地结束,所以这就是前台线程和后台线程的区别。

以下是后台线程的代码

class MyThread implements Runnable{
    @Override
    public void run() {
        while(true){
            System.out.println("hello,Thread");
        }
    }
}
public class Demo2 {
    public static void main(String[] args) {
        Thread thread = new Thread(new MyThread());
        thread.setDaemon(true);
        thread.start();
    }
}

线程的终止与中断

我们要学习线程的终端就需要分清楚线程的终止与终端的概念。

线程的终止

线程的终止问题其实就是一个线程如何的去结束他的生命周期,一般来说,一个线程的终止会有三种情况。

1、线程正常结束:线程的run方法结束之后完成了他的工作,线程就自动结束了。

2、线程异常结束:线程在执行的过程中发生了未捕获的异常,导致线程以外的终止。

3、线程意外结束:程序由于某种不可抗力因素(如断电,系统崩溃)意外结束。

线程的中断

而线程的中断其实是线程终止的一种机制,是一种协作式的线程终止方法,一个正在执行的线程A可以被另一个线程B中断,引起正在执行的线程A的终止,而这种中断的请求是由线程B发起的,而线程A可以根据自己的自身情况来决定是否要中断自己。

由于中断我们可以理解为线程终止的一种方法,而线程终止的一种情况是线程正常的结束,所以我们只要在另一个线程中控制正在运行的线程执行完run方法就可以中断一个线程了。

我们先来简单粗暴的进行一次线程的中断。

public class Demo3 {
    public static boolean flag = true;
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            while(flag){
                System.out.println("hello");
            }
        });
        thread.start();
        Thread.sleep(1000);
        flag = false;
    }
}

通过构造一个标志flag,在main线程中更改flag的值就会引起另一个线程的中断。

Interrupt()

Interrupt介绍

在Java中,线程中断是通过Thread类的interrupt()方法来实现的。当一个线程调用另一个线程的interrupt()方法时,被中断的线程会收到一个中断请求,并有机会在合适的时机停止执行。被中断的线程可以通过检查自身的中断状态来决定是否终止执行,并在适当的时候抛出InterruptedException异常。需要注意的是,线程中断并不会直接停止线程的执行,它只是向线程发出一个中断请求。被中断的线程需要在执行过程中自行检查中断状态,并决定是否终止执行。另外,线程中断只是一种协作式的终止方式,它不能强制终止线程的执行

Interrupt本质上来说就是一个线程内部自带的标志位,用来决定线程是否被中断,Thread.currentThread.isInterrupted()就是检查自身中断状态的一个方法。中断状态是判断线程是否被中断的一个值。

Thread.currentThread.isInterrupted()

1、Thread.currentThread()

这个方法会获得正在Cup上运行的线程的对象

2、isInterrupted()

isInterrupted()是Thread类的一个实例方法,用于检查当前线程是否被中断,它并不会清除线程的中断状态。如果当前线程被中断,则返回true,否则返回false。

3、Interrupt()

interrupt()是Thread类的一个实例方法,用于中断当前线程或者指定线程。当一个线程被中断后,它的中断状态会被设置为true。

Interrupt会做两件事

1、把线程内部的中断状态更改为true

2、如果线程处于sleep状态,会把线程唤醒。唤醒之后,sleep()会继续通知线程运行,并且把线程内部的布尔变量改变成false(中断清除)。

代码演示

接下来我们使用代码进行演示

class InterruptThread extends Thread{
    @Override
    public void run() {
        while(!Thread.currentThread().isInterrupted()){
            System.out.println("Thread is running");
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                System.out.println("Thread is Interrupted");
                throw new RuntimeException(e);
            }
        }
        System.out.println("Thread is stopped");
    }
}
public class Demo4 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new InterruptThread();
        thread.start();
        Thread.sleep(5000);
        thread.interrupt();

    }
}

我们来分析一下结果:

我们可以看到,在线程启动之后,main线程被中断了5秒,thread线程的打印速度是1秒一次,当进入循环的时候,因为线程没被终止, Thread.currentThread.isInterrupted()返回的值是false,取反之后判断为true,所以进入循环打印五次,当主线程执行到interrupt的时候,循环判断结果为false,会抛出一个InterruptedException异常。

现在我们对这个异常进行一下处理,如果我们删除throw new RuntimeException(e);这段代码,进程会不会终止呢?

我们可以分析一下现在的结果

我们可以看到,删除throw new RuntimeException(e);之后,线程会被InterruptedException捕获,但是没有停止,会继续运行。

这是因为当线程在执行过程中收到中断请求并抛出InterruptedException异常时,它的中断状态会被自动清除,即调用isInterrupted()方法会返回false。这意味着,如果在处理InterruptedException异常的过程中需要检查线程的中断状态,必须在catch语句块中重新设置线程的中断状态,以确保中断状态不丢失。

也就是说在触发了中断请求之后,sleep把中断状态给清除了,所以下一次进入循环判断还是会为true,也就会无限循环,中断操作就会失效。

改变后的代码:

class InterruptThread extends Thread{
    @Override
    public void run() {
        while(!Thread.currentThread().isInterrupted()){
            System.out.println("Thread is running");
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                System.out.println("Thread is Interrupted");
                Thread.currentThread().interrupt();
            }
        }
        System.out.println("Thread is stopped");
    }
}
public class Demo4 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new InterruptThread();
        thread.start();
        Thread.sleep(5000);
        thread.interrupt();

    }
}

 运行结果我们呢可以看到它正常退出了。

等待线程 join方法

join方法介绍

因为线程之间是抢占式执行的,我们不知道下一刻哪一个线程会被调度到cpu上执行,为了程序员更好的控制线程执行的顺序,我们引入了join这一方法。

join是Thread的一个实例方法,表示一个线程等待另一个线程执行完成,当一个线程A中调用另外一个线程B的join方法的时候,线程A就会被阻塞等待,直到线程B执行完成在之后,线程A才会开始执行。

需要注意的是,join()方法可能会抛出InterruptedException异常,因此在使用join()方法时需要进行异常处理。如果线程在等待期间被中断,则join()方法会抛出InterruptedException异常,并清除中断状态。

另外,如果一个线程的join()方法被调用多次,只有第一次调用是有效的,后续的调用会被忽略。

join中存在几个值得注意的问题:

1、join其实就是让线程主动退让cpu的使用权限,如果一个线程已经完成执行(即线程的run()方法已经执行完毕),那么在其他线程中调用该线程的join()方法时,该join()方法会立即返回,不会阻塞其他线程。因为一个已经完成执行的线程已经不再是一个活跃的线程,其他线程也没有必要等待它完成。在这种情况下,调用join()方法只是一个无效的操作,不会对程序的执行产生任何影响。

public class Demo5 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            System.out.println("start");
        });
        thread.start();
        Thread.sleep(1000);
        thread.join();
        System.out.println("end");
    }
}

 

2、join之后,处于运行状态的线程不代表着会阻止其他线程的执行,自己霸占着cpu资源,只是阻塞了调用该线程join方法的线程。

3、join还有指定等待时间的方法,当另一个线程执行时间超过join方法里面规定的时间的时候,无论这个线程有没有执行完毕,都需要回到就绪队列接收调度。

4、当指定等待时间之后,线程在等待时间内很早就完成了任务,被阻塞的进程不会傻傻的等待时间全部结束之后再执行,而是当join的线程执行完毕之后会立即执行自己,此时剩余的等待时间无效。

我们可以看到,线程很快的执行了end。

join的实际小用法

我们让两个变量各自增加10亿次,算一下系统所用的时间

单线程:

public class Demo6 {
    public static void main(String[] args) {
        int a = 0;
        int b = 0;
        long start = System.currentTimeMillis();//获得系统的时间戳
        for(int i = 0;  i< 10000_00000; i++){
            a++;
        }
        for(int i = 0 ; i < 1000_00000; i++){
            b++;
        }
        long end = System.currentTimeMillis();
        System.out.println(end-start);
    }
}

 接下来我们看一下多线程代码:

public class Demo7 {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
           int a = 0;
           for(int i = 0; i < 1000_00000; i++){
               a++;
           }
        });
        Thread thread1 = new Thread(()->{
            int b = 0;
            for(int i = 0; i < 1000_00000; i++){
                b++;
            }
        });
        long star = System.currentTimeMillis();
        thread1.start();
        thread.start();
        try {
            thread1.join();
            thread.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        long end = System.currentTimeMillis();
        System.out.println(end-star);
    }
}

 

那么多线程这么好用,我们能不能尽量多的使用多线程呢?

我们需要注意,这是由范围要求的,此处的区间范围内,两个线程并发执行,效率会比单线程的要高不少。但是并不一定是,所有的区间范围内,把一个大的任务拆解成多个任务,让不同的线程并发执行,多线程都比单线程执行效率高。

因为操作系统的调度也需要很多的时间,如果我们编写的线程过多,就会导致调度会更加频繁,也不利于我们缩小运行的时间。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值