线程的常用方法

线程的常用方法

方法名 static 功能描述 注意
start() 启动一个新线 程,在新的线程 运行 run 方法 中的代码 start 方法只是让线程进入就绪,里面代码不一定立刻 运行(CPU 的时间片还没分给它)。每个线程对象的 start方法只能调用一次,如果调用了多次会出现 IllegalThreadStateException
run() 新线程启动后会 调用的方法 如果在构造 Thread 对象时传递了 Runnable 参数,则 线程启动后会调用 Runnable 中的 run 方法,否则默 认不执行任何操作。但可以创建 Thread 的子类对象, 来覆盖默认行为
join() 等待线程运行结束
join(long n) 等待线程运行结束,最多等待 n 毫秒
getId() 获取线程长整型 的 id id唯一
getName() 获取线程名
setName(String) 修改线程名
getPriority() 获取线程优先级
setPriority(int) 修改线程优先级 java中规定线程优先级是1~10 的整数,较大的优先级 能提高该线程被 CPU 调度的机率
getState() 获取线程状态 Java 中线程状态是用 6 个 enum 表示,分别为: NEW(新建), RUNNABLE(可运行/就绪), BLOCKED(阻塞), WAITING(等待/不见不散), TIMED_WAITING(超时等待/过时不候), TERMINATED(终止) BLOCKED/WAITING/TIMED_WAITING 这三种状态都不会获得CPU使用权
isInterrupted() 判断是否被打断 不会清除 打断标记
isAlive() 线程是否存活 (还没有运行完 毕)
interrupt() 打断线程 如果被打断线程正在 sleep,wait,join 会导致被打断 的线程抛出 InterruptedException,并清除 打断标 记 ;如果打断的正在运行的线程,则会设置 打断标 记 ;park 的线程被打断,也会设置 打断标记
interrupted() static 判断当前线程是 否被打断 会清除 打断标记
currentThread() static 获取当前正在执行的线程
sleep(long n) static 让当前执行的线 程休眠n毫秒, 休眠时让出 cpu 的时间片给其它 线程
yield() static 提示线程调度器让出当前线程对 CPU的使用 主要是为了测试和调试

1 run()和start()

我想大家肯定会有这样的疑问,为什么不直接调用run()启动线程。

示例代码

import lombok.extern.slf4j.Slf4j;

/**
 * @author : look-word
 * 2022-08-13 15:31
 **/
@Slf4j
public class C1_RunAndStart {
    public static void main(String[] args) {
        Thread thread = new Thread() {
            @Override
            public void run() {
                log.info("当前线程 {} ", Thread.currentThread().getName());
            }
        };
        thread.run();
        thread.start();
    }
}

不难发现,他们俩被不同的线程执行了。

  • 直接执行run(), 会把run 方法当成一个main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。
  • new 一个Thread,线程进入了新建状态,调用start() 方法,会启动一个线程并使线程进入了就绪状态,当分配到 时间片 后就可以开始运行了。start() 会执行线程的相应准备工作,然后自动执行run() 方法的内容,这是真正的多线程工作。

image-20220813155102961

2 sleep

  1. 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
  2. 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
  3. 睡眠结束后的线程未必会立刻得到执行
  4. 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性

3 yield

  1. 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程
  2. 具体的实现依赖于操作系统的任务调度器

4 join

使其他线程等待,调用join方法的线程执行完成。

示例代码

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

/**
 * @author : look-word
 * 2022-08-13 16:34
 **/
@Slf4j
public class C3_Join {
    static int num = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                TimeUnit.MILLISECONDS.sleep(1000); // 使线程睡眠
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            num =10;
        },"t1");
        t1.start();
//        t1.join();
        log.info("num :{}",num);
    }
}
// 执行结果 会先输出num的值 程序不会立马结束运行。(异步)
16:50:39.034 [main] INFO c_常用方法.C3_Join - num :0
// 可以看到 上面的join方法是注释掉的。 我们给它放开之后的执行结果。会等待(同步)
16:52:40.783 [main] INFO c_常用方法.C3_Join - num :10

示例代码2

下面给大家演示的是带参数的join方法。

  • 先给出结论
    • t1.join(1000); 其他线程只会等待t1线程1000毫秒,过了这个时间并不会继续等待,往下执行。
    • t1.join(3000); 可以看到,t1线程会睡眠2000ms,而其他线程会等待起3000ms。当我们t1线程执行完成后。其他线程也不会继续等待3000。会立即往下执行。
        Thread t1 = new Thread(() -> {
            try {
                TimeUnit.MILLISECONDS.sleep(2000); // 使线程睡眠
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            num =10;
        },"t1");
        log.info("start。。。");
        t1.start();
        t1.join(1000); // 等待具体的毫秒数
        log.info("num :{}",num);

5 interrupt

如果被打断线程正在sleep,wait,join会导致被打断 的线程抛出 InterruptedException,并清除打断标记 ;如果打断的正在运行的线程,则会设置 打断标记 ;park 的线程被打断,也会设置 打断标记。

示例代码1

打断 sleep,wait,join 的线程

  • 会清空打断状态
        Thread t1 = new Thread(() -> {
            try {
                Thread.sleep(1000);   // 睡眠t1线程
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        t1.start();
        Thread.sleep(10);  // 睡眠主线程
        t1.interrupt(); // 打断t1线程
        log.debug("{}的打断状态{}", t1.getName(), t1.isInterrupted());

image-20220813172555819

示例代码2

打断正常运行的线程

  • 可以看到程序的执行结果,在被打断前,t1一直在执行。
  • 打断正常运行的线程, 不会清空打断状态
    /**
     * 打断正常运行的线程
     */
    private static void test2() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while (true){
                log.info("{} 线程 running。。",Thread.currentThread().getName());
                if (Thread.currentThread().isInterrupted()){ // 是否被打断
                    break;
                }
            }
        },"t1");
        t1.start();
        t1.interrupt(); // 打断线程
        log.debug("{}的打断状态{}", t1.getName(), t1.isInterrupted());
    }

image-20220813173312147

终止模式之两阶段终止模式

在一个线程 T1 中如何“优雅”终止线程 T2?这里的【优雅】指的是给 T2 一个料理后事的机会。

  • 错误思路
    • 使用线程对象的 stop() 方法停止线程
      • stop 方法会真正杀死线程,如果这时线程锁住了共享资源,那么当它被杀死后就再也没有机会释放锁, 其它线程将永远无法获取锁
    • 使用 System.exit(int) 方法停止线程
      • 目的仅是停止一个线程,但这种做法会让整个程序都停止

image-20220813204112280

代码实现

如果看不懂,结合上面的流程图观看。

  • 我们程序模拟的是,一个监控程序,先正常执行,在3500ms后,通过打断运行的监控线程 (打断正在运行的线程,会标记为true),而终止对监控线程的记录(当标记为true,结束程序的运行)。
import lombok.extern.slf4j.Slf4j;

/**
 * @author : look-word
 * 2022-08-13 18:15
 **/
public class 两极阶段终止模式 {
    public static void main(String[] args) throws InterruptedException {
        Monitor monitor = new Monitor();
        monitor.start(); // 启动监控程序
        Thread.sleep(3500);
        monitor.stop(); // 停止监控程序
    }

}
@Slf4j
class Monitor{ // 监控程序
    Thread monitor;
    // 启动监控线程
    public void start(){
        monitor = new Thread(() ->{
            while (true){
                Thread current = Thread.currentThread();
                if (current.isInterrupted()){
                    log.error("料理后事 结束!");
                    break;
                }
                try {
                    Thread.sleep(1000);
                    log.debug("执行监控记录!!");
                } catch (InterruptedException e) {
                    // 重写设置打断
                    Thread.currentThread().interrupt();
                    e.printStackTrace();
                }
            }
        });
        monitor.start(); // 启动监控线程
    }

    // 停止监控线程
    public void stop(){
        monitor.interrupt(); // 打断线程
    }
}

6 pack

LockSupport.park(); 打断当前线程

  • join 会使所有线程等待 (同步)
  • LockSupport.park(); 只会使当前线程等待 (异步)

描述: 描述t1线程启动,被 LockSupport.park();打断线程。

视频教程 p41

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.LockSupport;

/**
 * join 会使所有线程等待 (同步)
 * LockSupport.park(); 只会使当前线程等待 (异步)
 * @author : look-word
 * 2022-08-13 21:02
 **/
@Slf4j
public class C5_Park {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            log.debug("pack...");
            log.debug("打断状态前:{}", Thread.currentThread().isInterrupted());
            LockSupport.park();  // 使当前线程等待
            log.debug("unPark...");
            log.debug("打断状态后:{}", Thread.currentThread().isInterrupted());
        }, "t1");
        t1.start();
        Thread.sleep(500);
        // 这里会等500ms 立即执行
        log.debug("打断状态:{}", Thread.currentThread().isInterrupted());
        t1.interrupt(); // 打断线程
    }
}

结果

21:17:46.920 [t1] DEBUG c_常用方法.C5_Park - pack...
21:17:46.923 [t1] DEBUG c_常用方法.C5_Park - 打断状态前:false
21:17:47.422 [main] DEBUG c_常用方法.C5_Park - 打断状态:false 
21:17:47.422 [t1] DEBUG c_常用方法.C5_Park - unPark...
21:17:47.422 [t1] DEBUG c_常用方法.C5_Park - 打断状态后:true