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 线程结束, 所以就出现上面结果.


相关文章
|
2月前
|
Java
在Java多线程编程中,实现Runnable接口通常优于继承Thread类
【10月更文挑战第20天】在Java多线程编程中,实现Runnable接口通常优于继承Thread类。原因包括:1) Java只支持单继承,实现接口不受此限制;2) Runnable接口便于代码复用和线程池管理;3) 分离任务与线程,提高灵活性。因此,实现Runnable接口是更佳选择。
46 2
|
2月前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
26 3
|
2月前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
41 1
|
2月前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
35 2
|
7月前
|
Java Linux API
Java多线程基础-4:详解Thread类及其基本用法 (一)
Java 中的 `Thread` 类是用来管理线程的,每个线程都是通过 `Thread` 类的对象来描述。
309 0
|
7月前
|
Java API 调度
【Java多线程】Thread类的基本用法
【Java多线程】Thread类的基本用法
49 0
|
7月前
|
Java 程序员 调度
Thread类及常见方法
Thread类及常见方法
|
Java 调度
【多线程】Thread类的基本用法
【多线程】Thread类的基本用法
【多线程】Thread类的基本用法
|
7月前
|
资源调度 调度
Thread的基本方法(3)-yield方法的分析与实例说明
Thread的基本方法(3)-yield方法的分析与实例说明
76 0
Thread 类的基本用法
比较推荐:使用 lambda 表达式创建线程的时候不用重写 run 方法。 不需要显式重写run方法的原因是因为线程的目标方法已经在Lambda表达式中定义了。Lambda表达式是一种用于创建匿名函数的语法糖,它可以将一个方法(或一段代码块)包装为一个函数对象。当您使用Lambda表达式创建线程时,Lambda表达式的内容会被视为线程执行的任务,这个任务会自动成为run方法的实现。
82 0