线程状态的转换,sleep()、yield()和wait()方法的区别,join()方法的作用,打断线程的3种方式、volatile关键字

目录

1. 线程的生命周期 6种状态

1.1 线程的生命周期

1.2 线程的6种状态

1.3 线程的6种状态之间是如何变化的?

2. 如何保证线程顺序执行?join()

3.sleep()、yield()和wait()方法的区别?

3.1 面试回答(★):

3.2 详细解释

4.如何停止一个正在运行的线程?

4.1 三种方式可以停止线程

4.1.1 volatile关键字的作用

4.2 代码举例

4.2.1 使用退出标志(flag),使线程正常退出

4.2.2 使用stop方法强行终止(不推荐,方法已作废)

4.2.3 使用interrupt方法中断线程

1.两种情况

2.interrupt相关常用API


1. 线程的生命周期 6种状态

1.1 线程的生命周期

线程的生命周期也就是线程从生到死的过程中,经历的各种状态及状态转换

1.2 线程的6种状态


NEW: 新建状态,线程还没有启动
RUNNABLE: 可以运行状态,线程调用了start()方法后处于这个状态
BLOCKED: 锁阻塞状态,没有获取到锁处于这个状态
WAITING: 无限等待状态,线程执行时被调用了wait方法处于这个状态
TIMED_WAITING: 计时等待状态,线程执行时被调用了sleep(毫秒)或者wait(毫秒)方法处于这个状态
TERMINATED: 终止状态, 线程执行完毕或者遇到异常时,处于这个状态。

1.3 线程的6种状态之间是如何变化的?

这几种状态之间切换关系如下图所示

注意:sleep不会释放锁, wait会把锁释放

2. 如何保证线程顺序执行?join()

当一个线程调用另一个线程的 join() 方法时,调用 join() 的这个线程会暂停,直到被 join() 的线程执行完毕

例如:

package com.xcw.codefriend.controller;

public class A {


     Thread t1 = new Thread(()->{
        System.out.println("t1执行");
    });

    Thread t2 = new Thread(()->{
        try {
            t1.join();
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("t2执行");
    });

    Thread t3 = new Thread(()->{
        try {
            t2.join();
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("t3执行");
    });

    public static void main(String[] args) {
        A a = new A();
        a.t1.start();
        a.t2.start();
        a.t3.start();
    }
}

结果:

代码分析

代码中的线程 t1t2t3 是按依赖关系来启动和执行的:

  1. t2 依赖 t1:线程 t2 调用了 t1.join(),这意味着 t2 会等待 t1 执行完后,t2 才能继续。

  2. t3 依赖 t2:线程 t3 调用了 t2.join(),这意味着 t3 会等待 t2 执行完后,t3 才能继续。

3. 3种休眠线程的方式

1.Thread.sleep()

2.Object.wait()          sleep()、wait()的用法看4.2

3.LockSupport.park()

Java中的线程休眠大法系列(三)LockSupport.park()-优快云博客

面试 LockSupport.park()会释放锁资源吗? - 彤哥读源码 - 博客园 (cnblogs.com)

4.sleep()、yield()和wait()方法的区别?

4.1 面试回答(★):

共同点:

wait(),wait(long)、sleep(long)和yield() 的效果都是让当前线程暂时放弃CPU的使用权,进入阻塞状态

区别:

Object.wait():必须搭配锁使用,wait()调用会放弃锁 和 资源(当前线程放弃cpu,别的线程可以使用),线程进入阻塞状态可以使用notify() 和 notifyAll()唤醒

Thread.sleep() :不必搭配锁也可使用,sleep()只会放弃cpu但是锁是拿着的(当前线程放弃cpu,别的线程也用不了)

Thread.yield()当前线程愿意让出cpu时间片,当前线程并没有被阻塞,处于可运行(RUNNABLE)的状态,只有优先级等于或者高于当前线程的线程才可能获取cpu时间片,没有,则继续占用cpu。

4.2 wait()、sleep() 、sleep() 详细解释

Object.wait(): 使当前线程进入休眠状态(TIMED_WAITING状态),wait()的调用必须先获取wait()对象的锁,而sleep则无此限制,方法执行后会释放对象锁,允许其它线程获得该对象锁(我放弃cpu,但你们还可以用)进入 wait 状态的线程能够被 notify 和 notifyAll 线程唤醒,而 sleep 状态的线程不能被 notify 方法唤醒;

Thread.sleep() : 使当前线程进入休眠状态(TIMED_WAITING状态),休眠指定时间内暂时放弃对cpu的占用,线程会被唤醒并进入可运行状态,等待CPU时间片的重新分配。sleep()不获取sleep()对象的锁也可以使用,sleep()如果在synchronized代码块中执行,并不会释放对象锁(我放弃cpu,你们也用不了)

Thread.sleep() :只是通知线程调度器当前线程愿意让出cpu时间片,当前线程并没有被阻塞,处于可运行(RUNNABLE)的状态,只有优先级等于或者高于当前线程的线程才可能获取cpu时间片。

  • Thread.sleep()必须传入时间,Object.wait()可传可不传,不传表示一直阻塞下去;
  • Thread.sleep()到时间了会自动唤醒,然后继续执行;
  • Object.wait()不带时间的,需要另一个线程使用Object.notify()唤醒;
  • Object.wait()带时间的,假如没有被notify,到时间了会自动唤醒,这时又分好两种情况,一是立即获取到了锁,线程自然会继续执行;二是没有立即获取锁,线程进入同步队列等待获取锁;

调用 wait 方法之后,线程会变为 WATING 状态,而调用 sleep 方法之后,线程会变为 TIMED_WAITING 状态。

4.3 LockSupport.park()详细解释

1. LockSupport.park()休眠线程,LockSupport.unpark()唤醒线程,两个方法配合使用。也可以通过LockSupport.parkNanos()指定休眠时间后,自动唤醒。

2. LockSupport.park()不会释放monitor锁。


3. 线程被打断,LockSupport.park()不会抛出异常,也不会吞噬掉interrupt的状态,调用者可以获取interrupt状态,自行进行判断,线程是由于什么原因被唤醒了。


4. LockSupport.park()会是线程进入WAITING状态,而LockSupport.parkNanos(long nanos) 会进入TIMED_WAITING状态。


5. LockSupport.park(Object blocker)和LockSupport.getBlocker(t1)配合使用,可以进行自定义数据传输。

5.如何停止一个正在运行的线程?

5.1 三种方式可以停止线程

有三种方式可以停止线程
  • 使用volatile修饰的退出标志,使线程正常退出,也就是当run方法完成后线程终止
  • 使用stop方法强行终止(不推荐,方法已作废)
  • 使用interrupt方法中断线程

5.1.1 volatile关键字的作用

volatile它的主要作用是保证变量的可见性和禁止指令重排优化。

1)可见性(Visibility):
volatile关键字确保变量的可见性。当一个线程修改了volatile变量的值,新值会立即被刷新到主内存中,其他线程在读取该变量时可以立即获得最新的值。这样可以避免线程间由于缓存一致性问题导致的"看见"旧值的现象。


2)禁止指令重排序(Ordering):
volatile还通过内存屏障来禁止特定情况下的指令重排序,从而保证程序的执行顺序符合预期。对
volatile变量的写操作会在其前面插入一个StoreStore屏障,而对volatile变量的读操作则会在其后
插入一个LoadLoad屏障。这确保了在多线程环境下,某些代码块执行顺序的可预测性。

5.2 代码举例

代码参考如下:

5.2.1 使用退出标志(flag),使线程正常退出

run方法中每次子线程执行前都先对flag标签判断,为true才执行,如果想要终止,就调用主线程修改对flag的判断结果为flase
public class MyInterrupt1 extends Thread {
    volatile boolean flag = false ; // 线程执行的退出标记
    @Override
    public void run() {
        while(!flag) {
            System.out.println("MyThread...run...");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) throws
            InterruptedException {
// 创建MyThread对象
        MyInterrupt1 t1 = new MyInterrupt1() ;
        t1.start();
// 主线程休眠6秒
        Thread.sleep(6000);
// 更改标记为true
        t1.flag = true ;
    }
}

5.2.2 使用stop方法强行终止(不推荐,方法已作废)

与方法1类似,只不过这里不是主线程修改flag,而是子线程执行stop()强制关闭当前线程。

public class MyInterrupt2 extends Thread {
   
    @Override
    public void run() {
        while(true) {
            System.out.println("MyThread...run...");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) throws
            InterruptedException {
// 创建MyThread对象
        MyInterrupt2 t1 = new MyInterrupt2() ;
        t1.start();
// 主线程休眠2秒
        Thread.sleep(6000);
// 调用stop方法
        t1.stop();
    }
}

为什么stop()方法不推荐使用:

  1. 不安全stop()方法会立即停止线程,可能会导致线程持有的锁没有释放,造成死锁。

  2. 不可预测:线程可能在执行到一半的时候被强制停止,并且不会清理线程正在执行的任务,这可能会导致数据不一致或者资源泄露。

  3. 违反线程协作模型:线程应该通过协作的方式来中断,而不是强制停止。

5.2.3 使用interrupt方法中断线程

1.两种情况

分两种情况:

  • 打断阻塞的线程(sleep,wait,join)的线程,线程会抛出InterruptedException异常
  • 打断正常的线程,可以根据打断状态来标记是否退出线程

2.interrupt相关常用API

上面提到的标记其实就是一个true或false标签,例如:

 Thread current = Thread.currentThread(); //获取当前线程
 boolean interrupted = current.isInterrupted(); //判断线程是否被打断,没有则返回false

详细的看下面例2

举例:

1. 打断阻塞的线程(sleep,wait,join)的线程

package com.itheima.basic;

public class MyInterrupt3 {
    public static void main(String[] args) throws
            InterruptedException {
    //1.打断阻塞的线程
    Thread t1 = new Thread(()->{
    System.out.println("t1 正在运行...");
   
     try {
        Thread.sleep(5000);

    } catch (InterruptedException e) {
        e.printStackTrace();
    }

                                }, "t1");

    t1.start();
    Thread.sleep(500);
    t1.interrupt();
    System.out.println(t1.isInterrupted());

  }

}

运行结果:

打断阻塞的线程(sleep,wait,join)的线程,线程会抛出InterruptedException异常

2. 打断正常的线程,可以根据打断状态来标记是否退出线程

package com.itheima.basic;

public class MyInterrupt3 {
    public static void main(String[] args) throws
            InterruptedException {

//2.打断正常的线程
        Thread t2 = new Thread(() -> {
            while (true) {
                Thread current = Thread.currentThread();
                boolean interrupted = current.isInterrupted();
                if (interrupted) {
                    System.out.println("打断状态:" + interrupted);
                    break;
                }
            }
        }, "t2");
        t2.start();
        Thread.sleep(500);
        t2.interrupt();
    }
}

 刚开始while(true)

Thread current = Thread.currentThread(); //获取当前线程
 boolean interrupted = current.isInterrupted(); //判断线程是否被打断,没有则返回false

则if( interrupted)为false不执行

下面t2.interrupt( ),打断线程,则boolean interrupted = current.isInterrupted();返回true,则while循环死循环里面的if会执行,最后break跳出死循环,线程t2 被打断运行。

线程基础知识可以参考我以前的笔记,都有详细的介绍

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值