在很多情况下,主线程穿件并启动子线程,如果子线程中药进行大量的耗时运算,主线程往往将早于子线程结束之前结束。这时,如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到join方法了。方法join的作用是等待线程对象销毁。
先来看一下一个示例代码:
package JoinTest;
/**
* @Author LiBinquan
*/
public class MyThread extends Thread{
@Override
public void run() {
try{
int secondValue = (int)(Math.random()*1000);
System.out.println(secondValue);
Thread.sleep(secondValue);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
package JoinTest;
/**
* @Author LiBinquan
*/
public class Test {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
System.out.println("我想当threadTest对象执行完毕后我再执行");
System.out.println("但上面代码中的sleep 中的值应该写多少呢");
System.out.println("答案是:不能确定");
}
}
输出:
1.使用join方法解决
修改Test.java
package JoinTest;
/**
* @Author LiBinquan
*/
public class Test {
public static void main(String[] args) {
try {
MyThread myThread = new MyThread();
myThread.start();
myThread.join();
System.out.println("我想当threadTest对象执行完毕后我再执行");
System.out.println("但上面代码中的sleep 中的值应该写多少呢");
System.out.println("答案是:不能确定");
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
输出:
方法join的作用是使所属的线程对象x正常执行run()方法中的任务,而使当前线程z进行无限期的阻塞,等待线程x销毁后再继续执行线程z后面的代码。
方法join具有使线程排队运行的作用,有些类似同步的运行效果。join与synchronized的区别是:join在内部使用wait()方法进行等待,而synchronized关键字使用的是”对象监视器“原理做为同步。
2.方法join与异常
在join过程中,如果当前线程对象被中断,则当前线程出现异常。
示例代码:
线程a:
package JoinTest;
/**
* @Author LiBinquan
*/
public class ThreadA extends Thread{
@Override
public void run() {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
String str = new String();
Math.random();
}
}
}
线程b:
package JoinTest;
/**
* @Author LiBinquan
*/
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){
System.out.println("线程B在异常处打印");
e.printStackTrace();
}
}
}
线程c:
package JoinTest;
/**
* @Author LiBinquan
*/
public class ThreadC extends Thread{
private ThreadB threadB;
public ThreadC(ThreadB threadB){
this.threadB = threadB;
}
@Override
public void run() {
threadB.interrupt();
}
}
运行类:
package JoinTest;
/**
* @Author LiBinquan
*/
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){
e.printStackTrace();
}
}
}
输出:
由输出说明,方法join和interrupt方法如果遇到,则会出现异常,但是进程的按钮还是呈”红色“,原因是线程ThreadA并未出现异常时正常执行的状态。
3.方法join(long)的使用
方法join(long)中的参数是设定等待的时间。
示例代码:
Mythread类:
package JoinTest;
/**
* @Author LiBinquan
*/
public class MyThread extends Thread{
@Override
public void run() {
try{
System.out.println("begin Time = "+System.currentTimeMillis());
Thread.sleep(5000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
运行类:
package JoinTest;
/**
* @Author LiBinquan
*/
public class Test {
public static void main(String[] args) {
try {
MyThread myThread = new MyThread();
myThread.start();
myThread.join(2000);
System.out.println("end time = "+System.currentTimeMillis());
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
输出:
其实这边将myThread.join(2000)改为myThread.sleep(2000)运行结果还是等待了2秒。有兴趣可以自己修改运行一下。
4.方法join(long)与sleep(long)的区别
方法join(long)的功能在内部是使用wait(long)方法来实现的,所以join(long)方法具有释放锁的特点。
join的源码如下:
/**
* 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(long)方法后,当前线程的锁被释放,那么其他线程就可以调用此线程中的同步方法了。
而Thread.sleep(long)方法却不释放锁。在下面的示例中将测试Thread.sleep(long)方法具有不释放锁的特点。
示例代码如下:
线程A
package JoinTest;
/**
* @Author LiBinquan
*/
public class ThreadA extends Thread{
private ThreadB threadB ;
public ThreadA(ThreadB threadB){
super();
this.threadB = threadB;
}
@Override
public void run() {
try{
synchronized (threadB){
threadB.start();
Thread.sleep(6000);
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
线程b
package JoinTest;
/**
* @Author LiBinquan
*/
public class ThreadB extends Thread{
@Override
public void run() {
try{
System.out.println(" b run begin time = "+System.currentTimeMillis());
Thread.sleep(5000);
System.out.println(" b run end time = "+System.currentTimeMillis());
}catch (InterruptedException e){
System.out.println("线程B在异常处打印");
e.printStackTrace();
}
}
synchronized public void bService(){
System.out.println("打印了bService time = "+System.currentTimeMillis());
}
}
线程c
package JoinTest;
/**
* @Author LiBinquan
*/
public class ThreadC extends Thread{
private ThreadB threadB;
public ThreadC(ThreadB threadB){
this.threadB = threadB;
}
@Override
public void run() {
threadB.bService();
}
}
运行类:
package JoinTest;
/**
* @Author LiBinquan
*/
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();
}
}
}
输出:
由输出可得,由于线程ThreadA使用Thread.sleep(long)方法一直持有ThreadB对象的锁,时间达到6秒,所以线程ThreadC只有在ThreadA时间达到6秒后释放ThreadB的锁时,才可以调用ThreadB中的同步方法synchronized public void bService().
上面测试证明Thread.sleep(long)方法不释放锁。
接下来测试join()方法释放锁。
修改线程A
package JoinTest;
/**
* @Author LiBinquan
*/
public class ThreadA extends Thread{
private ThreadB threadB ;
public ThreadA(ThreadB threadB){
super();
this.threadB = threadB;
}
@Override
public void run() {
try{
synchronized (threadB){
threadB.start();
threadB.join();
for (int i = 0; i < Integer.MAX_VALUE; i++) {
String str = new String();
Math.random();
}
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
输出:
由于线程ThreadA释放了ThreadB的锁,所以线程ThreadC可以调用ThreadB中的同步方法synchronized public void bService();
5.方法join()后面的代码提前运行:出现意外
这边说明一下使用join会遇到的陷阱。
线程A:
package JoinTest;
/**
* @Author LiBinquan
*/
public class ThreadA extends Thread{
private ThreadB threadB ;
public ThreadA(ThreadB threadB){
super();
this.threadB = threadB;
}
@Override
public void run() {
try{
synchronized (threadB){
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){
e.printStackTrace();
}
}
}
线程B
package JoinTest;
/**
* @Author LiBinquan
*/
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();
}
}
synchronized public void bService(){
System.out.println("打印了bService time = "+System.currentTimeMillis());
}
}
运行类:
package JoinTest;
/**
* @Author LiBinquan
*/
public class Run {
public static void main(String[] args) {
try{
ThreadB b = new ThreadB();
ThreadA a = new ThreadA(b);
a.start();
b.start();
b.join(2000);
System.out.println(" main end "+System.currentTimeMillis());
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
输出:
这个就是陷阱,就是join方法后面的代码提前运行了。
6.方法join()后面的代码提前运行:释放意外
示例代码:
修改run运行类:
package JoinTest;
/**
* @Author LiBinquan
*/
public class Run {
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());
}
}
输出:
我们可以多次运行,然后可以发现:main end 往往都会第一个打印。所以可以完全确定得出:方法join(2000)大部分是先运行的,也就是先抢到ThreadB的锁,然后快速进行释放。