多线程教程(四)常见方法

多线程教程(四)常见方法

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

(1) 调用run()

@Slf4j(topic = "c.TestStart")
public class TestStart {
    public static void main(String[] args) {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                log.debug(Thread.currentThread().getName());
                FileReader.read(Constants.MP4_FULL_PATH);
            }
        };

        t1.run();
        log.debug("do other things ...");
    }
}

输出

19:39:14 [main] c.TestStart - main
19:39:14 [main] c.FileReader - read [1.mp4] start ...
19:39:18 [main] c.FileReader - read [1.mp4] end ... cost: 4227 ms
19:39:18 [main] c.TestStart - do other things ...

程序仍在 main 线程运行, FileReader.read() 方法调用还是同步的

(2) 调用start()

讲上述代码的 t1.run() 替换为 t1.start()

输出

19:41:30 [main] c.TestStart - do other things ...
19:41:30 [t1] c.TestStart - t1
19:41:30 [t1] c.FileReader - read [1.mp4] start ...
19:41:35 [t1] c.FileReader - read [1.mp4] end ... cost: 4542 ms

程序在 t1 线程运行, FileReader.read() 方法调用是异步的

(3) 小结

  • 直接调用 run 是在主线程中执行了 run,没有启动新的线程

  • 使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码

3. sleep() 与 yield()

(1)基本概念

sleep

  1. 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)

  2. 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException

  3. 睡眠结束后的线程未必会立刻得到执行

  4. 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性

yield

  1. 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程

  2. 具体的实现依赖于操作系统的任务调度器

线程优先级

  1. 线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它

  2. 如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用

4. join 方法详解

(1) 为什么使用join,什么情况下使用join

static int r = 0;
public static void main(String[] args) throws InterruptedException {
 	test1();
}
private static void test1() throws InterruptedException {
 	log.debug("开始");
 	Thread t1 = new Thread(() -> {
	log.debug("开始");
 	sleep(1);
 	log.debug("结束");
 	r = 10;
 });
 t1.start();
 log.debug("结果为:{}", r);
 log.debug("结束");
}

分析

  1. 因为主线程和线程 t1 是并行执行的,t1 线程需要 1 秒之后才能算出 r=10

  2. 而主线程一开始就要打印 r 的结果,所以只能打印出 r=0

解决方法

  1. 用 sleep 行不行?为什么?

​ sleep不能判断需要睡多久,睡短了其他线程执行不完,睡长了影响性能

​ 2. 用 join,加在 t1.start() 之后即可

public static void test3() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            sleep(2);
            r1 = 10;
        });

        long start = System.currentTimeMillis();
        t1.start();

        // 线程执行结束会导致 join 结束
        log.debug("join begin");
        t1.join();
        long end = System.currentTimeMillis();
        log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
    }

Thread.join()能够让所在线程等待指定线程结束后执行后续代码。

(2) join(Long millisecond)

在join的构造函数中加入Long类型的毫秒值,可以设置join的等待时间,如果超过了设置的毫秒值,就会执行所在线程的后续代码。

5. interrupt方法详解

(1) 方法功能介绍

打断 sleep,wait,join 的线程,这几个方法都会让线程进入阻塞状态

打断 sleep 的线程, 会清空打断状态,打断正常运行的线程不会清空打断状态。以 sleep 为例

private static void test1() throws InterruptedException {
    Thread t1 = new Thread(()->{
        sleep(1);
    }, "t1");
    t1.start();

    sleep(0.5);
    t1.interrupt();
    log.debug(" 打断状态: {}", t1.isInterrupted());
}

输出:

java.lang.InterruptedException: sleep interrupted
 at java.lang.Thread.sleep(Native Method)
 at java.lang.Thread.sleep(Thread.java:340)
 at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
 at cn.itcast.n2.util.Sleeper.sleep(Sleeper.java:8)
 at cn.itcast.n4.TestInterrupt.lambda$test1$3(TestInterrupt.java:59)
 at java.lang.Thread.run(Thread.java:745)
21:18:10.374 [main] c.TestInterrupt - 打断状态: false

打断正常线程:

private static void test2() throws InterruptedException {
    Thread t2 = new Thread(()->{
        while(true) {
            Thread current = Thread.currentThread();
            boolean interrupted = current.isInterrupted();
            if(interrupted) {
                log.debug(" 打断状态: {}", interrupted);
                break;
            }
        }
    }, "t2");
    t2.start();

    sleep(0.5);
    t2.interrupt();
}

输出:

20:57:37.964 [t2] c.TestInterrupt - 打断状态: true

(2) 两阶段终止(Two Phase Termination)

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

① 错误思路

  • 使用线程对象的 stop() 方法停止线程

  • stop 方法会真正杀死线程,如果这时线程锁住了共享资源,那么当它被杀死后就再也没有机会释放锁,其它线程将永远无法获取锁

  • 使用 System.exit(int) 方法停止线程

  • 目的仅是停止一个线程,但这种做法会让整个程序都停止

② 正确方法——两阶段终止模式

在这里插入图片描述

@Slf4j(topic = "c.TwoPhaseTermination")
class TwoPhaseTermination {
    // 监控线程
    private Thread monitorThread;
    // 停止标记
    private volatile boolean stop = false;
    // 判断是否执行过 start 方法
    private boolean starting = false;

    // 启动监控线程
    public void start() {
        synchronized (this) {
            if (starting) { // false
                return;
            }
            starting = true;
        }
        monitorThread = new Thread(() -> {
            while (true) {
                Thread current = Thread.currentThread();
                // 是否被打断
                if (stop) {
                    log.debug("料理后事");
                    break;
                }
                try {
                    Thread.sleep(1000);
                    log.debug("执行监控记录");
                } catch (InterruptedException e) {
                    current.interrupt();
                }
            }
        }, "monitor");
        monitorThread.start();
    }

    // 停止监控线程
    public void stop() {
        stop = true;
        monitorThread.interrupt();
    }
}

输出:

11:49:42.915 c.TwoPhaseTermination [监控线程] - 将结果保存
11:49:43.919 c.TwoPhaseTermination [监控线程] - 将结果保存
11:49:44.919 c.TwoPhaseTermination [监控线程] - 将结果保存
11:49:45.413 c.TestTwoPhaseTermination [main] - stop 
11:49:45.413 c.TwoPhaseTermination [监控线程] - 料理后事

(3)interrupt()打断 park 线程

park的作用也是让当前线程停下来

park和unpark并不是线程的方法,而是LockSupport的静态方法

暂停当前线程

LockSupport.park();//所在的线程调用

恢复某个线程的运行

LockSupport.unpark(暂停线程对象)//由另外的线程调用,

park/unpark与wait/notify的区别

wait/notify必须在有锁的情况下使用(需要关联Monitor对象),park/unpark没有这个限制条件。
park/unpark配对使用能够精确的指定具体的线程的阻塞/运行,notify只能随机唤醒一个线程
park/unpark配对使用可以先unpark,wait/notify配合使用不能够先notify。

打断 park 线程, 不会清空打断状态

private static void test3() throws InterruptedException {
     Thread t1 = new Thread(() -> {
         log.debug("park...");
         LockSupport.park();
         log.debug("unpark...");
         log.debug("打断状态:{}", Thread.currentThread().isInterrupted());
     }, "t1");
     t1.start();
     sleep(0.5);
     t1.interrupt();
}

输出:

21:11:52.795 [t1] c.TestInterrupt - park... 
21:11:53.295 [t1] c.TestInterrupt - unpark... 
21:11:53.295 [t1] c.TestInterrupt - 打断状态:true

当打断标记为true的情况下,park就会失效

private static void test4() {
     Thread t1 = new Thread(() -> {
         for (int i = 0; i < 5; i++) {
         log.debug("park...");
         LockSupport.park();
         log.debug("打断状态:{}", Thread.currentThread().isInterrupted());
         }
     });
     t1.start();
     sleep(1);
     t1.interrupt();
}

输出:

21:13:48.783 [Thread-0] c.TestInterrupt - park... 
21:13:49.809 [Thread-0] c.TestInterrupt - 打断状态:true 
21:13:49.812 [Thread-0] c.TestInterrupt - park... 
21:13:49.813 [Thread-0] c.TestInterrupt - 打断状态:true 
21:13:49.813 [Thread-0] c.TestInterrupt - park... 
21:13:49.813 [Thread-0] c.TestInterrupt - 打断状态:true 
21:13:49.813 [Thread-0] c.TestInterrupt - park... 
21:13:49.813 [Thread-0] c.TestInterrupt - 打断状态:true 
21:13:49.813 [Thread-0] c.TestInterrupt - park... 
21:13:49.813 [Thread-0] c.TestInterrupt - 打断状态:true

当我们需要将打断标记设置为false的时候,可以使用interrupted()(注意是interrupted,不是interrupt,两个方法的功能可以看前面方法汇总表)

private static void test4() {
    Thread t1 = new Thread(() -> {
        for (int i = 0; i < 5; i++) {
            log.debug("park...");
            LockSupport.park();
            log.debug("打断状态:{}", Thread.interrupted());
        }
    });
    t1.start();


    sleep(1);
    t1.interrupt();
}

输出

17:20:13.803 c.Test14 [Thread-0] - park...
17:20:14.814 c.Test14 [Thread-0] - 打断状态:true
17:20:14.817 c.Test14 [Thread-0] - park...
6. 不推荐使用的方法

还有一些不推荐使用的方法,这些方法已过时,容易破坏同步代码块,造成线程死锁

方法名static功能说明
stop()停止线程运行
suspend()挂起(暂停)线程运行
resume()恢复线程运行
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值