【多线程】Thread类的用法

🏀🏀🏀来都来了,不妨点个关注!
🎧🎧🎧博客主页:欢迎各位大佬!
在这里插入图片描述

前言

在上一篇进程和线程中,我们介绍了进程和线程的概念,今天我们来介绍一下在Java中如何进行多线程编程,此时就有人问了,哪为啥不是进行多进程编程呢,这是因为操作系统其实是提供了一组进行多进程编程的API,但JDK中并没有给我们Java程序员封装这些多进程的API,同时,在上一篇我们介绍进程和线程中介绍过,进程切换是开销比较大的操作,而线程切换的成本比较低。

1.Thread类

在Java标准库中提供了一个类Thread来表示一个线程。下面我们来简单的介绍一下如何通过Thread来进行多线程编程。

1.1 通过Thread进行多线程编程


class Mythread extends Thread {
           public void run() {
              System.out.println("hello world");
        }
}
public class ThreadDemo {
    public static void main(String[] args) {
        Thread t = new Mythread();
        t.start();
    }
}

运行结果如下:
在这里插入图片描述
下面我们来理解一下上述代码都干了些什么。
我们是先创建一个MyThread类的实例,然后再通过t.start()启动这个线程,这相当于在进程中又搞了一套流水线,开始和main并发的执行另外一个任务。
上述代码主要是涉及到两个线程:

  1. 第一个就是main方法对应的线程(每一个进程中至少有一个线程,也可以叫作主线程)
  2. 第二个就是通过t.start()创建的新线程

1.2 通过线程的生命周期理解Thread类

线程的生命周期是一个从创建到终止的完整过程,它包含了多个状态之间的转换。
下面是线程生命周期的五个主要状态:

  1. 新建(New):当使用new关键字创建一个线程对象时,该线程就处于新建状态。此时,线程对象已经分配了内存空间,但还没有被执行。
  2. 就绪(Runnable):调用线程的start()方法后,线程会进入就绪状态。这时,线程已经获取了执行所需的资源(如JVM为其创建的方法调用栈和程序计数器),并等待CPU的调度。
  3. 运行(Running:当就绪状态的线程被CPU调度并获得执行权时,它进入运行状态。此时,线程正在执行其run()方法中的代码,并占用CPU资源。
  4. 阻塞(Blocked):在运行过程中,线程可能因为多种原因(如等待I/O操作、调用sleep()或wait()方法等)而进入阻塞状态。此时,线程暂时无法获取CPU资源,并等待某个条件满足后被唤醒。
  5. 销毁(Terminated/Dead):当线程执行完毕(run()方法执行完成)、被强制终止(尽管不推荐使用stop()方法,因为它容易导致死锁)或因为异常而结束时,线程进入销毁状态。此时,线程所占用的资源被释放,线程的生命周期结束。
    在这里插入图片描述

由此可知,当我们通过创建一个MyThread实例的时候,该线程就处于新建状态,当我们调用该线程的start()方法后,线程就会进入就绪状态等待CPU的调度,当处于就绪态的线程被CPU调度获得执行权的时候,此时该线程执行自身的run()方法,并占用CPU的资源。

1.3 创建Thread的几种方法

  1. 继承Thread类重写run方法

这个就是我们最开始介绍Thread类使用的方法

class Mythread extends Thread {
           public void run() {
              System.out.println("hello world");
        }
}
public class ThreadDemo1 {
    public static void main(String[] args) {
        Thread t = new Mythread();
        t.start();
    }
}
  1. 继承Runnable接口重写run方法
class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("hello t");
    }
}
public class ThreadDemo2 {
    public static void main(String[] args) {
        MyRunnable runnable = new MyRunnable();
        Thread t = new Thread(runnable);
        t.start();
    }
}
  1. 继承Thread类,使用匿名内部类的方式
public class ThreadDemo3 {
    public static void main(String[] args) {
        Thread t = new Thread() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("hello t");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }

            }
        };
        t.start();
        while (true) {
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}
  1. 继承Runnable接口,使用匿名内部类的方式
public class ThreadDemo4 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while(true) {
                    System.out.println("hello t");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
         t.start();
        while(true) {
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
  1. lambda表达式(最推荐在这里插入代码片的写法,最简单最直观的写法
public class ThreadDemo5 {
    public static void main(String[] args) {
        Thread t = new Thread( () -> {
            while (true) {
                System.out.println("hello t");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        while (true) {
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

2.Thread类及常见方法

2.1 Thread常见方法

在这里插入图片描述
(1)Thread()
创建线程对象

Thread t = new Thread();

(2)Thread()
使用Runnable对象创建线程对象

Thread t2 = new Thread(new MyRunnable());

(3)Thread(String name)
创建线程并命名

Thread t3 = new Thread("这是我的名字");

(4)Thread(Runnable target,String name)
使用Runnable对象创建线程对象并命名

Thread t4 = new Thread(new MyRunnable(), "这是我的名字");

2.2 Thread类的常见属性

  1. ID
    getId()
    ID 是线程的唯一标识,不同线程不会重复
  2. 名称
    getName()
    名称是各种调试工具用到
  3. 状态
    getState()
    状态表示线程当前所处的一个情况,对应着上面我们介绍的线程的生命周期,下面我们看例子:
class Mythread extends Thread {
           public void run() {
               System.out.println("hello world");
        }
}
public class ThreadDemo1 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Mythread();
        System.out.println("实例化Thread之后的状态: " + t.getState());
        t.start();
        System.out.println("调用start()方法之后的状态" + t.getState());
        t.sleep(1000);
        System.out.println("run()方法执行之后的状态" + t.getState());
    }
}


运行结果如下:
在这里插入图片描述

  1. 优先级
    getPriority()
    优先级对于系统来说只是给出"建议",理论上优先级越高,更容易被调度到
  2. 是否后台进程
    isDaemon()
    如果是true表示为后台线程(守护线程),false表示是前台线程(用户线程)
    【后台线程】后台线程不阻止java进程结束,哪怕后台线程还没执行完,java进程该结束就结束
    【前台线程】我们创建的线程默认是前台线程,可以通过setDaemon()设置成后台线程,JVM会在一个进程的所有非后台线程结束后,才会结束运行
public class ThreadDemo5 {
    public static void main(String[] args) {
        Thread t = new Thread( () -> {
                System.out.println("hello t");
        });
        t.start();
        //判断线程是否是后台线程
        System.out.println(t.isDaemon());
        
    }
}

运行结果如下:
在这里插入图片描述
6. 是否存活
isAlive()
判断当前的线程是否处于活动状态,描述的是操作系统里的那个线程是否存活,线程处于正在运行或准备开始运行的状态,就认为线程是"存活"的状态
1.在调用start()方法前只是给该线程分配了内存,所以isAlive()为false,而调用run()方法时,该线程被CPU调用,此时为存活状态,当执行完run()方法后过一会线程就没了,此时isAlive()为false,如下面代码运行结果:

class MyThread extends Thread {

    @Override
    public void run() {
        System.out.println("调用run(): " + this.isAlive());
    }
}
public class ThreadDemo1{
    public static void main(String[] args) throws InterruptedException {
        Thread t = new MyThread();
        System.out.println("调用start(): " + t.isAlive());
        t.start();
        t.sleep(1000);
        System.out.println("run()方法结束后: "+t.isAlive());
    }
}

运行结果如下:
在这里插入图片描述

  1. 是否被中断
    isInterrupted()
    判断该线程是否被中断,被中断返回true,未被中断返回false,这个地方我们在下面会详情介绍

2.3 中断一个线程

线程的中断,就是让一个线程停止下来,需要注意的是,这里的中断不是让线程立刻停下来,它仅仅是一种协作机制,意味着线程在接收到中断请求后,可以选择在合适的时机响应这个请求,优雅地终止其运行。
这个其实也很好理解,比如现在我正在刷题中,此时我的女朋友小万过来说,你去给我买杯咖啡,此时就相当于小万告诉我要停止我的刷题了,但至于我什么时候停止,取决于我自己,我可以选择将题目写完再去给她买咖啡,我也可以选择立刻停下来去给她买咖啡。
那么如何通知一个线程需要中断了呢,目前常见的有两种方法:

  1. 自定义一个共享的标志位通知
  2. 使用Thread类自带的interrupt()方法

2.3.1 自定义标志位中断线程

public class ThreadDemo {
    public static boolean isQuit = false;
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while(!isQuit) {
                System.out.println("hello t");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("t 线程中止");
        });
        t.start();
        Thread.sleep(3000);
        isQuit = true;
    }
}

上述代码,我们创建了一个线程循环打印“hello t”,当我们的标志位一直为false的情况下会一直循环打印,就会陷入死循环,此时我们在main()线程中休眠三秒后将标志位设置为true,此时就相当于通知t线程需要结束了,当t线程下一次进入循环发现循环判断条件为false就会退出循环执行下面的逻辑了。

2.3.2 调用interrupt()方法中断线程

在我们Thread类内置了一个标志位,用于表示线程的中断状态。这个标志位主要用于线程间的通信,允许一个线程请求另一个线程停止其正在执行的活动。
下面我们通过具体的代码进行举例:

public class ThreadDemo {
    public static void main(String[] args) {
        Thread t = new Thread( () -> {
            //currentThread()用于获取当前线程的实例,此处就是获取线程t,
            //isInterrupted()用于判断该线程是否被中断,上面Thread类的属性里提到
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("hello t");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //将标志位设为true
        t.interrupt();
    }
}

运行结果如下:
在这里插入图片描述
这里我们发现,结果并没有按照我们的预想那样,我们发现3s后调用t.interrupt()方法之后线程并没有结束,而是打印了一个异常信息后继续执行。这里我们就需要从interrupt()方法的两个作用来解释了:

  1. 将标志位设置为true
  2. 如果当前线程处在阻塞中(比如在执行sleep),此时就会把阻塞状态唤醒,并通过抛出异常的方式让sleep()结束。

这里需要注意一个非常的严重的问题就是,当sleep()被唤醒的时候,它会将isInterrupted()标志位清除(true—> false)。这就导致下次循环时,循环条件依然为true,继续打印“hello t”。
这里的解决方案就是在catch{}中加个break,当抛出异常后我们就直接跳出循环。如下图:
在这里插入图片描述
为什么sleep要清空标志位
目的就是为了让线程对于自身何时结束有一个明确的控制,这和我们最开始介绍中断的概念时是一样的,中断只是告诉线程需要结束了,而什么时候结束,都是由线程自身灵活控制的。
注意事项:

  1. 当线程在阻塞状态(如sleep()、wait()、join()等)中被中断时,会抛出InterruptedException异常,并且中断状态会被清除。因此,在捕获到这个异常后,如果需要保持中断状态,可能需要手动重新设置。
  2. 线程的中断是一种协作机制,而不是强制停止线程执行的方式。线程必须在其执行逻辑中适时地检查中断状态,并根据需要做出响应。
  • 22
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值