Java线程详解:wait、notify、notifyAll、join

Java线程详解:wait、notify、notifyAll、join,第1张

线程的概念

线程是进程的子任务,一个进程可以创建多个线程,线程不拥有系统资源,但是线程可以共享进程的资源,而线程自己也有一块独立的小块空间:包括堆栈,程序计数器和局部变量。
线程是CPU调度和分派的基本单位。在同一时刻cpu只能执行一段代码,或者说叫一段顺序执行流,也就是线程。cpu在不同的线程之间来回切换,因为cpu的运行速度非常高,看起来就像这些线程一起执行一样,这就是并发。

线程状态

从不同的角度,会有不同的结果。
说到线程的状态,网上的说法众说纷纭,有说5种的,有说6种的,还有说7种的。具体怎么回事呢,得看是从什么角度来看。、
从 *** 作系统来看: 线程可以分为5个状态

  1. 新建状态(NEW)
    线程的初始状态,还没有调用线程的start()方法
  2. 就绪状态(READY)
    线程启动之后,就进入就绪状态,等待cpu分配时间片执行。处于就绪状态的线程,只是说线程做好了cpu随时调用执行的准备
  3. 执行状态(RUNNING)
    cpu开始调度执行处于就绪状态的线程时,线程就是执行状态了。要想线程进入执行状态,它必须先是就绪状态
  4. 阻塞状态(BLOCKED)
    当处于运行状态的线程,由于某些原因暂时放弃cpu的使用权,则处于阻塞状态。直到其进入就绪状态,cpu才可以重新调度执行它。
  5. 终止状态(TERMINATED)
    线程执行完成或异常退出,线程终止。

而Java中,线程则分了6种状态。
新建和终止状态跟上面一样,就绪和执行状态,Java线程将这两种合并为一种:RUNNABLE:运行状态,表示线程已经在虚拟机中执行了。而阻塞状态,Java将之细分了3个状态: BLOCKED,WAITING,TIMEED_WAITING。
在Thread.State枚举中列举了这6种状态:

  1. 新建状态(NEW)
    Thread 创建之后的初始状态,还未调用start()方法,只是内存中的一个对象, *** 作系统并没有创建线程。
  2. 运行状态(RUNNABLE)
    线程已经在java虚拟机中执行,可能cpu调度已经正式开始运行,也有可能等待cpu调度。
  3. 阻塞状态(BLOCKED)
    在Java中,BLOCKED 状态跟内置锁synchronized(也叫监视器锁)关联,线程在等待获取其它线程持有的监视器锁进入同步代码块时,线程处于BLOCK状态。或者在线程调用wait方法之后等待重新进入同步代码块,也处于BLOCK状态。BLOCK只跟监视器锁有关,如果不需要进入同步代码块,而是因为其它的原因线程需要等待,不属于BLOCK.
  4. 等待状态(WAITING )
    一个线程由于调用了以下方法之一: Object.wait(),Thread.join(),LockSupport.park()而处于等待状态。
    处于WAITING状态的线程在等待其它线程执行特定的行为。例如:
    一个线程A调用了某个对象上的wait()方法,需要等待其它线程去执行对象上的notify()或者notifyAll()方法,线程A才会结束等待。
  5. 限时等待(TIMED_WAITING)
    一个线程由于调用了以下方法之一: Thread.sleep(long),Object.wait(long),Thread.join(long),LockSupport.parkNanos(long),LockSupport.parkUtil(long) 而等待。从这个命名上也说明是限时等待,不会无限等待下去,特定时间之后即使它等待的事件没有发生,也会退出等待状态继续执行。
    终止状态(TERMINATED)
    线程已经终止,包括正常执行完成或者异常退出。

线程通信: wait, notify, notifyAll, join

上面在RUNNABLE到WAITING,TIMED_WAITING状态的转换过程中,提到了wait,notify等方法。

wait

该方法是Object的一个实例方法,

/   ** @throws  IllegalMonitorStateException  if the current thread is not
     *               the owner of the object's monitor.
     * @throws  InterruptedException if any thread interrupted the
     *             current thread before or while the current thread
     *             was waiting for a notification.  The interrupted
     *             status of the current thread is cleared when
     *             this exception is thrown.
     * @see        java.lang.Object#notify()
     * @see        java.lang.Object#notifyAll()
     * /
 public final void wait() throws InterruptedException {
        wait(0);
    }

这也表明任何对象都可以调用wait方法:此时会释放对象的监视器锁,(监视器锁是内置锁的另一种称呼,两者是一样的),并且当前线程(标记为线程T)会保持休眠
直到发生一下四种情况之一:

  1. 其它线程调用该对象上的notify方法,并且当前线程正好被选中唤醒
  2. 其它线程调用该对象上notifyAll方法
  3. 其它线程中断了当前线程,当前线程的中断标志也会被清除
  4. 调用wait时传入了一个正数的等待时间,等待的时间过去

以上四种情况发生之后,线程T结束休眠,重新进入线程调度:它会按正常的方式与其他线程竞争锁。也就是说,休眠结束后,线程的同步状态跟调用wait前完全一样。
第3种情况中,wait方法会抛出InterruptedException异常,并且将线程的中断标志清除
第4种情况中,等待的时间过后如果线程在竞争中没有获得内置锁,后续代码也是无法执行的,超时时间达到仅仅是把线程从休眠状态中移出

调用wait方法的线程必须拥有对象的监视器锁,怎么拥有呢,有3种方式:
5. 调用对象的synchronized同步方法
6. 执行锁定在这个对象上的同步代码块
7. 对于Class对象,执行该类上的静态同步方法(static synchronized methodName)

这也跟Java内置锁 synchronized里的“获得内置锁的唯一途径就是进入由这个锁保护的同步代码块或方法”说法一致。
如果线程没有获得这个对象的监视器锁呢,wait方法会抛出 IllegalMonitorStateException异常。

虚假唤醒

我们调用wait时,经常是需要判断某些条件的,例如当某某资源找不到时,调用wait方法。通常会这么写

synchronized(obj){
dosomething
if( condition not satisfy){
	obj.wait()
}

实际上并不推荐这么写,而是这样:

synchronized(obj){
dosomething
while( condition not satisfy){
	obj.wait()
}

就是因为虚假唤醒。一个线程也有可能在没有别的线程调用notify,nofifyAll,也没有中断,等待的时间也没有到的时候被唤醒,这叫做”虚假唤醒“,虽然在实践中很少出现,但是应用也要预防这种情况的发生。

notify 和 notifyAll

这两个方法也是Object的实例方法:

	/*
     * @throws  IllegalMonitorStateException  if the current thread is not
     *               the owner of this object's monitor.
     * @see        java.lang.Object#notifyAll()
     * @see        java.lang.Object#wait()
     */
    public final native void notify();
    
    /* @throws  IllegalMonitorStateException  if the current thread is not
     *               the owner of this object's monitor.
     * @see        java.lang.Object#notify()
     * @see        java.lang.Object#wait()
     */
    public final native void notifyAll();

notify()方法唤醒一个正在该对象监视器锁的线程(调用了wait方法的线程),如果有多个线程在等待对象监视器锁,其中之一会被唤醒,具体唤醒哪个由java虚拟机决定。notifyAll()则会唤醒所有在该对象监视器上等待的线程,并且顺序是先进后出,最后一个进入休眠的线程会被首先唤醒

值得注意的是

调用notify(),notifyAll()时不会释放对象锁,而是notify所在的同步代码块结束之后才会释放锁,所以在调用notify,notifyAll方法之后,最好直接结束同步代码块。否则notify调用之后,锁不释放,被唤醒的线程也是在synchronized中的,获取不到对象锁也无法执行

notify,notifyAll 代码示例:
public static void main(String[] args) {
        Object service = new Object();
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (service) {
                    System.out.println("thread1 sleep 2s");
                    ThreadUtil.sleep(2000);
                    System.out.println("thread1 call wait");
                    try {
                        service.wait();
                        System.out.println("thread1 wait finish");
                    } catch (Exception e) {
                        System.out.println("thread1 wait throw a exception");
                        e.printStackTrace();
                    }
                }
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (service) {
                    System.out.println("thread2 sleep 2s");
                    ThreadUtil.sleep(2000);
                    System.out.println("thread2 call wait");
                    try {
                        service.wait();
                        System.out.println("thread2 wait finish");
                    } catch (Exception e) {
                        System.out.println("thread2 wait throw a exception");
                        e.printStackTrace();
                    }
                }
            }
        });

        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (service) {
                    System.out.println("thread3 is running");
                    service.notify();
                }

            }
        });

        thread1.start();
        thread2.start();
        ThreadUtil.sleep(2100); // 确保有一个线程调用了wait方法,如果没有线程调用wait方法而陷入等待,notify方法没有意义
        thread3.start();
    }
    执行结果:
thread1 sleep 2s
thread1 call wait
thread2 sleep 2s
thread2 call wait
thread3 is running
thread1 wait finish

thread1先执行,在调用wait之后释放对象锁进入等待,thread2获得对象锁,然后执行,也调用wait方法进入等待,thread3获得锁,调用notify方法唤醒某一个线程。thread1和thread2中一个会被唤醒,上面是thread1被唤醒:输出thread1 wait finish。也有可能是thread2被唤醒。notify只能唤醒一个线程,所以thread2的wait将会一直等待下去。
如果将thread3例的notify改为notifyAll。

thread2 sleep 2s
thread2 call wait
thread1 sleep 2s
thread1 call wait
thread3 is running
thread1 wait finish
thread2 wait finish

thread1和thread2都被唤醒了。

带时间参数的wait示例
public static void main(String[] args) {
        Object service = new Object();
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (service) {
                    System.out.println("thread1 call wait");
                    try {
                        service.wait();
                        System.out.println("thread1 wait finish");
                        System.out.println("thread1 sleep 10s");
                        ThreadUtil.sleep(10000);
                    } catch (Exception e) {
                        System.out.println("thread1 wait throw a exception");
                        e.printStackTrace();
                    }
                }
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (service) {
                    System.out.println("thread2 call wait");
                    try {
                        service.wait(3000);
                        System.out.println("thread2 wait finish");
                    } catch (Exception e) {
                        System.out.println("thread2 wait throw a exception");
                        e.printStackTrace();
                    }
                }

            }
        });

        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (service) {
                    System.out.println("thread3 is running");
                    service.notify();
                }

            }
        });

        thread1.start();
        thread2.start();
        ThreadUtil.sleep(100);
        thread3.start();
    }
执行结果:
thread1 call wait
thread2 call wait
thread3 is running
thread1 wait finish
thread1 sleep 10s
// 这里等了10秒,而不是3秒
thread2 wait finish

thread2调用了wait(3000),超时时间设置为3秒。在thread3调用notify之后,唤醒的是thread1进程,然后thread1调用sleep(10s),(sleep函数不会影响内置锁)内置锁依然在thread1手上。在3秒中的时候,thread2从休眠状态醒来,但是它没有获取到内置锁,也只能等待。直到10秒后,thread1执行完毕,释放内置锁,thread2取得锁才接着执行。

wait中被中断示例
public static void main(String[] args) {
        Object service = new Object();
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (service) {
                    System.out.println("Thread.currentThread().isInterrupted(): "+Thread.currentThread().isInterrupted());
                    System.out.println("thread1 call wait");
                    try {
                        service.wait();
                        System.out.println("thread1 wait finish");
                        System.out.println("thread1 sleep 10s");
                        ThreadUtil.sleep(10000);
                    } catch (Exception e) {
                        System.out.println("thread1 wait throw a exception");
                        System.out.println("exception Thread.currentThread().isInterrupted(): "+Thread.currentThread().isInterrupted());
                        e.printStackTrace();
                    }
                }
            }
        });


        Thread thread4 = new Thread(new Runnable() {
            @Override
            public void run() {
                thread1.interrupt();
            }
        });

        thread1.start();
        ThreadUtil.sleep(100);
        thread4.start();
    }
结果:
Thread.currentThread().isInterrupted(): false
thread1 call wait
thread1 wait throw a exception
exception Thread.currentThread().isInterrupted(): false
java.lang.InterruptedException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at com.hz.MainTest.run(MainTest.java:85)
	at java.lang.Thread.run(Thread.java:748)

可以看到wait方法抛出了InterruptedException异常,并且线程的中断标志为false了。

join 方法 示例

join是Thread类的一个实例方法。
当前线程A调用另一个线程b的join()方法,会让当前线程A阻塞,直到线程b结束(包括正常结束或者异常退出)。
如果调用b.join(long)带时间参数的,则线程A会阻塞,但是如果时间达到b还没有结束,A也会结束阻塞。

public static void main(String[] args) throws Exception {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                    try {
                        System.out.println("thread1 sleep 10s");
                        ThreadUtil.sleep(10000);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                
            }
        });
        thread1.start();
        thread1.join();
        System.out.println("main thread finish");
        执行结果:
        thread1 sleep 10s
        // 等待10秒
        main thread finish

主线程main会阻塞10秒,直到thread1结束,才输出 main thread finish
如果main中调用thread1.join(3000), 那主线程只会阻塞3秒。
为什么join 能达到这个效果呢。

我们以上文thread1.join()来分析一下源码

join方法源码
// 这是一个同步方法,先获得thread1的内置锁,thread1也是一个对象,也有内置锁
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()) { // 循环判断线程thread1是否还存活,存活则持续调用wait阻塞
                wait(0); // 这里的wait,是thread1.wait,而且是主线程调用的thread1.wait,阻塞的是主线程,thread1线程是正常执行的
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

从上面的源码中,可以发现

  • join(long millis)方法的签名中,加了synchronized关键字来保证线程安全,join(long millis)最终调用的是Object对象的wait()方法,让主线程等待。这里需要注意的是,synchronized关键字实现的隐式锁,锁的是this,即thread这个对象,上例中就是thread1对象(因为synchronized修饰的join(long millis)方法是一个成员方法,因此锁的是实例对象),我们是在main线程中,调用了thread这个对象的join()方法,所以调用wait()方法时,调用的是thread这个对象的wait()方法,所以是main线程进入到等待状态中。(调用的是thread这个对象的wait()方法,这一点很重要,因为后面唤醒main线程时,需要用thread这个对象的notify()或者notifyAll()方法)
  • 既然是wait,那么必然有notify或者notifyAll。但是源码里并没有调用这两个方法,在哪里调用的呢,在jvm虚拟机中!
  • Java里面的Thread类在JVM上对应的文件是thread.cpp。thread.cpp文件的路径是jdk-jdk8-b120/hotspot/src/share/vm/runtime/thread.cpp
    在thread.cpp文件中定义了很多和线程相关的方法,Thread.java类中的native方法就是在thread.cpp中实现的。
    join方法是当目标线程执行完成之后,主线程继续执行。我们猜测,notifyAll方法是在thread的run方法执行完之后调用的,做一些收尾工作。
    在JVM中,当每个线程执行完成时,会调用到thread.cpp文件中JavaThread::exit(bool destroy_vm, ExitType exit_type)方法
void JavaThread::exit(bool destroy_vm, ExitType exit_type) {
	assert(this == JavaThread::current(),  "thread consistency check");
	// ...省略了很多代码
	// Notify waiters on thread object. This has to be done after exit() is called
	// on the thread (if the thread is the last thread in a daemon ThreadGroup the
	// group should have the destroyed bit set before waiters are notified).
    
    // 关键代码就在这一行,从方法名就可以推断出,它是线程在退出时,用来确保join()方法的相关逻辑的。而这里的this就是指的当前线程。
    // 从上面的英文注释也能看出,它是用来处理join()相关的逻辑的
	ensure_join(this);
  • 可以看到,主要逻辑在ensure_join()方法中,接着找到ensure_join()方法的源码,源码如下

  • `static void ensure_join(JavaThread* thread) {
    // We do not need to grap the Threads_lock, since we are operating on ourself.
    Handle threadObj(thread, thread->threadObj());
    assert(threadObj.not_null(), “java thread object must exist”);
    ObjectLocker lock(threadObj, thread);
    // Ignore pending exception (ThreadDeath), since we are exiting anyway
    thread->clear_pending_exception();
    // Thread is exiting. So set thread_status field in java.lang.Thread class to TERMINATED.
    java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED);
    // Clear the native thread instance - this makes isAlive return false and allows the join()
    // to complete once we’ve done the notify_all below
    java_lang_Thread::set_thread(threadObj(), NULL);

    // 核心代码在下面这一行
    // notify_all()方法肯定就是用来唤醒。这里的thread对象就是我们demo中的线程thread1这个实例对象
    lock.notify_all(thread);

既然这样,如果我提前将调用thread1的notifyAll()方法呢,那不就可以让主线程提前退出阻塞了么。于是我尝试了下面的代码

public static void main(String[] args) throws Exception {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                    try {
                        System.out.println("thread1 sleep 10s");
                        ThreadUtil.sleep(10000);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                ThreadUtil.sleep(1000);
                synchronized (thread1) {
                    try {
                        System.out.println("call notify");
                        thread1.notifyAll();
                        System.out.println("after notify");
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        thread1.start();
        thread2.start();
        thread1.join();

        System.out.println("main thread finish");
    }
    结果:
Connected to the target VM, address: '127.0.0.1:58744', transport: 'socket'
thread1 sleep 10s
call notify
after notify
Disconnected from the target VM, address: '127.0.0.1:58744', transport: 'socket'
// 这句话依然是10秒钟后输出的
main thread finish

我在thread2中先停1秒钟,然后调用thread1.notifyAll(),是希望在1秒钟后,主线程能结束阻塞,继续执行。但是不起作用! why?
再来看源码里,有这一句

 if (millis == 0) {
            while (isAlive()) { 
                wait(0); 
            }
        } 

用的是while(isAlive()),而不是 if(isAlive()),thread1并没有结束,所以会再次调用wait阻塞。而且由于“虚假唤醒”的存在,调用wait时一般都是用while循环判断是否需要调用wait的,如下。

synchronized(obj){
	while (! condition){
		obj.wait()
	}
}

那我能不能把它改成if来验证呢,我本来想创建一个新的Thread子类,覆盖join方法,但是join方法是一个final方法,无法覆写,jdk也是考虑到这种情况吧

public final synchronized void join(long millis)

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/720088.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-25
下一篇 2022-04-25

发表评论

登录后才能评论

评论列表(0条)

保存