当前位置 博文首页 > 最爱吃鱼罐头:每日三道面试题,通往自由的道路8——多线程

    最爱吃鱼罐头:每日三道面试题,通往自由的道路8——多线程

    作者:[db:作者] 时间:2021-08-29 13:10

    茫茫人海千千万万,感谢这一秒你看到这里。希望我的面试题系列能对你的有所帮助!共勉!

    愿你在未来的日子,保持热爱,奔赴山海!

    每日三道面试题,成就更好自我

    今天我们继续聊聊多线程的话题吧!

    1. 昨天你讲到创建线程后使用start方法去调用线程,为什么run方法不行呢?有什么区别?

    这道题也是非常经典的一道题,虽然难度不大,但是突然忘了,也就答不上来了。

    我们先来看看代码吧。

    public class ThreadDemo {
    
        public static void main(String[] args) {
            MyThread myThread = new MyThread();
            MyThread myThead2 = new MyThread();
    //        myThread.start();
    //        myThead2.start();
            myThread.run();
            myThead2.run();
        }
    }
    
    class MyThread extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 6; i++) {
                System.out.println(Thread.currentThread().getName() + " :" + i);
                try {
                    sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    这里我们创建了MyThread继承了Thread类,这种方法是一种可以创建线程的方式。接着我们在main方法中创建了两个线程,都调用了start方法和run方法。让我们先看看结果吧!

    // 注释掉两个run方法 开启start方法得到的结果
    Thread-0 :0
    Thread-1 :0
    Thread-1 :1
    Thread-0 :1
    Thread-1 :2
    Thread-0 :2
    Thread-1 :3
    Thread-0 :3
    Thread-1 :4
    Thread-0 :4
    Thread-1 :5
    Thread-0 :5
    
    // 注释掉两个start方法 开启run方法得到的结果
    main :0
    main :1
    main :2
    main :3
    main :4
    main :5
    main :0
    main :1
    main :2
    main :3
    main :4
    main :5
    

    接下来我们讲一下:

    1. start方法的作用:

      启动线程,相当于开启一个线程调用我们重写的run方法里面的逻辑,此时相当于有两个线程,一个main的主线程和开启的子线程。可以看到我们的代码,相当于有三个线程,一个主线程、一个Thread-0线程和一个Thread-1线程。并且线程之间是没有顺序的,他们是抢占cpu的资源来回切换的。

    2. run方法的作用:

      执行线程的运行时代码,相当于我们只是单纯的调用一个普通方法。然后通过主线程的顺序调用的方式,从myThread调用run方法结束后到myThread2去调用run方法结束,并且我们也可以看到我们控制台中的线程名字就是main主线程。

    3. run方法我们可以重复调用,而start方法在一个线程中只能调用一次。即myThread这个实例对象只能调用一次start方法,如果再调用一次start方法的话,就会抛出IllegalThreadStateException 的异常。

    4. 我们调用start方法算是真正意义上的多线程,因为它是额外开启一个子线程去调用我们的run方法了。如果我们是调用run方法,就需要等待上一次的run方法执行完毕才能调用下一次。所以我们要调用start方法充分挥多核CPU的优势,采用多线程的方式去同时完成几件事情而不互相干扰。

    妙啊,妙花种子妙妙秒啊!

    2. 你知道你开启一个线程后,它的状态有那些吗?

    我们可以通过查看Thread的源码中State枚举发现有6个状态:

        public enum State {
            /**
             * Thread state for a thread which has not yet started.
             */
            NEW,
    
            /**
             * Thread state for a runnable thread.  A thread in the runnable
             * state is executing in the Java virtual machine but it may
             * be waiting for other resources from the operating system
             * such as processor.
             */
            RUNNABLE,
    
            /**
             * Thread state for a thread blocked waiting for a monitor lock.
             * A thread in the blocked state is waiting for a monitor lock
             * to enter a synchronized block/method or
             * reenter a synchronized block/method after calling
             * {@link Object#wait() Object.wait}.
             */
            BLOCKED,
    
            /**
             * Thread state for a waiting thread.
             * A thread is in the waiting state due to calling one of the
             * following methods:
             * <ul>
             *   <li>{@link Object#wait() Object.wait} with no timeout</li>
             *   <li>{@link #join() Thread.join} with no timeout</li>
             *   <li>{@link LockSupport#park() LockSupport.park}</li>
             * </ul>
             *
             * <p>A thread in the waiting state is waiting for another thread to
             * perform a particular action.
             *
             * For example, a thread that has called <tt>Object.wait()</tt>
             * on an object is waiting for another thread to call
             * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
             * that object. A thread that has called <tt>Thread.join()</tt>
             * is waiting for a specified thread to terminate.
             */
            WAITING,
    
            /**
             * Thread state for a waiting thread with a specified waiting time.
             * A thread is in the timed waiting state due to calling one of
             * the following methods with a specified positive waiting time:
             * <ul>
             *   <li>{@link #sleep Thread.sleep}</li>
             *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
             *   <li>{@link #join(long) Thread.join} with timeout</li>
             *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
             *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
             * </ul>
             */
            TIMED_WAITING,
    
            /**
             * Thread state for a terminated thread.
             * The thread has completed execution.
             */
            TERMINATED;
        }
    

    接下来我们具体来说说吧:

    • NEW(新建)

      线程刚被创建,还只是一个实例对象,并未调用start方法启动。。MyThread myThread = new MyThread只有线程对象,没有线程特征。

    • Runnable(可运行)

      在创建对象对象完成后,调用了myThread.start()方法线程,可以在Java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。也可以叫做处于就绪状态,需要等待被线程调度选中,获取cpu资源的使用权。

    • Teminated(被终止)

      因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。代表着此线程的生命周期结束了。

    处于运行状态中的线程由于某种原因,暂时放弃对 CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被 CPU 调用以进入到运行状态。有以下三种相关阻塞状态:

    • Blocked(锁阻塞)

      当一个线程试图获取一个对象锁如(Synchronzied或Lock),而该对象锁被其他的线程持有,则该线程进入Blocked状态;只有当该线程持有锁时,该线程将变成Runnable状态。

    • Waiting(无限等待)

      在调用了wait方法,JVM会把该线程放入等待队列中,等待另一个线程执行一个(唤醒),该线程此时状态表示进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。

    • TimedWaiting(计时等待)

      同waiting状态一样,调用sleep方法或者其他超时方法时,他们将进入Timed Waiting状态。不过这一状态只需保持到超时期满或者接收到唤醒通知。

    可以,那问你最后一道:

    3. 既然讲到超时方法,那你讲下sleep和wait的区别和他们需要怎样唤醒

    sleep和wait方法他们都是可以暂停当前线程的执行,进入一个阻塞状态。

    • sleep:

      我们可以指定睡眠时间,即让程序暂停指定时间运行,时间到了会继续执行代码,如果时间未到我们想要换醒需要调用interrupt 方法来随时唤醒即可。而调用interrupt 会使得sleep()方法抛出InterruptedException 异常,当sleep()方法抛出异常我们就中断了sleep的方法,从而让程序继续运行下去。

    • wait:

      调用该方法,可以导致线程进入等待阻塞状态,会一直等待直到它被其他线程通过notify或者notifyAll方法唤醒。或者也可以使用wait(long timeout)表示时间到了自动执行,类似于sleep(long millis)。

      notify():该方法会随机选择一个在该对象上调用wait方法的线程,解除其阻塞状态。

      notifyAll():该方法会唤醒所有的wait对象。

    两者的区别:

    • 两者所属的类不同:sleep是 Thread线程类的静态方法;而wait是 Object类的方法。

    • 两者是否是否锁呢:sleep不释放锁;wait释放锁。

    • 两者所使用的场景:sleep可以在任何需要的场景下调用;而wait必须使用在同步代码块或者同步方法中。

    • 两者不同唤醒机制:sleep方法执行睡眠时间完成后,线程会自动苏醒;而wait方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify或者 notifyAll方法,或者可以使用wait(long timeout)超时后线程会自动苏醒。

    小伙子不错嘛!今天就到这里,期待你明天的到来,希望能让我继续保持惊喜!

    注: 如果文章有任何错误和建议,请各位大佬尽情留言!如果这篇文章对你也有所帮助,希望可爱亲切的您给个三连关注下,非常感谢啦!

    cs