zoukankan      html  css  js  c++  java
  • 方法join()使用详解

      在线程的常见方法一节中,已经接触过join()方法的使用。

      在很多情况下,主线程创建并启动子线程,如果子线程中要进行大量的耗时运算,主线程将早于子线程结束。这时,如果主线程想等子线程执行完成才结束,比如子线程处理一个数据,主线程想要获得这个数据中的值,就要用到join()方法了。方法join()的作用是等待线程对象销毁。

      join方法的主要作用就是同步,它可以使得线程之间的并行执行变为串行执行。在A线程中调用了B线程的join()方法时,表示只有当B线程执行完毕时,A线程才能继续执行。

      join方法中如果传入参数,则表示这样的意思:如果A线程中掉用B线程的join(10),则表示A线程会等待B线程执行10毫秒,10毫秒过后,A、B线程并行执行。需要注意的是,jdk规定,join(0)的意思不是A线程等待B线程0秒,而是A线程等待B线程无限时间,直到B线程执行完毕,即join(0)等价于join()。(其实join()中调用的是join(0))

      join方法必须在线程start方法调用之后调用才有意义。这个也很容易理解:如果一个线程都没有start,那它也就无法同步了。

    源码如下:     方法join(long)的功能在内部是使用wait(long)来实现的,所以join(long)方法具有释放锁的特点。

        public final void join() throws InterruptedException {
            join(0);
        }
        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);
                }
            } else {
                while (isAlive()) {
                    long delay = millis - now;
                    if (delay <= 0) {
                        break;
                    }
                    wait(delay);
                    now = System.currentTimeMillis() - base;
                }
            }
        }

    1.一个简单的join方法的使用方法

    package cn.qlq.thread.nine;
    
    
    /**
     * 线程类join()使用方法
     * 
     * @author Administrator
     *
     */
    public class Demo1 extends Thread {
        /**
         * 更改线程名字
         * 
         * @param threadName
         */
        public Demo1(String threadName) {
            this.setName(threadName);
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 2; i++) {
                try {
                    Thread.sleep(1 * 500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "-----" + i);
            }
        }
    
        public static void main(String[] args) {
            Demo1 t1 = new Demo1("t1");
            Demo1 t2 = new Demo1("t2");
            Demo1 t3 = new Demo1("t3");
            t1.start();
            /**
             * join的意思是使得放弃当前线程的执行,并返回对应的线程,例如下面代码的意思就是:
             * 程序在main线程中调用t1线程的join方法,则main线程放弃cpu控制权,并返回t1线程继续执行直到线程t1执行完毕
             * 所以结果是t1线程执行完后,才到主线程执行,相当于在main线程中同步t1线程,t1执行完了,main线程才有执行的机会
             */
            try {
                t1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (t2.isAlive()) {
                System.out.println("t2 is alive");
            } else {
                System.out.println("t2 is not alive");
            }
            t2.start();
            t3.start();
        }
    }

    结果:

    t1-----0
    t1-----1
    t2 is not alive
    t3-----0
    t2-----0
    t2-----1
    t3-----1

      方法x.join()的作用是使所属线程x 正常执行run()中的方法,而使得调用x.join()的线程处于无限期阻塞状态,等待x线程销毁后再继续执行线程z后面的代码。

      方法join()具有使线程排队运行的作用,有些类似于同步的运行效果。join()与synchronized的区别是:join在内部调用wait()方法进行等待,而synchronized关键字使用的是"对象监视器"原理作为同步。

    2. join()与异常

      在join()过程中,如果当前线程被中断,则当前线程出现异常。(注意是调用thread.join()的线程被中断才会进入异常,比如a线程调用b.join(),a中断会报异常而b中断不会异常)

    如下:threadB中启动threadA,并且调用其方法等待threadA完成,此时向threadB发出中断信号,会进入中断异常代码。

    package cn.qlq.thread.nine;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    /**
     * 线程类join()使用方法--join中中断
     * 
     * @author Administrator
     *
     */
    public class Demo2 extends Thread {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(Demo2.class);
    
        public static void main(String[] args) throws InterruptedException {
    
            final Thread threadA = new Thread(new Runnable() {
                @Override
                public void run() {
                    LOGGER.info("threadA run");
                    while (true) {
    
                    }
                }
            }, "threadA");
    
            Thread threadB = new Thread(new Runnable() {
                @Override
                public void run() {
                    LOGGER.info("threadB run");
                    threadA.start();
                    try {
                        threadA.join();
                    } catch (InterruptedException e) {
                        LOGGER.error("join error ,threadName - > {}", Thread.currentThread().getName(), e);
                    }
                }
            }, "threadB");
            threadB.start();
    
            // 向threadB发出中断信号
            Thread.sleep(1 * 1000);
            threadB.interrupt();
        }
    }

    结果:

     上面虽然进入异常代码块,但是线程仍然未停止 (因为threadA并没有抛出异常,所以仍然在存活),我们用jvisualVM查看线程:

     3. 方法join(long)的使用

      方法join(long)是设定等待的时间。实际join()方法中调用的是join(0),当参数是0的时候表示无限期等待。

    package cn.qlq.thread.nine;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    /**
     * 线程类join()使用方法--join中中断
     * 
     * @author Administrator
     *
     */
    public class Demo3 extends Thread {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(Demo3.class);
    
        public static void main(String[] args) throws InterruptedException {
    
            final Thread threadA = new Thread(new Runnable() {
                @Override
                public void run() {
                    LOGGER.info("threadA run");
                    while (true) {
    
                    }
                }
            }, "threadA");
    
            Thread threadB = new Thread(new Runnable() {
                @Override
                public void run() {
                    LOGGER.info("threadB run");
                    threadA.start();
                    try {
                        threadA.join(2 * 1000);
                    } catch (InterruptedException e) {
                        LOGGER.error("join error ,threadName - > {}", Thread.currentThread().getName(), e);
                    }
                    LOGGER.info("threadB end");
                }
            }, "threadB");
            threadB.start();
        }
    }

     结果:threadB线程等待threadA线程2秒钟之后两个线程开始并行运行)

     4. 方法join(long)与sleep(long)的区别

       方法join(long)的功能在内部是使用wait(long)来实现的,所以join(long)方法具有释放锁的特点。

    方法join(long)的源码如下:

        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);
                }
            } else {
                while (isAlive()) {
                    long delay = millis - now;
                    if (delay <= 0) {
                        break;
                    }
                    wait(delay);
                    now = System.currentTimeMillis() - base;
                }
            }
        }

      从源码可以看出,调用join(long)方法之后内部调用了wait()方法,因此会释放该对象锁。

    (1)测试join(long)会释放锁

    package cn.qlq.thread.nine;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;/**
     * join(long)释放锁
     * 
     * @author Administrator
     *
     */
    public class Demo4 extends Thread {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(Demo4.class);
    
        public static void main(String[] args) throws InterruptedException {
            LOGGER.info("main start");
            final Demo4 demo4 = new Demo4();
    
            // 启动demo4线程并且占用锁之后调用join(long)方法
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        synchronized (demo4) {
                            LOGGER.info("进入同步代码块,threadName ->{} 占有 demo4 的锁", Thread.currentThread().getName());
                            demo4.start();
                            demo4.join(4 * 1000);
                            LOGGER.info("退出同步代码块,threadName ->{}", Thread.currentThread().getName());
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "threadA").start();
    
            // 休眠2秒钟,调用对象的同步方法
            Thread.currentThread().sleep(2 * 1000);
            demo4.test2();
        }
    
        @Override
        public void run() {
            try {
                Thread.sleep(10 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        public synchronized void test2() {
            LOGGER.info("进入test2方法,占有锁,threadname->{}", currentThread().getName());
        }
    }

    结果: (在休眠2秒钟后调用对象的同步方法能进入方法则证明join方法释放锁;而且在退出同步代码块之前打印了test信息则说明test2占用锁成功)

    17:57:02 [cn.qlq.thread.nine.Demo4]-[INFO] main start
    17:57:02 [cn.qlq.thread.nine.Demo4]-[INFO] 进入同步代码块,threadName ->threadA 占有 demo4 的锁
    17:57:04 [cn.qlq.thread.nine.Demo4]-[INFO] 进入test2方法,占有锁,threadname->main
    17:57:06 [cn.qlq.thread.nine.Demo4]-[INFO] 退出同步代码块,threadName ->threadA

    (2)测试sleep(long)不会释放锁

    package cn.qlq.thread.nine;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;/**
     * sleep(long)不会释放锁
     * 
     * @author Administrator
     *
     */
    public class Demo5 extends Thread {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(Demo5.class);
    
        public static void main(String[] args) throws InterruptedException {
            LOGGER.info("main start");
            final Demo5 demo4 = new Demo5();
    
            // 启动demo4线程并且占用锁之后调用join(long)方法
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        synchronized (demo4) {
                            LOGGER.info("进入同步代码块,threadName ->{} 占有 demo4 的锁", Thread.currentThread().getName());
                            demo4.start();
                            demo4.sleep(4 * 1000);
                            LOGGER.info("退出同步代码块,threadName ->{}", Thread.currentThread().getName());
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "threadA").start();
    
            // 休眠2秒钟,调用对象的同步方法
            Thread.currentThread().sleep(2 * 1000);
            demo4.test2();
        }
    
        @Override
        public void run() {
            try {
                Thread.sleep(10 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        public synchronized void test2() {
            LOGGER.info("进入test2方法,占有锁,threadname->{}", currentThread().getName());
        }
    }

    结果:(退出代码块才进入test2方法,证明sleep(long)没有释放锁)

    17:59:30 [cn.qlq.thread.nine.Demo5]-[INFO] main start
    17:59:30 [cn.qlq.thread.nine.Demo5]-[INFO] 进入同步代码块,threadName ->threadA 占有 demo4 的锁
    17:59:34 [cn.qlq.thread.nine.Demo5]-[INFO] 退出同步代码块,threadName ->threadA
    17:59:34 [cn.qlq.thread.nine.Demo5]-[INFO] 进入test2方法,占有锁,threadname->main

  • 相关阅读:
    剑指 Offer 46. 把数字翻译成字符串
    剑指 Offer 45. 把数组排成最小的数
    1319.连通网络的操作次数-并查集
    数字序列中某一位的数字
    989.数组形式的整数加法
    java多线程
    剑指offer 48 -最长不含重复字符的子字符串 动态规划
    springboot 使用 lombok插件中的@data 注解
    netty 转发服务
    在静态方法中获取properties /yml 配置文件中的信息
  • 原文地址:https://www.cnblogs.com/qlqwjy/p/10120457.html
Copyright © 2011-2022 走看看