Thread 类的基本方法

简介: Thread 类的基本方法

线程创建(五种方法)


run方法是线程的入口方法, 每次我们去创建线程时都要重写run方法, run方法执行结束即该线程结束.


1. 继承 Thread, 重写 run

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

输出: main run 或 run main

因为线程调度不同, 所以线程执行快慢不一样.


2. 实现 Runnable 接口, 重写 run

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


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

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


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

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

5. 使用 lambda 表达式(重点掌握)

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


线程休眠


class MyThread extends Thread {
    @Override
    public void run() {
        while(true) {
            System.out.println("run");
            try {   //因为sleep是静态方法,所以可以直接调用
                Thread.sleep(1000);  //可能会出现异常, 所以要捕获
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class ThreadDemo1 {
    public static void main(String[] args) {
        Thread t = new MyThread();
        t.start();
        while(true) {
            System.out.println("main");
            try {
                Thread.sleep(1000);  //单位是毫秒, 这里是1s
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}


输出:

6d1a77b4420b4c9f88a04588b0e9122a.png


因为线程调度不同, 所以这里打印是随机的, 这里没有体现出来.

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


获取线程实例


Thread.currentThread() 获取线程的实例


public static void main(String[] args) {
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName());
    }


输出: main


线程中断


就是让一个线程停下来, 线程终止.

本质上来说, 让一个线程终止就一个方法, 让线程的入口方法执行完.


1. 给线程中设定一个结束标志位


class MyThread extends Thread {
    public static boolean isQuit;  //设置一个成员变量, 表示标志位
    @Override
    public void run() {
        while(!isQuit) {  //通过成员变量我们可以控制while循环
            System.out.println("run");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("程序终止!");
    }
    public static void main(String[] args) {
        Thread t = new MyThread();
        t.start();
        System.out.println("main");
        isQuit = true;  //在主线程中将标志位置为true, 影响到t线程
    }
}


上述代码中, 我们用的是成员变量设置标志位, 那局部变量可以吗?

我们可以试试:

4ec277687bd7402f91f2dd97f36d8078.png


显示找不到该变量, 为什么找不到呢?

按理来说线程与线程之间共用一个内存地址空间, 它们之间的变量都可以访问的, 那为什么不行呢?

其实这里和 lambda 表达式有关.


lambda 表达式中捕获的变量必须是 final 修饰的 或者是 “实际 final”(没有被final修饰, 但是代码中没有对该变量进行修改过)


再看上述代码, isQuit 在主线程中创建, 又被修改过, 所以不行.


2. 使用Thread类内置的标志位来控制


class MyThread extends Thread {
    @Override
    public void run() {
      //这里的Thread.currentThread()是获取当前对象的实例,也就是t
      //isInterrupted()得到内置标志位, 初始为false
        while(!Thread.currentThread().isInterrupted()) {
            System.out.println("run");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("程序终止!");
    }
    public static void main(String[] args) {
        Thread t = new MyThread();
        t.start();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //interrupt() 设置标志位为true
        t.interrupt();
    }
}

364ebde27f664e10a59940695c179e10.png


当我们在调用 interrupt() 时, 如果该线程在正在阻塞中(比如正在sleep中), 此时就会把阻塞状态唤醒, 通过抛出异常的方式让sleep立即结束.

注意:

当线程处于sleep状态被唤醒时, sleep会自动把isInterrupted标志位清空(true -> false). 这就导致下次循环还是会进去, 而主线程已近结束了, 没有interrupt() 来影响标志位, 就会一直循环打印run.


这就产生了上面的输出结果.

当然, 如果sleep刚刚好结束了, 这时候我们调用interrupt(), 则直接结束循环.(这种情况是很低的)

我们可以通过添加一个break来让代码抛出异常后退出循环.


class MyThread extends Thread {
    @Override
    public void run() {
        while(!Thread.currentThread().isInterrupted()) {
            System.out.println("run");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
                break;
            }
        }
        System.out.println("程序终止!");
    }
    public static void main(String[] args) {
        Thread t = new MyThread();
        t.start();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t.interrupt();
    }
}


775cf24384b34afcb2c6f5792632d1bf.png


线程等待


因为线程之间是并发执行的, 操作系统对线程的调度是无序的, 所以我们无法判断哪个线程先结束, 哪个后结束.

因此, 在Thread类里提供了一个 join 方法, 来进行线程间的等待.

比如:

t.join() 放在 main 线程里就是main线程等待 t 线程结束. 这时main线程是阻塞状态, 直到 t 执行结束main线程才会从阻塞解除, 继续执行.

例:


public class ThreadDemo5 {     //使用join方法可能会有异常,所以声明一下
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.out.println("t1");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        Thread t2 = new Thread(() -> {
            try {
                t1.join();   //放在这里就是t2等待t1结束
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t2");
        });
        //首先创建两个线程
        t1.start();
        t2.start();
        t2.join();   //这里main线程等待t2结束
        System.out.println("main");
    }
}
aa7779328104489f90df44978e228a0e.png


首先main线程等待 t2 线程执行结束, 而 t2 线程在等待 t1 线程结束, 所以就出现上面结果.


相关文章
|
缓存 监控 网络协议
【华为HCIP | 职业认证考试】821每日一刷
【华为HCIP | 职业认证考试】821每日一刷
1846 0
【华为HCIP | 职业认证考试】821每日一刷
|
前端开发 Java Maven
Springboot创建项目(idea版本)
Springboot创建项目(idea版本)
529 0
|
12月前
|
机器学习/深度学习 人工智能 测试技术
探索AI在软件开发中的应用:提升效率与创新
【10月更文挑战第25天】本文探讨了AI在软件开发中的应用,包括自动化测试、代码生成与优化、智能项目管理等方面,介绍了TensorFlow、PyTorch和GitHub Copilot等实用工具,展望了AI在未来的潜力,并强调了AI对提升开发效率和创新能力的重要性。
|
移动开发 安全 JavaScript
SpringBoot接入微信JSSDK,看这篇妥妥的
这篇教程详细介绍了如何在Spring Boot项目中接入微信JSSDK,实现H5页面的自定义分享和调用相册选取图片等功能。文章首先通过对比理想与现实的分享效果,引出了接入微信JSSDK的必要性。接着,作者提供了GitHub和Gitee上的项目源码链接,并逐步讲解了整个接入过程的关键步骤,包括配置文件、主要类和方法的实现细节,以及必要的微信公众号设置。此外,还特别强调了几个常见问题及其解决方案,如域名绑定、IP白名单设置和签名验证等。最后,通过实际测试验证了功能的正确性。适合初学者快速上手微信JSSDK接入。
294 8
SpringBoot接入微信JSSDK,看这篇妥妥的
|
API 网络架构 Python
使用Python和Flask构建简单的RESTful API
【10月更文挑战第12天】使用Python和Flask构建简单的RESTful API
187 0
|
PHP
PHP实现自制随机图片API- 调用文件夹和引用网络图片
PHP实现随机图片API- 调用文件夹和引用网络图片
382 0
|
安全 网络协议 网络安全
常见网络攻击方式及防御方法
网络安全威胁的不断演变和增长,网络攻击的种类和数量也在不断增加,攻防对抗实战演练在即,让我们一起了解一下常见网络攻击方式及防御方法。
958 0
|
设计模式 Java
Java设计模式之责任链模式详解
Java设计模式之责任链模式详解
|
Java 数据库连接 数据库
【Apollo】(3)---SpringBoot整合Apollo
【Apollo】(3)---SpringBoot整合Apollo
1079 0
|
XML 前端开发 JavaScript
从Vue2到Vue3, 一键升级前端开发技能
本文的目的,是为了让已经有 Vue2 开发经验的 人 ,快速掌握 Vue3 的写法。
下一篇
开通oss服务