Javaマルチスレッドにおけるjoin()メソッドの概要と面接問題


ネット上の多くのブログがjoin()方法について最下位のソース分析から大まかに見ただけで、私は本当に理解できませんでした.しかし、この3つの文章を読むまで、おおらかな感じがしました.だから自分で先輩の上でjoin()の方法をまとめてみます.
文1:https://blog.csdn.net/lyzx_in_csdn/article/details/79676708
文2:https://blog.csdn.net/chenkaibsw/article/details/80912878
文3(おおらか):https://blog.csdn.net/u013425438/article/details/80205693#commentsedit
 
joinメソッドの本当の意味:
スレッドBのオブジェクトをオンラインAで呼び出す.join()メソッド:1.スレッドBが実行可能である場合、スレッドAは、スレッドBの実行が終了するまで、自分の実行をブロックする.2.スレッドBが実行可能状態でない場合、スレッドAは実行され続け、スレッドBは実行されない.      
 
 
コードで感じる
実験1
// 。
public class Thread1 extends Thread{

	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int i=0;i<11;i++) {
			System.out.println(Thread.currentThread().getName() + "------" + i);
		}
	}
}


public class Thread2 implements Runnable {

	public void run() {
		// TODO Auto-generated method stub
		for(int i=0;i<11;i++) {
			System.out.println(Thread.currentThread().getName() +"----- " + i);
		}
	}

}



// 
public class JoinTest {

	public static void main(String[] args) {

        // t1 t2    
		Thread t1 = new Thread(new Thread1(), "Thread---t1--");
		Thread t2 = new Thread(new Thread2(), "Thread---t2--");

		t1.start();

		try {
			t1.join();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		t2.start();

	}
}


実行結果:
複数回実行すると、実行結果は
Thread---t1--------0
Thread---t1--------1
Thread---t1--------2
Thread---t1--------3
Thread---t1--------4
Thread---t1--------5
Thread---t1--------6
Thread---t1--------7
Thread---t1--------8
Thread---t1--------9
Thread---t1--------10
Thread---t2------- 0
Thread---t2------- 1
Thread---t2------- 2
Thread---t2------- 3
Thread---t2------- 4
Thread---t2------- 5
Thread---t2------- 6
Thread---t2------- 7
Thread---t2------- 8
Thread---t2------- 9
Thread---t2------- 10

 
実験2
//Thread1 Thread2      
public class JoinTest {

	public static void main(String[] args) {

		Thread t1 = new Thread(new Thread1(), "Thread---t1--");
		Thread t2 = new Thread(new Thread2(), "Thread---t2--");

		t1.start();
		t2.start();
		try {
			t1.join();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}
}

実行結果:
複数回実行すると、t 1スレッドとt 2スレッドが交互に実行されることが分かった.
Thread---t1--------0
Thread---t1--------1
Thread---t1--------2
Thread---t2------- 0
Thread---t1--------3
Thread---t2------- 1
Thread---t2------- 2
Thread---t2------- 3
Thread---t2------- 4
Thread---t2------- 5
Thread---t2------- 6
Thread---t2------- 7
Thread---t2------- 8
Thread---t2------- 9
Thread---t2------- 10
Thread---t1--------4
Thread---t1--------5
Thread---t1--------6
Thread---t1--------7
Thread---t1--------8
Thread---t1--------9
Thread---t1--------10

 
実験1と実験2の運行結果の違いは、t 1によるものである.join()が加わる位置が違います.
 
実験1中ti.join()はt 2に加算.start()の前、すなわち、メインスレッドmainは、t 1スレッドのjoin()メソッドを呼び出すと、t 2スレッドが開かない.
 
実験2ではt 1.join()はt 2に置いた.start()の後、すなわち、プライマリスレッドは、tiスレッドのjoin()メソッドを呼び出す前に、t 1、t 2の2つのスレッドを開いている.
オンのt 2スレッドは、t 1スレッド、メインスレッド、並列に実行されます.一方、メインスレッドはt 1のjoin()メソッドを呼び出し、すでに実行状態にあるt 2スレッドには影響を及ぼさず、t 1のjoin()メソッドを呼び出すメインスレッドをブロックする.t 1スレッドが実行終了しないまで、メインスレッドは待機します.t 1スレッドが終了するまで、t 1スレッドはスレッド自体のnotifyAll()メソッドを呼び出し、スレッドオブジェクト上で待機しているすべてのスレッドの実行を通知します.

フローチャートの実行(メインスレッドとt 1スレッドのみの場合)


join()のソース:
    /**
     * Waits for this thread to die.
     *
     * 

An invocation of this method behaves in exactly the same * way as the invocation * *

* {@linkplain #join(long) join}{@code (0)} *
* * @throws InterruptedException * if any thread has interrupted the current thread. The * interrupted status of the current thread is * cleared when this exception is thrown. */ public final void join() throws InterruptedException { join(0);            //join() join(0) } /** * Waits at most {@code millis} milliseconds for this thread to * die. A timeout of {@code 0} means to wait forever. * *

This implementation uses a loop of {@code this.wait} calls * conditioned on {@code this.isAlive}. As a thread terminates the * {@code this.notifyAll} method is invoked. It is recommended that * applications not use {@code wait}, {@code notify}, or * {@code notifyAll} on {@code Thread} instances. * * @param millis * the time to wait in milliseconds * * @throws IllegalArgumentException * if the value of {@code millis} is negative * * @throws InterruptedException * if any thread has interrupted the current thread. The * interrupted status of the current thread is * cleared when this exception is thrown. */ 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);           //join(0) wait(0), t1 wait notify } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } }


 
コアコードは次のとおりです.
while (isAlive()) {        // join 
      wait(0);             // 
 }

isAlive()メソッドは以下で詳しく紹介しますが、まずwait(0)、wait(0)とはどういう意味かを見て、次のwait()メソッドのソースコードを見てみましょう.実はwait()メソッドはwait(0)メソッドを呼び出して実現し、wait(0)はそれをずっと待たせることです.ここで,joinメソッドの本質は,上記のスレッドインスタンスをオブジェクトロックとして利用する原理であり,スレッドが終了すると,スレッド自体のnotifyAll()メソッドが呼び出され,そのスレッドオブジェクト上で待機しているすべてのスレッドの実行を通知することである.
while(isAlive)文の役割
次の簡単なコードがあります.
コード実装:
public class HighConcurrency {
	 
    // 1. T2、T3 , T2 T1 ,T3 T2 
        final Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //  t1 , t1 
                    t1.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t2");
            }
        });
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //  t2 , t2 
                    t2.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t3");
            }
        });
        t3.start();// , !
        t2.start();
    }
}

プログラム出力:
t2
t3

まずt 3が実行する.start()は、t 3スレッドのrunメソッドでt 2を実行する.join()は、この場合2つのケースがあり、t 2がまだ実行されていない可能性がある.start()は、t 2が初期状態である、t 2が実行する可能性もある.start()、t 2はランタイム状態にあるので、ここを見て分かるようにjoinソースコードのwhile(isAlive()は、実はwhile(this.isAlive()に相当する)は、ここのt 2がすでにランタイム状態であるか否か(startメソッドを呼び出すか否か)を判断することに相当する.このように設計されたのは、t 2スレッドが実行されないことを防止するためであり、このときt 3スレッドがwait(0)メソッドを直接実行すると、コードが詰まって死ぬまで待機する.
検証のため、コードは次のように変更されました.
        t3.start();
        try {
		Thread.sleep(10);
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
        t2.start();

このとき出力
t3
t2

 
分析:t 2.start()とt 3.start()間の時間間隔が長くなり、t 3スレッドでt 2が実行する.join()の場合、t 2が初期状態でまだ運転状態ではないことが保証され、このときwhile(isAlive()は成立せず、wait(0)メソッドは実行されないので、t 3スレッドは待機せず、t 3が先に出力される.
次に、t 2が実行することを保証するために、コードを以下のように変更する.join()の場合、t 2.start()は既に実行されており、t 2は既に実行中である:
public class HighConcurrency {
	
    public static void main(String[] args) {
 
        final Thread t2 = new Thread(new Runnable() {
 
            @Override
            public void run() {         
                System.out.println("t2");
            }
        });
        Thread t3 = new Thread(new Runnable() {
 
            @Override
            public void run() {
                try {
                	Thread.sleep(10);    // t2 
                    t2.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t3");
            }
        });
        t3.start();
        t2.start();
    }
}

このとき出力
t2
t3

解析:t 3スレッドでt 2を実行する.join()メソッドの前にsleep(10)メソッドを実行し、t 2を実行することを保証する.join()の場合、t 2は既にランタイム状態であるため、t 3はwait(0)メソッドを実行して待機し、t 2が先に実行され、t 3が実行されるまで待機するので、t 2 t 3を出力する.
 
 

join()の役割は、メインスレッドがサブスレッドの終了を待ってから実行を続行することです。


面接問題:


今T 1、T 2、T 3の3つのスレッドがあって、あなたはどのようにT 2がT 1の実行が終わった後に実行することを保証して、T 3はT 2の実行が終わった後に実行します

package Demo0423JoinTest;

/**
 * join

* * join() “ ” “ ” * * T1、T2、T3 , T2 T1 ,T3 T2 */ public class JoinTest02 { public static void main(String[] args) throws InterruptedException { Thread t3 = new Thread(new MyThread3()); t3.setName(" 3"); t3.start(); t3.join(); System.out.println(Thread.currentThread().getName()+" "); } } class MyThread1 extends Thread{ @Override public void run() { try { //t1 System.out.println(Thread.currentThread().getName()+" "); Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println(Thread.currentThread().getName()+" "); } } } class MyThread2 extends Thread{ @Override public void run() { try { // t1, t2 t1 Thread t1 = new Thread(new MyThread1()); t1.setName(" 1"); t1.start(); t1.join(); //t2 System.out.println(Thread.currentThread().getName()+" "); Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println(Thread.currentThread().getName()+" "); } } } class MyThread3 extends Thread{ @Override public void run() { try { // t2, t3 t2 MyThread2 t2 = new MyThread2(); t2.setName(" 2"); t2.start(); t2.join(); // t3 System.out.println(Thread.currentThread().getName()+" "); Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println(Thread.currentThread().getName()+" "); } } }

 :

 1 
 1 
 2 
 2 
 3 
 3 
main