Java多线程-wait-notify-notifyAll-sleep-join-yield-中断

190 阅读5分钟

1.wait()函数

当一个线程调用共享变量的wait()方法时,该调用线程会被阻塞挂起,并且释放共享变量的监视器锁。

当发生如下几种情况,才会返回:

  • 其他的线程调用该共享对象的notify()/notifyAll()方法,被阻塞的线程会被重新唤醒,进入可运行状态。

  • 其他线程调用了被阻塞线程上的interrupt()方法,则被阻塞的线程会抛出InterruptException异常放回。

如果一个线程在没有获取到共享对象上的监视器锁,就立即调用共享对象上的wait()方法,线程会抛出IllegalMonitorStateException异常。


synchronized(obj) {

  // do something you like here.

}

另外,在实际中,一个线程有可能会在没有任何线程调用notify()/notifyAll()方法的情况下从挂起状态转换到可运行状态,即是虚假唤醒。为了避免这样的情况发生,推荐使用下面的方法调用wait():


synchronized(obj) {

    while (线程唤醒条件不满足) {

        obj.wait();

    }

}

上述的用法,可以在一个简单的消息队列中实践:


// 生产者线程

synchronized(queue) {

    // get the monitor lock

    while (queue.size() == MAX_QUEUE_SIZE) {

        // 调用消息队列的wait方法,使得生产者线程阻塞,

        // 让消费者线程可以获得消息队列的监视器锁

        // 进而让消息队列中的消息得到消费

        try {

            queue.wait();

        } catch(Exception e) {

            e.printStackTrace();

        }

    }

    // 消息队列仍然还有空间可以存放新生产的消息

    queue.add(new Message());

    // 唤醒消费者线程,通知其消费消息

    queue.notifyAll();

}



// 消费者线程

synchronized(queue) {

    while (queue.size() == 0) {

        // 消息队列中没有消息了,需要阻塞自己,释放消息队列的监视器锁

        // 使得生产者线程可以往消息队列中添加消息

        try {

            queue.wait();

        } catch (Exception e) {

            e.printStackTrace();

        }

    }

    // 消息队列中存在消息可以消费

    queue.take();

    // 通知生产者线程,产生消息

    queue.notifyAll();

}

当一个线程持有多个共享对象的监视器锁,只会释放调用了wait()方法的对象的监视器锁。其他的共享对象的监视器锁是不会释放的,此时如果其他的线程再来获取其他共享对象的监视器锁的时候会被阻塞,挂起。

2.notify()函数

一个线程调用了共享对象的notify方法后,会唤醒一个在该共享变量上调用了wait方法的线程,使用该方法唤醒线程会在因为调用共享变量而阻塞的一系列线程中随机选择一个线程唤醒。

被notify唤醒的线程并不会立即获得共享变量的监视器锁,而是会和其他竞争该监视器锁的线程一起来竞争这个锁。

3.notifyAll()

顾名思义,该方法是唤醒所有在某个共享变量上调用了wait方法的线程,并使他们共同来竞争该共享变量的监视器锁。

4.join()

在实际的开发过程中,我们可能会遇到这样一个情况:需要等待几件事情都完成之后,才能继续往下面执行,比如多线程去读取加载某个资源,需要等待所有的线程都正常完成之后,才能够继续后续的操作。

这种情况下,我们可以使用Thread为我们提供的静态join()方法来达到这个效果。


public static void main(String[] args) {

    Thread thread1 = new Thread(new Runnable() {

        @Override

        public void run() {

            //do something

        }

    });

    Thread thread2 = new Thread(new Runnable() {

        @Override

        public void run() {

            //do something

        }

    });

    thread1.start();

    thread2.start();



    thread1.join();

    thread2.join();

}

这样的话,主线程会等待thead1和thead2都执行完成之后,才会继续执行后续的程序逻辑。

5.sleep()

Thread为我们也提供了sleep()这个静态方法,该方法会使得调用该方法的线程放弃参与CPU时间片竞争,但是线程不会释放他已经获得的资源和锁,在达到睡眠的时间后,会自动苏醒,参与到和CPU的竞争中。

6.yield()

这个方法会让调用方法的线程暂时放弃已经获得的CPU的时间片,不管已经获得的时间片是否使用完,都会放弃,然后参与到下一次的CPU时间片轮转。当然,在下一次调度中,也有可能调度到刚刚让出CPU的线程。

7.线程中断

线程的中断是线程之间的协作的机制,主要有以下相关的操作:

  • void interrupt():中断线程,例如线程A在运行过程中,**线程B可以调用A的interrupt方法来设置A的中断标志为true并立即返回。**但是这个方法仅仅是设置一个标志,线程A并没有实际被中断,他实际上会继续往下执行。如果线程A因为调用了wait/join/sleep方法而被阻塞挂起,这时候B如果调用A的interrupt,线程A会在调用这些方法的地方抛出InterruptedException异常而返回。

// JDK中断源码

public void interrupt() {

    if (this != Thread.currentThread()) {

        checkAccess();




        // thread may be blocked in an I/O operation

        synchronized (blockerLock) {

            Interruptible b = blocker;

            if (b != null) {

                interrupt0();  // set interrupt status

                b.interrupt(this);

                return;

            }

        }

    }




    // set interrupt status

    interrupt0();

}

  • boolean isInterrupted():检查当前线程是否中断,如果是返回true,否则为false

  • boolean interrputed():返回逻辑和isInterrupted()一致,但与其不同的是,该方法如果发现当前线程已经被中断,则会清除中断标志,并且该方法是静态的,可以通过Thread直接调用。

线程中断优雅退出的范例:


public void run() {

    try {

        ...

        while (!Thread.currentThread().isInterrupted() && (more work need to do)) {

            // do some more work here.

        }

    } catch (InterruptedException e) {

        // handle the exception

        // thread was interrupted during sleep or wait

    } finally {

        // clean up,清除资源等操作

    }

}