线程join之底层分析

一、线程的五种状态及相互转换条件

从操作系统的角度,线程分为就绪、运行和阻塞三种状态。

                                                         线程的生命周期

  创建:一个新的线程被创建,等待该线程被调用执行;

  就绪:时间片已用完,此线程被强制暂停,等待下一个属于它的时间片到来;

  运行:此线程正在执行,正在占用时间片;

  阻塞:也叫等待状态,等待某一事件(如IO或另一个线程)执行完;

  退出:一个线程完成任务或者其他终止条件发生,该线程终止进入退出状态,退出状态释放该线程所分配的资源。

java中定义了线程的六种状态:

public enum State {
        NEW,
        RUNNABLE,
        BLOCKED,
        WAITING,//无限等待-直到线程终结或线程被唤醒
        TIMED_WAITING,//超时等待 到指定时间后线程自动被唤醒
        TERMINATED;//线程终止-正常结束和异常被终止
    }

新建(New)

线程创建后尚未启动。

可运行(Runnable)

一旦调用了线程的start()方法,线程就处于可运行状态。可运行状态的线程可能正在运行,也可能还没有运行而正在等待 CPU 时间片。(Java规范中并没有分为可运行状态和正在运行状态这两个状态,而是把它们合为一个状态。所以,可以把一个正在运行中的线程仍然称其处于可运行状态。)

阻塞(Blocked)

处于阻塞状态的线程并不会占用CPU资源。

以下情况会让线程进入阻塞状态:

①等待获取锁

       等待获取锁分为两种情形:1、在对象锁等待池 2、在对象锁池 在对象锁池的同志可以竞争对象锁 在对象锁等待池的同志就需要先被调度到等待锁池了。。。。。。

等待获取一个锁,而该锁被其它线程持有,则该线程进入阻塞状态。当其它线程释放了该锁,并且线程调度器允许该线程持有该锁时,该线程退出阻塞状态。

②IO阻塞

线程发起了一个阻塞式IO后也会进入阻塞状态。最典型的场景,如等待用户输入内容然后继续执行。

无限期等待(Waiting)-需要唤醒

处于这种状态的线程不会被分配 CPU 时间片,需要等待其它线程显式地唤醒。

以下方法会让线程进入无限期的等待状态

  • Object.wait() 方法                    结束:Object.notify() / Object.notifyAll()
  • Thread.join() 方法                    结束:被调用的线程执行完毕
  • LockSupport.park() 方法          结束:LockSupport.unpark(currentThread)

限时等待(Timed Waiting)

处于这种状态的线程也不会被分配CPU 时间片,在一定时间之后会被系统自动唤醒。

以下方法会让线程进入限期等待状态:

  • Thread.sleep(time) 方法          结束:sleep时间结束
  • Object.wait(time) 方法             结束:wait时间结束,或者Object.notify() / notifyAll()    
  • LockSupport.parkNanos(time)/parkUntil(time) 方法     结束:park时间结束,或者LockSupport.unpark(当前线程)

死亡(Terminated)

可以是线程结束任务之后自己结束,或者产生了异常而结束。

注意:在有些书籍(如《Java并发编程实战》中5.4章节)或笼统的称呼中,就将阻塞、等待和超时等待这三种状态统称为阻塞状态。

2.几种状态的转换

yield让出cpu的使用权 进入就绪状态。

线程常见的方法

1.sleep

使当前线程休眠(暂停运行)指定的时间。

用法:Thread.sleep(time)

Thread.sleep(0)

Thread.sleep(0)的作用是触发操作系统立刻重新进行一次CPU竞争。

参考文章:代码中的Thread.sleep(0) 有什么意义?是写错了吗?

2.join

等待相应线程运行结束。若线程A调用线程B的join方法,那么线程A将会暂停运行,直到线程B运行结束。(谁join进来就等谁先运行完)

join实际上是通过wait来实现的。

3.yield

使当前线程主动放弃对处理器的占用,这可能导致当前线程被暂停。

这个方法是不可靠的,该方法被调用时当前线程可能仍然继续运行(视系统当前的运行状况而定)。

4.wait和notify()/notifyAll()

需要注意的是

①wait和notify/notifyAll方法只能在同步代码块里调用

wait()、notify()、notifyAll() 这三个方法能够被调用的前提是已经获取了相应的互斥锁,所以我们会发现 wait()、notify()、notifyAll() 都是在synchronized{}内部被调用的

②notify操作必须要在wait操作之后,否则,可能导致线程不会醒来。

③wait

5.LockSupport.park()/parkNano(time)/parkUntil(time) 

LockSupport比Object的wait/notify有两大优势:

①LockSupport不需要在同步代码块里 。所以线程间也不需要维护一个共享的同步对象了,实现了线程间的解耦。

②unpark方法可以先于park方法调用,所以不需要担心线程间的执行的先后顺序。

二、join的作用及实例

Join作用是将多线程的并行执行转换为串行执行。控制线程的执行顺序。

必须先调用线程的start方法  再调用join才有用的哦。

public static void main(String[] args) throws InterruptedException
    {
        System.out.println("main start");

        Thread t1 = new Thread(new Worker("thread-1"));
        t1.start();
        t1.join();
        System.out.println("main end");
    }

在上面的例子中,main线程要等到t1线程运行结束后,才会输出“main end”。如果不加t1.join(),main线程和t1线程是并行的。而加上t1.join(),程序就变成是顺序执行了。

三、join底层分析

底层代码:

/**
     * Waits for this thread to die.
     *
     * <p> An invocation of this method behaves in exactly the same
     * way as the invocation
     *
     * <blockquote>
     * {@linkplain #join(long) join}{@code (0)}
     * </blockquote>
     *
     * @throws  InterruptedException
     *          if any thread has interrupted the current thread. The
     *          <i>interrupted status</i> of the current thread is
     *          cleared when this exception is thrown.
     */
    public final void join() throws InterruptedException {
        join(0);            //join()等同于join(0)
    }
    /**
     * Waits at most {@code millis} milliseconds for this thread to
     * die. A timeout of {@code 0} means to wait forever.
     *
     * <p> This implementation uses a loop of {@code this.wait} calls
     * conditioned on {@code this.isAlive}. As a thread terminates the
     * {@code this.notifyAll} method is invoked. It is recommended that
     * applications not use {@code wait}, {@code notify}, or
     * {@code notifyAll} on {@code Thread} instances.
     *
     * @param  millis
     *         the time to wait in milliseconds
     *
     * @throws  IllegalArgumentException
     *          if the value of {@code millis} is negative
     *
     * @throws  InterruptedException
     *          if any thread has interrupted the current thread. The
     *          <i>interrupted status</i> of the current thread is
     *          cleared when this exception is thrown.
     */
    public final synchronized void join(long millis) throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;
 
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
 
        if (millis == 0) {
            while (isAlive()) {
                wait(0);           //join(0)等同于wait(0),即wait无限时间直到被notify
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }

}

可见底层是通过wait进行实现的,wait后进行waiting状态 

也就是说:当前线程调用了另一个线程的join方法后 当前线程会被wait等待 等待另一个线程执行完成后才继续执行当前线程。但是从源码中并不能看到线程的notify方法,这是因为线程在die的时候会自动调用自身的notifyAll方法,来释放所有的资源和锁。

补充几道面试题:

1.java中线程的几种状态?状态间的转换?

看上图

2.挂起和等待有什么区别?

线程挂起-suspend 已经被废弃了。挂起不会释放锁资源 导致其他线程执行时会获取不到锁而无法继续执行。而且挂起的线程从线程状态上看仍然是Runnable状态,很难排查。

等待 -waiting 会释放锁资源 一般是因为某条件未达到 使线程等待 条件满足时 被唤醒。必须在同步代码块或同步方法中调用。

3.wait和sleep的区别?

                    wait sleep
同步 只能在同步上下文中调用wait方法,否则或抛出IllegalMonitorStateException异常 不需要在同步方法或同步块中调用
作用对象 wait方法定义在Object类中,作用于对象本身 sleep方法定义在java.lang.Thread中,作用于当前线程
释放锁资源
唤醒条件 其他线程调用对象的notify()或者notifyAll()方法 超时或者调用interrupt()方法体
方法属性 wait是实例方法 sleep是静态方法

4.sleep(0)有什么含义?

Thread.sleep(0)的作用是触发操作系统立刻重新进行一次CPU竞争。

5.wait、notify/notifyAll为什么要在synchronized块中调用?

wait()、notify()、notifyAll() 这三个方法能够被调用的前提是已经获取了相应的互斥锁。释放锁资源。所以需要在synchronized块中执行。

6.wait为什么要在while循环而非if中调用?

wait是由于某条件达不到才用的 但是执行唤醒后 可能其他条件又达不到了 while循环判断条件符合后才唤醒 条件达不到的话继续wait。

7.interrupt()和static interrupted()两者的区别?

public void interrupt():中断线程

public boolean interrupted():判断是否被中断

public static boolean interrupted():判断是否被中断,并清除当前中断状态。

8.stop,suspend/resume过时的原因?

stop太暴力 可能导致数据不一致;suspend和resume是一对操作 挂起不会释放锁资源 导致其他线程无法执行 而且如果不小心resume提前执行了 那么线程将永远不可能执行了。

发布了23 篇原创文章 · 获赞 0 · 访问量 2931

猜你喜欢

转载自blog.csdn.net/kris_lh123/article/details/103668929
今日推荐