java多线程 关于join 的问题

我想通过join让线程 a b c d顺序打印,但是现在是乱序打印

public class ExJoin extends Thread {

    Thread thread;

    public ExJoin(){

    }

    public ExJoin(Thread thread){
        this.thread  = thread;
    }

    @Override
    public void run() {
        try {
            if (thread != null){
                thread.join();
            }
            System.out.println(Thread.currentThread().getName()+"  is running");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Thread threadA = new ExJoin();
        threadA.setName("A");
        Thread threadB = new ExJoin(threadA);
        threadB.setName("B");
        Thread threadC = new ExJoin(threadB);
        threadC.setName("C");
        Thread threadD =  new ExJoin(threadC);
        threadD.setName("D");


        threadC.start();
        threadD.start();
        threadA.start();
        threadB.start();
    }
}
阅读 4.9k
5 个回答

在run方法里加个sleep试试。

按照楼主的代码逻辑,join是无法做到有序的,因为A/B/C/D线程的启动运行依赖于CPU调度,如果线程B未启动线程C调用threadB.join方法是不会生效的。可以看看jdk内置的逻辑实现isAlive()false时直接返回了。

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 方法的具体实现:
join 方法的具体实现

无参的 join 方法即执行下列代码:

while (isAlive()) {
    wait(0);
}

然后我们看看 isAlive 方法的实现,发现 isAlive 是一个 native 方法 —— 不过没关系,我们可以看它的 javadoc 注释:
isAlive 方法

"A thread is alive if it has been started and has not yet died"
—— 所以一个线程只有在线程已经开始之后(并且还没有结束),isAlive 方法才会返回 true
也就是说只有线程是 started (已经启动)之后,join 方法才会生效(即阻塞直到线程执行完毕)。

首先,每个线程新建完毕时候(就是 new Thread(...)) 调用之后,此时线程处于 NEW 状态;
然后线程的 start 方法调用之后,此时线程才处于可运行状态(RUNNABLE,即 started)。
但是所有线程在何时运行,都是由 JVM 来调度的

代码:

threadC.start();
threadD.start();
threadA.start();
threadB.start();

这四个方法都是处于主线程中,threadC.start() 之后,JVM 新建了线程 threadC,那么如果这时候 JVM 调度 threadC 来运行,而不是继续运行主线程,那么 threadC 中去调用 threadB.join() 便会直接返回而不是等待 threadB 完成 —— 因为此时 threadB 的 start 方法还没有被调用, threadB 并没有处于运行状态(started)。

如果要实现顺序输出的逻辑,可以这样修改代码:

public class ExJoin extends Thread {

    Thread thread;

    public ExJoin() {

    }

    public ExJoin(Thread thread) {
        this.thread = thread;
    }

    @Override
    public void run() {
        try {
            if (thread != null) {
                System.out.println(thread.getName() + " State: " + thread.getState());
                while (thread.getState() == Thread.State.NEW) { // 如果线程状态是 NEW,那就等待其为 RUNNABLE
                    Thread.sleep(1);
                }
                thread.join();
            }
            System.out.println(Thread.currentThread().getName() + "  is running");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread threadA = new ExJoin();
        threadA.setName("A");
        Thread threadB = new ExJoin(threadA);
        threadB.setName("B");
        Thread threadC = new ExJoin(threadB);
        threadC.setName("C");
        Thread threadD = new ExJoin(threadC);
        threadD.setName("D");

        threadC.start();

        Thread.sleep(0, 1); // 模拟调度时间差为 1 纳秒
        threadD.start();

        Thread.sleep(0, 1);
        threadA.start();

        Thread.sleep(0, 1);
        threadB.start();
    }
}

某次的运行结果:
某次的运行结果

run方法中sleep(1000)确实会顺序出现。我猜测可能是指令重排序,前面的线程已经就绪,但是后面的线程对象还没有创建出来,所以出现了乱序。

我们使用join()方法是为了实现线程同步,那么是事先知道事务的处理先后顺序,然后用join()方法来保证这个先后顺序。你现在的代码设计上就存在一定的逻辑问题,虽然执行上没有报错,但是纯粹属于实验测试,没有任何实际场景需要。另外还缺少对比测试,无法体现join()方法的作用。

首先,关于join()方法作用的理解,建议你看一下这篇文章《线程中join方法的作用》

当我们在主程序中某一段代码块中新建了一个线程,而且,这个线程的处理结果直接影响到下面代码的执行结果,总所周知,线程之间是异步执行的,我们无法控制主线程和新建子线程之间的执行顺序,故而我们需要找一个可以让多线程之间实现同步执行的一种方式,在Java中我们有了Join方法,该方法使得子线程能够一直保持执行或者是在限定的时间内始终让该子线程执行,超过时间后,再是异步执行。

然后,关于你的代码,我进行一些调整,现粘贴如下:

public class ExJoin {

    private String name;
    
    public ExJoin(String name) {
        this.name = name;
    }
    
    @Override
    public String toString() {
        return name + ">do something";
    }
    
    public static void main(String[] args) {
        final ExJoin exJoinA = new ExJoin("A");
        final ExJoin exJoinB = new ExJoin("B");
        final ExJoin exJoinC = new ExJoin("C");
        final ExJoin exJoinD = new ExJoin("D");
        
        //未加入join方法
//        System.out.println("未加入join方法的运行结果>>");
//        final Thread t1 = new Thread() {
//            public void run() {
//                System.out.println(exJoinA);
//            }
//        };
//        final Thread t2 = new Thread() {
//            public void run() {
//                System.out.println(exJoinB);
//            }
//        };
//        final Thread t3 = new Thread() {
//            public void run() {
//                System.out.println(exJoinC);
//            }
//        };
//        Thread t4 = new Thread() {
//            public void run() {
//                System.out.println(exJoinD);
//            }
//        };
        
        //加入join方法
        System.out.println("加入join方法");
        final Thread t1 = new Thread() {
            public void run() {
                System.out.println(exJoinA);
            }
        };
        final Thread t2 = new Thread() {
            public void run() {
                try {
                    t1.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(exJoinB);
            }
        };
        final Thread t3 = new Thread() {
            public void run() {
                try {
                    t2.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(exJoinC);
            }
        };
        Thread t4 = new Thread() {
            public void run() {
                try {
                    t3.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(exJoinD);
            }
        };

        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }

}

运行结果对比:

clipboard.png

clipboard.png

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题