• 线程sleep,wait,notify,join,yield方法解析


    线程的五种状态

    线程从创建到销毁一般分为五种状态,如下图:

    1) 新建

    当用new关键字创建一个线程时,就是新建状态。

    2) 就绪

    调用了 start 方法之后,线程就进入了就绪阶段。此时,线程不会立即执行run方法,需要等待获取CPU资源。

    3) 运行

    当线程获得CPU时间片后,就会进入运行状态,开始执行run方法。

    4) 阻塞

    当遇到以下几种情况,线程会从运行状态进入到阻塞状态。

    • 调用sleep方法,使线程睡眠。
    • 调用wait方法,使线程进入等待。
    • 当线程去获取同步锁的时候,锁正在被其他线程持有。
    • 调用阻塞式IO方法时会导致线程阻塞。
    • 调用suspend方法,挂起线程,也会造成阻塞。

    需要注意的是,阻塞状态只能进入就绪状态,不能直接进入运行状态。因为,从就绪状态到运行状态的切换是不受线程自己控制的,而是由线程调度器所决定。只有当线程获得了CPU时间片之后,才会进入运行状态。

    5) 死亡

    当run方法正常执行结束时,或者由于某种原因抛出异常都会使线程进入死亡状态。另外,直接调用stop方法也会停止线程。但是,此方法已经被弃用,不推荐使用。

    线程常用方法

    1)sleep

    当调用 Thread.sleep(long millis) 睡眠方法时,就会使当前线程进入阻塞状态。millis参数指定了线程睡眠的时间,单位是毫秒。 当时间结束之后,线程会重新进入就绪状态。

    注意,如果当前线程获得了一把同步锁,则 sleep方法阻塞期间,是不会释放锁的。

    2) wait、notify和notifyAll

    首先,它们都是Object类中的方法。需要配合 Synchronized关键字来使用。

    调用线程的wait方法会使当前线程等待,直到其它线程调用此对象的notify/notifyAll方法。 如果,当前对象锁有N个线程在等待,则notify方法会随机唤醒其中一个线程,而notifyAll会唤醒对象锁中所有的线程。需要注意,唤醒时,不会立马释放锁,只有当前线程执行完之后,才会把锁释放。

    另外,wait方法和sleep方法不同之处,在于sleep方法不会释放锁,而wait方法会释放锁。wait、notify的使用如下:

    public class WaitTest {
        private static Object obj = new Object();
    
        public static void main(String[] args) throws InterruptedException {
            ListAdd listAdd = new ListAdd();
    
            Thread t1 = new Thread(() -> {
                synchronized (obj){
                    try {
                        for (int i = 0; i < 10; i++) {
                            listAdd.add();
                            System.out.println("当前线程:"+Thread.currentThread().getName()+"添加了一个元素");
                            Thread.sleep(300);
                            if(listAdd.getSize() == 5){
                                System.out.println("发出通知");
                                obj.notify();
                            }
                        }
                    } catch(InterruptedException e){
                        e.printStackTrace();
                    }
                }
            });
    
            Thread t2 = new Thread(() -> {
                synchronized (obj){
                    try {
                        if(listAdd.getSize() != 5){
                            obj.wait();
                        }
                    } catch(InterruptedException e){
                        e.printStackTrace();
                    }
                    System.out.println("线程:"+Thread.currentThread().getName()+"被通知.");
                }
    
            });
    
            t2.start();
            Thread.sleep(1000);
            t1.start();
        }
    }
    
    class ListAdd {
        private static volatile List<String> list = new ArrayList<String>();
    
        public void add() {
            list.add("abc");
        }
    
        public int getSize() {
            return list.size();
        }
    }
    

    以上,就是创建一个t2线程,判断list长度是否为5,不是的话,就一直阻塞。然后,另外一个t1线程不停的向list中添加元素,当元素长度为5的时候,就去唤醒阻塞中的t2线程。

    然而,我们会发现,此时的t1线程会继续往下执行。直到方法执行完毕,才会把锁释放。t1线程去唤醒t2的时候,只是让t2具有参与锁竞争的资格。只有t2真正获得了锁之后才会继续往下执行。

    3) join

    当线程调用另外一个线程的join方法时,当前线程就会进入阻塞状态。直到另外一个线程执行完毕,当前线程才会由阻塞状态转为就绪状态。

    或许,你在面试中,会被问到,怎么才能保证t1,t2,t3线程按顺序执行呢。(因为,我们知道,正常情况下,调用start方法之后,是不能控制线程的执行顺序的)

    咳咳,当前青涩的我,面试时就被问到这个问题,是一脸懵逼。其实,是非常简单的,用join方法就可以轻松实现:

    public class TestJoin {
        public static void main(String[] args) throws InterruptedException {
            Thread t1 = new Thread(new MultiT("a"));
            Thread t2 = new Thread(new MultiT("b"));
            Thread t3 = new Thread(new MultiT("c"));
            
            t1.start();
            t1.join();
    
            t2.start();
            t2.join();
    
            t3.start();
            t3.join();
        }
    
    }
    
    class MultiT implements Runnable{
        private String s;
        private int i;
    
        public MultiT(String s){
            this.s = s;
        }
    
        @Override
        public void run() {
            while(i<10){
                System.out.println(s+"===="+i++);
            }
        }
    }
    

    最终,我们会看到,线程会按照t1,t2,t3顺序执行。因为,主线程main总会等调用join方法的那个线程执行完之后,才会往下执行。

    4) yield

    Thread.yield 方法会使当前线程放弃CPU时间片,把执行机会让给相同或更高优先级的线程(yield英文意思就是屈服,放弃的意思嘛,可以理解为当前线程暂时屈服于别人了)。

    注意,此时当前线程不会阻塞,只是进入了就绪状态,随时可以再次获得CPU时间片,从而进入运行状态。也就是说,其实yield方法,并不能保证,其它相同或更高优先级的线程一定会获得执行权,也有可能,再次被当前线程拿到执行权。

    yield方法和sleep方法一样,也是不释放锁资源的。可以通过代码来验证这一点:

    public class TestYield {
        public static void main(String[] args) {
            YieldThread yieldThread = new YieldThread();
            for (int i = 0; i < 10; i++) {
                Thread t = new Thread(yieldThread);
                t.start();
            }
        }
    }
    
    class YieldThread implements Runnable {
    
        private int count = 0;
    
        @Override
        public synchronized void run() {
            for (int i = 0; i < 10; i++) {
                count ++;
                if(count == 1){
                    Thread.yield();
                    System.out.println("线程:"+Thread.currentThread().getName() + "让步");
                }
                System.out.println("线程:"+Thread.currentThread().getName() + ",count:"+count);
            }
        }
    }
    

    结果:

    会看到,线程让步之后,并不会释放锁。因此,其它线程也没机会获得锁,只能把当前线程执行完之后,才会释放。(对于这一点,其实我是有疑问的。既然yield不释放锁,那为什么还要放弃执行权呢。就算放弃了执行权,别的线程也无法获得锁啊。)

    所以,我的理解,yield一般用于不存在锁竞争的多线程环境中。如果当前线程执行的任务时间可能比较长,就可以选择用yield方法,暂时让出CPU执行权。让其它线程也有机会执行任务,而不至于让CPU资源一直消耗在当前线程。

    5)suspend、resume

    suspend 会使线程挂起,并且不会自动恢复,只有调用 resume 方法才能使线程进入就绪状态。注意,这两个方法由于有可能导致死锁,已经被废弃。

  • 相关阅读:
    [Swift]LeetCode823. 带因子的二叉树 | Binary Trees With Factors
    [Swift]LeetCode822. 翻转卡片游戏 | Card Flipping Game
    [Swift]LeetCode821. 字符的最短距离 | Shortest Distance to a Character
    [Swift]LeetCode818. 赛车 | Race Car
    [Swift]LeetCode817. 链表组件 | Linked List Components
    [Swift]LeetCode816. 模糊坐标 | Ambiguous Coordinates
    [Swift]LeetCode815. 公交路线 | Bus Routes
    [Swift]LeetCode814. 二叉树剪枝 | Binary Tree Pruning
    [Objective-C语言教程]指针(15)
    转 : net use的使用
  • 原文地址:https://www.cnblogs.com/starry-skys/p/12386722.html
Copyright © 2020-2023  润新知