JavaEE——多线程Thread 类及常见方法

目录

 

一、Thread(String name)

二、是否后台线程 isDeamon()

三、是否存活 isAlive()

四、run()方法和start()方法的区别

五、中断线程

法一:

法二:

六、线程等待join()

七、线程休眠sleep()

一、Thread(String name)

定义:这个东西是给线程(thread 对象) 起一个名字。起一个啥样的名字,不影响线程本身的执行,仅仅只是影响到程序猿调试可以借助一些工具看到每个线程以及名字.很容易在调试中对线程做出区分。

那么如何查看线程的名字呢?这里jdk本身就自带了一个程序叫做jconsole可以用来查看本地进程。

那么首先先在我们自己idea里面创建一个线程demo8,右键运行,代码如下:

public class demo8 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            while (true){
                System.out.println("hello 1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"T1");
        t1.start();
 
        Thread t2 = new Thread(() -> {
            while (true){
                System.out.println("hello 2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"T2");
        t2.start();
    }
}

二、是否后台线程 isDeamon()

什么是后台线程呢?

如果线程是后台线程,就不影响 进程退出如果线程不是后台线程(前台线程),就会影响到进程退出。
创建的 t1 和 t2 默认都是前台的线程。
即使 main 方法执行完毕,进程也不能退出.得等 t1 和 t2 都执行完,整个进程才能退出!!!如果 t1 和 t2 是后台线程此时如果 main 执行完毕,整个进程就直接退出,t1 和 t2 就被强行终止了。

三、是否存活 isAlive()

这个属性的意思就是——操作系统中对应的线程是否正在运行
Thread t 对象的生命周期和内核中对应的线程,生命周期并不完全一致~~创建出 t 对象之后,在调用 start 之前,系统中是没有对应线程的~~在 run 方法执行完了之后, 系统中的线程就销毁了但是 t 这个对象可能还存在。通过 isAlive 就能判定当前系统的线程的运行情况。

如果 调用 start 之后, run 执行完之前,isAlive 就是返回 true

如果 调用 start 之前, run 执行完之后, isAlive 就返回 false

四、run()方法和start()方法的区别

1. start() 方法来启动线程,真正实现了多线程运行。这时无需等待 run 方法体代码执行完毕, 可以直接继续执行下面的代码。
2. 通过调用 Thread 类的 start()方法来启动一个线程, 这时此线程是处于 就绪状态 , 并没有运 行。
3. 方法 run()称为线程体,它包含了要执行的这个线程的内容,线程就进入了 运行状态,开始运 行 run 函数当中的代码 。 Run 方法运行结束, 此线程终止。然后 CPU 再调度其它线程。

run 单纯的只是一个普通的方法, 描述了任务的内容。

start 则是一个特殊的方法, 内部会在系统中创建线程。

看一段代码:

run() 方法只是一个普通的方法你在 main 线程里调用 run,其实并没有创建新的线程这个循环仍然是在 main 线程中执行。

之前我们已经看到了如何通过覆写 run 方法创建一个线程对象,但线程对象被创建出来并不意味着线程 就开始运行了。
覆写 run 方法是提供给线程要做的事情的指令清单
线程对象可以认为是把 李四、王五叫过来了
而调用 start() 方法,就是喊一声: 行动起来! ,线程才真正独立去执行了。
调用 start 方法 , 才真的在操作系统的底层创建出一个线程 .

五、中断线程

中断线程的意思就是让一个线程停下来!

线程停下来的关键,是要让线程对应的 run 方法执行完(还有一个特殊的,是 main 这个线程对于 main 来说,得是 main 方法执行完,线程就完了)。

法一:通过共享的标记来进行沟通

手动设置一个标志位。
看一段代码:我们在这个代码里面创建一个标志位isquit,通过while(!isquit)来判断循环是否结束,可以通过手动的设置isquit的值来控制线程的结束与否。为什么在其他线程中修改isquit的值可以影响到当前线程呢?此处因为,多个线程共用同一个虚拟地址空间!!因此, main 线程修改的 isquit 和 t 线程判定的 isquit, 是同一个值。

public class demo10 {
    public static  boolean isquit = false;
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            while(!isquit){
                System.out.println("hello Thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        //只要把这个isquit设置为true,进一步的run就执行完了,再进一步就是线程执行结束了。
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        isquit = true;
        System.out.println("终止线程!");
    }
}

那么像刚才那种写法并不够严谨,我们可以参考第二种写法。

.

法二: 调用 interrupt() 方法来通知

使用Thread中内置的标志位来判定。
我们可以通过:
Thread.interrupted()—— 一个静态的方法。
Thread.currentThread0.islnterrupted()—— 实例方法.

其中 currentThread 能够获取到当前线程的实例。

看新的代码段:

public class demo11 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()){
                System.out.println("hello thread!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    //当触发异常之后 立刻退出循环
                    break;
                }
            }
        });
        t.start();
 
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
 
        t.interrupt();
    }
}

那么使用这种方法的原因就是——这个代码绝大部分情况,都是在休眠状态阻塞,此处的中断,是希望能够立即产生效果。如果线程已经是阻塞状态下,此时设置标志位就不能起到及时唤醒的效果

异常处理方式:调用这个 interrupt 方法, 就会让 sleep 触发一个异常从而导致 线程从阻塞状态被唤醒。

+

 当下的代码,一旦触发了异常之后, 就进入了 catch 语句.在catch中,就单纯的只是打了一个日志
printStackTrace 是打印当前出现异常位置的代码调用栈,打完日志之后,就直接继续运行。

我们可以发现在代码的最后我们调用了interrupt()方法,那么调用这个方法可能产生两种情况。

1、如果t线程处在就绪状态,就是设置线程的标志位为true

2、如果t线程处在阻塞状态,就会触发一个InterruptException。

Thread.currentThread0.islnterrupted()—— 实例方法.

这个方法判定的标志位是 Thread 的普通成员,每个示例都有自己的标志位,所以一般无脑使用这个方法即可。

六、线程等待join()

在我们的日常开发中,因为线程之间的调度顺序是按照调度器来安排的,这个过程是随机,无序的,我们希望能够控制多个线程之间的调度顺序,那么线程等待就是一种方式。

要想实现线程等待这里会使用到一个函数叫做join(),即哪个线程调用join哪个线程就会进入阻塞状态。

代码演示:

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

我们可以看到在代码的后面出现了这么一行代码  t.join(10000);这里的意思就是让main线程等待t线程的run方法执行完毕,等待时间是10000ms。

七、线程休眠sleep()

这个线程休眠这里就不在过多赘述了,因为之前的案例中已经出现了很多的实例调用了sleep()方法。这里介绍一下进程中各个线程之间的关系。

进程是由PCB + 双向链表组成。这个说法是针对只有一个线程的进程,是如此的。
如果是一个进程有多个线程, 此时每个线程都有一个 PCB一个进程对应的就是一组 PCB 了。PCB 上有一个字段 tgroupld,这个 id 其实就相当于进程的 id.同一个进程中的若干个线程的 tgroupld 是相同的。

即某个线程调用了sleep()方法,PCB就会进入阻塞队列,操作系统调度线程的时候,就只是从就绪队列中挑选合适的 PCB到 CPU 上运行。阻塞队列里的 PCB 就只能干等着。

上一篇:解决端口被占用


下一篇:振动韧性与智能的双翼,让数智金融飞向未来之屿