Java多线程学习笔记14之线程间通信

版权声明:分享才能获得最大的价值 https://blog.csdn.net/qq_32252957/article/details/83450596

详细代码见:github代码地址

本节内容:

1) 实战 等待/通知之交叉备份

2) 方法join的使用(Jdk文档翻译及源码解析)

    join()及join(long)的使用和实现原理

    join(long)和sleep(long)的区别及源码分析


(3) 实战 等待/通知之交叉备份
创建20个线程,其中10个线程是将数据备份到A数据库中,另外10个线程将数据备份到B数据库中,并且备份
A数据库和B数据库是交叉进行的。
我们使用等待/通知技术,让20个线程的运行效果变成有序的。

package chapter03.section1.thread_3_1_14.project_1_wait_noify_insert_test;

public class DBTools {
	
	/**
	 * 变量prevIsA的主要作用就是确保备份"****"数据库A首先执行,
	 * 然后"@@@@"数据库B交替进行备份
	 */
	volatile private boolean prevIsA = false;
	
	synchronized public void backupA() {
		try {
			while(prevIsA == true) {
				wait(); //先前A备份,则A等待B备份
			}
			for(int i = 0; i < 5; i++) {
				System.out.println("****");
			}
			prevIsA = true;
			notifyAll();
		} catch (InterruptedException e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}
	
	synchronized public void backupB() {
		try {
			while(prevIsA == false) {
				wait();
			}
			for(int i = 0; i < 5; i++) {
				System.out.println("@@@@");
			}
			prevIsA = false;
			notifyAll();
		} catch (InterruptedException e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}
}


package chapter03.section1.thread_3_1_14.project_1_wait_noify_insert_test;

public class BackupA extends Thread{
	private DBTools dbtools;
	
	public BackupA(DBTools dbtools) {
		super();
		this.dbtools = dbtools;
	}
	
	@Override
	public void run() {
		dbtools.backupA();
	}
}


package chapter03.section1.thread_3_1_14.project_1_wait_noify_insert_test;

public class BackupB extends Thread{
	private DBTools dbtools;
	
	public BackupB(DBTools dbtools) {
		super();
		this.dbtools = dbtools;
	}
	
	@Override
	public void run() {
		dbtools.backupB();
	}
}


package chapter03.section1.thread_3_1_14.project_1_wait_noify_insert_test;

public class Run {

	public static void main(String[] args) {
		DBTools dbtools = new DBTools();
		for(int i = 0; i < 20; i++) {
			BackupB output = new BackupB(dbtools);
			output.start();
			BackupA input = new BackupA(dbtools);
			input.start();
		}
	}
}
/*
result:
@@@@
@@@@
@@@@
@@@@
@@@@
****
****
****
****
****
@@@@
@@@@
@@@@
@@@@
@@@@
交替运行
*/

结果分析:
这个程序并没有利用好多线程优势,只是一个示例

4. 方法join的使用
在很多情况下,主线程创建并启动子线程,如果子线程中要进行大量的耗时运算(比如网络请
求,下载数据、读写文件等)主线程往往将早于子线程结束之前结束。
这时,如果主线程想等待
子线程执行完之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用
到join()方法了。方法join()的作用是等待线程对象销毁。

方法join的作用是使所属的线程对象x正常执行run()方法中的任务,而使当前线程z进行无限期
的阻塞,等待线程x销毁后(即线程的状态是TEMINATED)再继续执行线程z后面的代码
join在内部使用wait()方法进行等待,而synchronized关键字使用的是"对象监视器"原理作为
同步


阅读JDK9文档

join():
public final void join​()
                throws InterruptedException
Waits for this thread to die.
An invocation of this method behaves in exactly the same way as the invocation
等待此线程死亡
调用方法join()与调用join(0)作用完全相同

join(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
抛出:
InterrupedException - 如果有任何线程中断了当前的线程。当这个异常被抛出,则当前线程
的中断状态被清除


join(long millis):
public final void join​(long millis)
                throws InterruptedException
Waits at most millis milliseconds for this thread to die. A timeout of 0 means 
to wait forever.
This implementation uses a loop of this.wait calls conditioned on this.isAlive. 
As a thread terminates the this.notifyAll method is invoked. It is recommended 
that applications not use wait, notify, or notifyAll on Thread instances.
最多等待调用此方法的线程死亡millis毫秒,0意味着永远等待。实现是使用一个调用wait(0)的循环
while(isAlive){this.wait(0);}.当这个线程被终止之后释放锁,this.notifyAll通知线程来
准备获得这个锁。建议应用程序不要在线程实例上使用等待、通知或notifyAll方法。

Parameters:
millis - the time to wait in milliseconds 最大等待的毫秒数
Throws:
IllegalArgumentException - if the value of millis is negative 
如果millis是负数,那么抛出非法参数异常IllegalArgumentException
InterruptedException - if any thread has interrupted the current thread. The 
interrupted status of the current thread is cleared when this exception is thrown.
如果任意线程中断了当前线程。那么抛出这个异常,同时当前线程的中断状态被清除


(1) 学习方法join前的铺垫

package chapter03.section2.thread_3_2_1.project_1_joinTest1;

public class MyTest extends Thread {
	
	@Override
	public void run() {
		try {
			int secondValue = (int)(Math.random() * 10000);
			System.out.println(secondValue);
			Thread.sleep(secondValue);
		} catch (InterruptedException e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}
}


package chapter03.section2.thread_3_2_1.project_1_joinTest1;

public class Test {
	public static void main(String[] args) {
		MyTest threadTest = new MyTest();
		threadTest.start();
		
		// Thread.sleep(?)
		System.out.println("我想当threadTest对象执行完毕后我再执行");
		System.out.println("但上面代码中的sleep()中的值应该写多少呢?");
		System.out.println("答案是:根据不能确定:)");	
	}
}
结果分析:
只需要修改Test.java类就可以解决
package chapter03.section2.thread_3_2_2.project_1_joinTest2;

public class Test {
	public static void main(String[] args) throws InterruptedException{
		MyTest threadTest = new MyTest();
		threadTest.start();
		
		threadTest.join();
		
		// Thread.sleep(?)
		System.out.println("我想当threadTest对象执行完毕后我再执行");
		System.out.println("但上面代码中的sleep()中的值应该写多少呢?");
		System.out.println("答案是:根据不能确定:)");	
	}
}
/*
result:
8370
我想当threadTest对象执行完毕后我再执行
但上面代码中的sleep()中的值应该写多少呢?
答案是:根据不能确定:)
*/

结果分析:
join方法实现是通过wait.当main线程调用t.join时,main线程会获得线程对象t锁,
调用该对象的wait(等待时间),直到该对象唤醒main线程.这些具体代码设计到JVM
低层实现,由于本人对C++不太熟,也暂时没有学习计划,对于这块感兴趣的可以去看
OpenJdk 源码: http://openjdk.java.net/

(2) 方法join与异常
在join过程中,如果当前线程对象被中断,则当前线程出现异常

package chapter03.section2.thread_3_2_3.project_1_joinException;

public class ThreadA extends Thread {
	
	@Override
	public void run() {
		for(int i = 0; i < Integer.MAX_VALUE; i++) {
			String newString = new String();
			Math.random();
		}
	}
}


package chapter03.section2.thread_3_2_3.project_1_joinException;

public class ThreadB extends Thread{
	
	@Override
	public void run() {
		try {
			ThreadA a = new ThreadA();
			a.start();
			a.join();
			
			System.out.println("线程B在run end处打印了");
		} catch (InterruptedException e) {
			// TODO: handle exception
			System.out.println("线程B在catch处打印了");
			e.printStackTrace();
		}
	}

}


package chapter03.section2.thread_3_2_3.project_1_joinException;

public class ThreadC extends Thread {
	
	private ThreadB threadB;
	
	public ThreadC(ThreadB threadB) {
		super();
		this.threadB = threadB;
	}
	
	@Override
	public void run() {
		threadB.interrupt();
	}
}


package chapter03.section2.thread_3_2_3.project_1_joinException;

public class Run {
	
	public static void main(String[] args) {
		try {
			ThreadB b = new ThreadB();
			b.start();
			
			Thread.sleep(500);
			
			ThreadC c = new ThreadC(b);
			c.start();
		} catch (InterruptedException e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}
}
/*
result:
线程B在catch处打印了
java.lang.InterruptedException
	at java.base/java.lang.Object.wait(Native Method)
	at java.base/java.lang.Thread.join(Unknown Source)
	at java.base/java.lang.Thread.join(Unknown Source)
	at chapter03.section2.thread_3_2_3.project_1_joinException.ThreadB.run(ThreadB.java:10)
*/

结果分析:
可以看到线程B调用了join()方法,并且被C线程中断,抛出了InterruptedException
异常,但是A线程并没有出现异常,是正常执行的状态。

(3) 方法join(long)的使用
方法join(long)中的参数是设定等待的时间

package chapter03.section2.thread_3_2_4.project_1_joinLong;

public class MyThread extends Thread {
	@Override
	public void run() {
		try {
			System.out.println("begin Timer=" + System.currentTimeMillis());
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}
}


package chapter03.section2.thread_3_2_4.project_1_joinLong;

public class Test extends Thread{
	
	public static void main(String[] args) {
		try {
			MyThread threadTest = new MyThread();
			threadTest.start();
			
			threadTest.join(2000); //只等待2秒
//			Thread.sleep(2000);
			
			System.out.println("  end Timer=" + System.currentTimeMillis());
		} catch (InterruptedException e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}
}
/*
result:
begin Timer=1540606857302
  end Timer=1540606859304
*/

可以看到大约2秒后主线程开始执行,执行结束之后回到另一个线程继续执行3秒

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


下面是实现的源码(不包含JVM实现部分):

sleep(long millis):
/**
 * Causes the currently executing thread to sleep (temporarily cease
 * execution) for the specified number of milliseconds, subject to
 * the precision and accuracy of system timers and schedulers. The thread
 * does not lose ownership of any monitors.
 *
 * @param  millis
 *         the length of time to sleep in milliseconds
 *
 * @throws  IllegalArgumentException
 *          if the value of {@code millis} is negative
 *
 * @throws  InterruptedException
 *          if any thread has interrupted the current thread. The
 *          <i>interrupted status</i> of the current thread is
 *          cleared when this exception is thrown.
 */
public static native void sleep(long millis) throws InterruptedException;
可以看到这个方法是native方法,大家可以自己去OpenJDK查看C++代码,这里有机会以后分析。
sleep(long millis)造成当前线程休眠(暂时停止执行)一段特定的时间,取决于当前系统时钟
和调度器的精度和准确度。这个线程不释放任何监视器的所有权。


wait(long millis):
/**
 * Waits at most {@code millis} milliseconds for this thread to
 * die. A timeout of {@code 0} means to wait forever.
 *
 * <p> 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
 *          <i>interrupted status</i> 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); 
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay); 
            now = System.currentTimeMillis() - base;
        }
    }
}
其中wait()与wait(long timeout)是native方法,可以看到
millis为负数抛出非法参数异常
millis为0,永远等待,直到其他线程isAlive()为false,调用join方法的当前线程拿到锁,
然后就可以继续执行了
millis为正,则调用wait(delay)方法延迟时间到了自动唤醒调用join方法线程,然后执行
当前线程。


验证Thread.sleep(long)方法不释放锁

package chapter03.section2.thread_3_2_5.project_1_join_sleep_1;

public class ThreadA extends Thread {
	
	private ThreadB b;
	
	public ThreadA(ThreadB b) {
		super();
		this.b = b;
	}
	
	@Override
	public void run() {
		try {
			synchronized(b) {
				b.start();
				Thread.sleep(6000);
			}
		} catch (InterruptedException e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}
}


package chapter03.section2.thread_3_2_5.project_1_join_sleep_1;

public class ThreadB extends Thread{

	@Override
	public void run() {
		try {
			System.out.println("   b run begin timer="
					+ System.currentTimeMillis());
			Thread.sleep(5000);
			System.out.println("   b run   end timer="
					+ System.currentTimeMillis());
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	synchronized public void bService() {
		System.out.println("打印了bService timer=" + System.currentTimeMillis());
	}
}


package chapter03.section2.thread_3_2_5.project_1_join_sleep_1;

public class ThreadC extends Thread {
	
	private ThreadB threadB;

	public ThreadC(ThreadB threadB) {
		super();
		this.threadB = threadB;
	}

	@Override
	public void run() {
		threadB.bService();
	}
}


package chapter03.section2.thread_3_2_5.project_1_join_sleep_1;

public class Run {
	
	public static void main(String[] args) {

		try {
			ThreadB b = new ThreadB();

			ThreadA a = new ThreadA(b);
			a.start();

			Thread.sleep(1000);

			ThreadC c = new ThreadC(b);
			c.start();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
/*
result:
   b run begin timer=1540608282186
   b run   end timer=1540608287194
打印了bService timer=1540608288190
*/

结果分析:
线程ThreadA一开始运行持有ThreadB对象的锁,时间是6秒多,所以线程ThreadC只有在
ThreadA时间到达6秒后run方法执行结束释放ThreadB的锁时,才可以调用ThreadB中的
同步方法ysynchronized public void bService()方法。

验证join()方法释放锁的特点
修改上面中的ThreadA.java

package chapter03.section2.thread_3_2_5.project_1_join_sleep_1;

public class ThreadA extends Thread {
	
	private ThreadB b;
	
	public ThreadA(ThreadB b) {
		super();
		this.b = b;
	}
	
	@Override
	public void run() {
		try {
			synchronized(b) {
				b.start();
				b.join(); //说明join释放锁了
				for (int i = 0; i < Integer.MAX_VALUE; i++) {
					String newString = new String();
					Math.random();
				}
			}
		} catch (InterruptedException e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}
}
/*
result:
   b run begin timer=1540608854468
打印了bService timer=1540608855473
   b run 

ThreadA调用了join,释放了ThreadB对象锁,所以线程ThreadC拿到锁可以调用ThreadB
中的同步方法bService()

方法join()后面的代码提前运行: 出现以外
举个例子:

package chapter03.section2.thread_3_2_6.project_1_joinMoreTest;

public class ThreadA extends Thread{
	private ThreadB b;
	
	public ThreadA(ThreadB b) {
		super();
		this.b = b;
	}
	
	@Override
	public void run() {
		try {
			synchronized(b) {
				System.out.println("begin A ThreadName="
						+ Thread.currentThread().getName() + "  "
						+ System.currentTimeMillis());
				Thread.sleep(5000);
				System.out.println("  end A ThreadName="
						+ Thread.currentThread().getName() + "  "
						+ System.currentTimeMillis());
			}
		} catch (InterruptedException e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}
}


package chapter03.section2.thread_3_2_6.project_1_joinMoreTest;

public class ThreadB extends Thread {
	@Override
	synchronized public void run() {
		try {
			System.out.println("begin B ThreadName="
					+ Thread.currentThread().getName() + "  "
					+ System.currentTimeMillis());
			Thread.sleep(5000);
			System.out.println("  end B ThreadName="
					+ Thread.currentThread().getName() + "  "
					+ System.currentTimeMillis());
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}


package chapter03.section2.thread_3_2_6.project_1_joinMoreTest;

public class Run1 {
	public static void main(String[] args) {
			ThreadB b = new ThreadB();
			ThreadA a = new ThreadA(b);
			a.start();
			b.start();
			System.out.println("                    main end "
					+ System.currentTimeMillis());
	}
}
/*
result3:
                    main end 1540622542511
begin A ThreadName=Thread-1  1540622542511
  end A ThreadName=Thread-1  1540622547512
begin B ThreadName=Thread-0  1540622547512
  end B ThreadName=Thread-0  1540622552517
 */


package chapter03.section2.thread_3_2_6.project_1_joinMoreTest;

public class RunFirst {

	public static void main(String[] args) throws InterruptedException {
		ThreadB b = new ThreadB();
		ThreadA a = new ThreadA(b);
		a.start();
		b.start();
		b.join(2000);
		System.out.println("   main end=" + System.currentTimeMillis());
	}
}
/*
result1:
begin B ThreadName=Thread-0  1540621815361
  end B ThreadName=Thread-0  1540621820366
   main end=1540621820366
begin A ThreadName=Thread-1  1540621820366
  end A ThreadName=Thread-1  1540621825376
result2:
begin A ThreadName=Thread-1  1540621971188
  end A ThreadName=Thread-1  1540621976192
   main end=1540621976192
begin B ThreadName=Thread-0  1540621976192
  end B ThreadName=Thread-0  1540621981197
*/

猜你喜欢

转载自blog.csdn.net/qq_32252957/article/details/83450596