• join和wait


    最近看多线程的时候发现对于join的理解有些错误,在网上查了不少资料,根据自己的理解整理了一下,这里之所以把join和wait放在一起,是因为join的底层实现就是基于wait的,一并讲解更容易理解。

    wait

    了解join就先需要了解wait,wait是线程间通信常用的信号量,作用就是让线程暂时停止运行,等待其他线程使用notify来唤醒或者达到一定条件自己苏醒。
    wait是一个本地方法,属于Object类,其底层实现是JVM内部实现,是基于monitor对象监视锁。

    //本地方法
    public final native void wait(long timeout) throws InterruptedException;
    //参数有纳秒和毫秒
    public final void wait(long timeout, int nanos) throws InterruptedException {
        if (timeout < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
    
        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }
        //存在纳秒,默认加一毫秒
        if (nanos > 0) {
            timeout++;
        }
    
        wait(timeout);
    }
    //无参数,默认为0
    public final void wait() throws InterruptedException {
        wait(0);
    }
    

    根据源码可以发现,虽然wait有三个重载的方法,但是主要的还是wait(long timeout)这个本地方法,其他两个都是基于这个来封装的,由JVM底层源码不太好看到,我就以流程的形式来描述。

    synchronized (this) {
            System.out.println("A begin " + System.currentTimeMillis());
            try {
                wait(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("A end " + System.currentTimeMillis());
    }
    
    • 上述代码在执行到wait(5000)时首先会释放当前占用的锁,并暂停线程。
    • 在暂停的5秒内如果收到其他线程的notify()方法发来的信号,那么就再次尝试获取已经释放的锁
    • 如果获取到那么就继续执行,没有就等待锁释放来竞争。
    • 如果在5秒内未收到信号,那么到时间后就自动苏醒去尝试获取锁。

    而对于时间的参数timeout需要注意的是,如果输入0不代表不暂停,而是需要特殊情况自己苏醒或者notify唤醒,这里有个特殊点,wait(0)是可以自己苏醒的

    public class Thread2 extends Thread{
    	private Thread1 a;
    	public Thread2(Thread1 a) {
        	this.a = a;
    	}
    
    	@Override
    	public void run() {
        	synchronized (a) {
            	System.out.println("B begin " + System.currentTimeMillis());
            	try {
                a.wait();
            	} catch (InterruptedException e) {
                	e.printStackTrace();
            	}
            	System.out.println("B end " + System.currentTimeMillis());
        	}
    	}
    }
    
    public class Thread1 extends Thread{
    	@Override
    	public void run() {
        	synchronized (this) {
            	System.out.println("A begin " + System.currentTimeMillis());
            	System.out.println("A end " + System.currentTimeMillis());
        	}
    	}
    }
    
    public class Main{
    	public static void main(String[] args) {
        	Thread1 thread = new Thread1();
        	Thread2 thread2 = new Thread2(thread);
        	thread2.start();
        	thread.start();
        	System.out.println("main end "+System.currentTimeMillis());
    	}
    }
    

    这个例子运行结果存在以下情况

    B begin 1494995803564
    main end 1494995803565
    A begin 1494995803565
    A end 1494995803565
    B end 1494995803565
    

    wait()在没有notify()情况下自动苏醒了,因此这里可以看到,当前情况下Thread.wait()等待过程中,如果Thread结束了,是可以自动唤醒的。这个会在join中被使用。

    join

    了解了wait的实现原理之后就可以来看join了,join是Thread类的方法,不是底层本地方法,这里可以看一下它的源码。

    public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;
        //参数判断<0,抛异常
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
        //参数为0,即join()
        if (millis == 0) {
            //当前线程存活,就调用wait(0),一直到调用join的线程结束再自动苏醒
            while (isAlive()) {
                wait(0);
            }
            //参数>0,调用wait(long millis)等待一段时间后自动唤醒
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
    
    public final synchronized void join(long millis, int nanos)
    throws InterruptedException {
    
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
    
        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }
    
        if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
            millis++;
        }
    
        join(millis);
    }
    
    public final void join() throws InterruptedException {
        join(0);
    }
    

    很明显,join的三个重载方法主要还是基于join(long millis)方法,因此我们主要关注这个方法,方法的处理逻辑如下

    • 判断参数时间参数,如果参数小于0,抛出IllegalArgumentException("timeout value is negative")异常
    • 参数等于0,判断调用join的线程(假设是A)是否存活,不存活就不执行操作,如果存活,就调用wait(0),阻塞join方法,等待A线程执行完在结束join方法。
    • 参数大于0,判断调用join的A线程是否存活,不存活就不执行操作,如果存活,就调用wait(long millis),阻塞join方法,等待时间结束再继续执行join方法。

    由于join是synchronized修饰的同步方法,因此会出现join(long millis)阻塞时间超过了millis的值。

    public class Thread1 extends Thread{
    	@Override
    	public void run() {
        	synchronized (this) {
            	System.out.println("A begin " + System.currentTimeMillis());
            	try {
    				sleep(5000);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
            	System.out.println("A end " + System.currentTimeMillis());
        	}
    	}
    }
    
    public class Main{
    	public static void main(String[] args) {
        	Thread1 thread = new Thread1();
        	thread.start();
        	try {
    			thread.join(1000);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
        	System.out.println("main end "+System.currentTimeMillis());
    	}
    }
    

    这个例子的运行结果是

    A begin 1494996862054
    A end 1494996867056
    main end 1494996867056
    

    main线程一定是最后执行完的,按照流程来说,1秒之后阻塞就结束了,main线程应该就可以开始执行了,但是这里有一个注意点,join(long millis)在执行millis>0的时候在wait(delay)之后还有一行代码,而上面代码1秒之后只是结束了wait方法,并没有执行完join方法。上面的例子,由于join的锁和thread的锁相同,在thread运行完之前,锁不会释放,那么导致join一直阻塞在最后一步无法结束,才会出现上面的情况。

  • 相关阅读:
    HashCode的作用
    为什么重写equals一定要重写hashcode?
    java timer 定时器原理
    java内部类作用
    jeesite添加多数据源
    Eclipse之安装Jadclipse插件直接查看class文件
    NIO和IO的主要区别
    sql 语句
    HTTP Error 500.0
    基于.NetCore3.1系列 ——认证授权方案之Swagger加锁
  • 原文地址:https://www.cnblogs.com/xudilei/p/6867045.html
Copyright © 2020-2023  润新知