多线程的常用操作方法sleep() yield() join() interrupt()

本文深入探讨Java中线程的命名、休眠、让步、join等控制方法,解析线程停止的三种策略,包括使用标记位、stop方法及interrupt()方法的优缺点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

线程命名与获取

多线程的运行状态是不确定的,所以对于多线程操作必须有一个明确标识出线程对象的信息,这个信息往往通过名称来描述。在Thread类中提供有如下的线程名称方法:

在这里插入图片描述
我们看一段线程命名的代码:

public class MyRunnable implements Runnable  {
    public void run() {
        for(int i=0;i<3;i++)
        {
            System.out.println("当前线程:"+Thread.currentThread().getName()+" i="+i);
        }
    }

    public static void main(String[] args) {
        MyRunnable myThread=new MyRunnable();
        new Thread(myThread).start();//没有设置名称
        new Thread(myThread).start();//没有设置名称
        new Thread(myThread,"线程A").start();//设置名称

    }
}

在这里插入图片描述
主方法本身就是一个线程,所有的线程都是通过主线程创建并启动的。

实际上每当使用了java命令去解释程序的时候,都表示启动了一个新的JVM进程。而主方法只是这个进程上的一个线程而已。

现放一张图,以便后面的理解。
在这里插入图片描述
这是一张线程的五种状态,以及它们之间具体是如何从一个状态转换到另一个状态的。

线程的休眠(sleep()方法)

线程休眠:是指让线程暂缓执行,等到预计时间之后再继续执行。
线程休眠的时候回交出CPU的使用权,让CPU去执行其他的任务,但是要注意的是线程休眠的时候不会释放锁,也就是说,当线程持有某个对象的锁,则即使是线程休眠,其他对象也是无法访问这个对象的。

public static native void sleep(long millis) throws InterruptedException

示例:

public class MyRunnable implements Runnable  {
    public void run() {
        for(int i=0;i<100;i++)
        {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("当前线程:"+Thread.currentThread().getName()+" i="+i);
        }
    }

    public static void main(String[] args) {
        MyRunnable myThread=new MyRunnable();
        new Thread(myThread).start();
        new Thread(myThread).start();

    }
}

仔细观察结果会发现,结果有了间隔性。

通过代码观察会错误的认为这三个线程是同时休眠的,但是千万要记住,所有的代码是依次进入到run()方法中的。真正进入到方法的对象可能是多个,也可能是一个。进入代码的顺序可能有差异,但是总体的执行是并发执行。

线程让步(yield()方法)

线程让步:暂停当前正在执行的任务,执行其他任务,意思就是调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。

注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的

在这里我要多说几句,因为这一点我自己开始就很难理解,首先我们明确一个事实,sleep()调用会使线程进入阻塞状态,而yield()方法调用会使线程进入就绪状态。这是因为sleep方法交出CPU使用权,然后在休眠的这个时间段里,什么也不做,等到时间到了,在继续。而yield方法交出CPU使用权以后,在这一时间段里它一直在等待CPU的使用权,并且在yield方法调用时,交出CPU使用权的时间是不确定的,通俗的来讲,其实就是sleep方法在休眠的时间段里,如果说,有别的线程给它CPU的使用权,它是不会要的,因为它的时间段没有结束,所以它不算是就绪状态,也并非运行状态,而yield在这一时间段里,如果别的线程给它CPU的使用权,它是会接受的,所以就进入了就绪状态,其实从字面意思就可以理解了,一个是休眠,一个是让步。

观察yield()方法

public class MyRunnable implements Runnable  {
    public void run() {
        for(int i=0;i<3;i++)
        {
            Thread.yield();
            System.out.println("当前线程:"+Thread.currentThread().getName()+" i="+i);
        }
    }
    public static void main(String[] args) {
        MyRunnable myThread=new MyRunnable();
        new Thread(myThread,"线程A").start();
        new Thread(myThread,"线程B").start();
        new Thread(myThread,"线程C").start();
    }
}

join()方法

等待该线程终止。意思就是如果在主线程中调用该方法时就会让主线程休眠,让调用该方法的线程run方法先执行完毕之后在开始执行主线程。这里不太好理解,看个例子就能有所明白了。

// 主线程
public class Father extends Thread {
    public void run() {
        Son s = new Son();
        s.start();
        s.join();
        ...
    }
}
// 子线程
public class Son extends Thread {
    public void run() {
        ...
    }
}

在上面的代码中,有两个线程,一个是Father,一个是Son,而Son是在Father中创建的,所有Son是Father的子线程,Father是主线程,现在Father中创建了一个子线程,然后通过s.start()启动子线程,再通过s.join()让主线程休眠,一直到子线程执行完毕后,再继续执行主线程。

上面的例子比较泛,再看一个具体一些的,就能对比出来。

public class MyRunnable implements Runnable  {
    public void run() {
        try {
        System.out.println("主线程睡眠前时间");
        printTime();
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName());
        System.out.println("主线程睡眠后时间");
        printTime();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
    public static void printTime() {
        Date date=new Date();
        DateFormat format=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String time=format.format(date);
        System.out.println(time);
    }
    public static void main(String[] args) throws InterruptedException {
        MyRunnable myThread=new MyRunnable();
        Thread thread=new Thread(myThread,"子线程A");
        thread.start();
        System.out.println(Thread.currentThread().getName());
        thread.join();
        System.out.println("代码结束");
    }
}

上面这个例子,在main方法中可以看到创建并启动了一个子线程A,此时还没有使用join方法,所以主线程还没有休眠所以会先打印main线程的名称,之后在调用了join方法,此时主线程开始休眠直到子线程A执行完毕后,再继续执行主线程,结果如下:

在这里插入图片描述

线程停止

多线程中有三种方式可以停止线程:

  • 设置标记位,可以使线程正常退出。
  • 使用stop方法强制使线程退出,但是该方法不太安全所以已经被废弃了。
  • 使用Thread类中的一个 interrupt() 可以中断线程。

使用标记位退出线程

public class MyRunnable implements Runnable  {
    private boolean flag=true;
    public void run() {
        int i=1;
        while(flag){
            try {
                Thread.sleep(1000);
            System.out.println("第"+i+"次执行"+Thread.currentThread().getName()+" i="+i);
            i++;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
    public void setFlag(boolean flag) {
        this.flag = flag;
    }
    public static void main(String[] args) throws InterruptedException {
        MyRunnable myThread=new MyRunnable();
        Thread thread=new Thread(myThread,"子线程A");
        thread.start();
        Thread.sleep(3000);
        myThread.setFlag(false);
        System.out.println("代码结束!");
    }
}

使用stop方法使线程退出

 public static void main(String[] args) throws InterruptedException {
        MyRunnable myThread=new MyRunnable();
        Thread thread=new Thread(myThread,"子线程A");
        thread.start();
        Thread.sleep(3000);
        myThread.setFlag(false);
        System.out.println("代码结束!");
    }
}

使用stop方法强制退出线程,由于这种方法不安全所以已经被废弃。

为什么说stop不安全?这是因为stop会解除由线程获取的所有锁定,当在一个线程对象上调用stop()方法时,这个线程对象所运行的线程就会立即停止,假如一个线程正在执行:synchronized void { x = 3; y = 4;} 由于方法是同步的,多个线程访问时总能保证x,y被同时赋值,而如果一个线程正在执行到x = 3;时,被调用了 stop()方法,即使在同步块中,它也会马上stop了,这样就产生了不完整的残废数据。

使用Thread.interrupt()

public class MyRunnable implements Runnable  {
    private boolean flag=true;
    public void run() {
        int i=1;
        while(flag){
            try {
                Thread.sleep(1000);
              boolean bool=Thread.currentThread().isInterrupted();
           if(bool){
               System.out.println("非阻塞状态下执行该操作。。。");
           }
           else{
               System.out.println("第"+i+"次执行"+Thread.currentThread().getName());
           i++;
           }
            } catch (InterruptedException e) {
                System.out.println("退出了");
            }
        }

    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public static void main(String[] args) throws InterruptedException {
        MyRunnable myThread=new MyRunnable();
        Thread thread=new Thread(myThread,"子线程A");
        thread.start();
        Thread.sleep(3000);
        thread.interrupt();
        System.out.println("代码结束!");

    }
}

在上面的代码中,在非阻塞状态下,bool值一直是false,所以会执行“第一次…第二次…”,如果收到中断信号,bool会变成true,此时会输出“非阻塞状态下执行该操作。。。”,而且线程不会因此停止运行,因为只是改变了一个中断信号。

其实interrupt() 方法只是改变中断状态而已,它不会中断一个正在运行的线程。这一方法实际完成的是,给受阻塞的线程发出一个中断信号,这样受阻线程就得以退出阻塞的状态。
然而 interrupt() 方法并不会立即执行中断操作;具体而言,这个方法只会给线程设置一个为true的中断标志(中断标志只是一个布尔类型的变量),而设置之后,则根据线程当前的状态进行不同的后续操作。如果,线程的当前状态处于非阻塞状态,那么仅仅是线程的中断标志被修改为true而已;如果线程的当前状态处于阻塞状态,那么在将中断标志设置为true后,还会有如下三种情况之一的操作:如果是 wait、sleep以及join 三个方法引起的阻塞,那么会将线程的中断标志重新设置为false,并抛出一个InterruptedException ;
如果在中断时,线程正处于非阻塞状态,则将中断标志修改为true,而在此基础上,一旦进入阻塞状态,则按照阻塞状态的情况来进行处理;例如,一个线程在运行状态中,其中断标志被设置为true之后,一旦线程调用了wait、join、sleep方法中的一种,立马抛出一个InterruptedException,且中断标志被程序会自动清除,重新设置为false。

通过上面的分析,我们可以总结,调用线程类的interrupted方法,其本质只是设置该线程的中断标志,将中断标志设置为true,并根据线程状态决定是否抛出异常。因此,通过interrupted方法真正实现线程的中断原理是:开发人员根据中断标志的具体值,来决定如何退出线程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值