在多数情况下,主线程创建并启动子线程,如果子线程中要进行大量的耗时计算,主线程往往将早于子线程结束之前结束。这时,如果主线程想等待子线程执行完了再结束。比如子线程处理一个数据,主线程要取到这个数据中的值,就要用到join()方法了。方法join()的作用是等待线程对象销毁。
学习join方法前的铺垫:
创建如下代码:
public class MyThread extends Thread{
@Override
public void run() {
int sencondValue = (int)(Math.random()*1000);
System.out.println(sencondValue);
try {
Thread.sleep(sencondValue);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Run {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
//Thread.sleep(?);
System.out.println("当myThread对象执行完毕后再执行");
System.out.println("但上面代码的sleep的值写多少");
System.out.println("答案是不确定");
}
}
程序运行结果如下:
用join()方法来解决:
方法join可以解决这个问题。创建如下代码:
public class MyThread extends Thread{
@Override
public void run() {
int sencondValue = (int)(Math.random()*1000);
System.out.println(sencondValue);
try {
Thread.sleep(sencondValue);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Run {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
myThread.join();
System.out.println("当对象myThread执行完毕后再执行");
}
}
执行结果如下:
方法join的作用是使所属的线程对象x正常执行run()方法中的任务,而使当前线程z进行无限期阻塞,等待线程x销毁后再继续执行线程z后面的代码。
方法join具有使线程排队运行的作用,有些类似同步的效果。join与synchronized的区别是:join内部是使用wait()方法进行等待的,而synchronized关键字是使用的是“对象监视器”原理做为同步。
方法join与异常:
在join过程中,如果当前线程对象被中断,则当前线程出现异常。创建如下代码:
public class ThreadA extends Thread{
@Override
public void run() {
for(int i=0;i<Integer.MAX_VALUE;i++){
String newString = new String();
Math.random();
}
}
}
public class ThreadB extends Thread {
@Override
public void run() {
try {
ThreadA ta = new ThreadA();
ta.start();
ta.join();
System.out.println("线程B在runend处打印了");
} catch (InterruptedException e) {
System.out.println("线程B在catch处打印了");
e.printStackTrace();
}
}
}
public class ThreadC extends Thread{
private ThreadB threadb;
public ThreadC(ThreadB threadb){
this.threadb = threadb;
}
@Override
public void run() {
threadb.interrupt();
}
}
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(Exception e){
e.printStackTrace();
}
}
}
执行结果如下:
说明方法join()与interrupt()方法如果彼此相遇,则会出现异常。但进程按钮还是呈红色状态,原因是线程ThreadA还在继续运行,线程ThreadA并未出现异常,是正常的状态。
方法join(long)的使用:
方法join(long)中的参数是设定等待的时间。创建如下代码:
public class MyThread extends Thread{
@Override
public void run() {
System.out.println("begin Time="+System.currentTimeMillis());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Run {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
myThread.join(2000);
System.out.println(" end Time="+System.currentTimeMillis());
}
}
执行结果如下:
运行结果等待了2秒。
但将main方法中的代码改为使用sleep(2000)方法时,运行的效果还是等待了2秒。如下所示:
public class Run {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
// myThread.join(2000);
Thread.sleep(2000);
System.out.println(" end Time=" + System.currentTimeMillis());
}
}
那使用sleep(2000)和使用join(2000)有什么区别,从上面的例子运行效果是看不出区别的,其实主要区别还是来自于这2个方法对同步的处理上。
方法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;
}
}
}
从源码中可以了解到,当执行wait(long)方法后,当前线程的锁被释放,那么其他线程就可以调用此线程中的同步方法了。
而Thread.sleep(long)方法却不释放锁。创建如下代码:
public class ThreadB extends Thread{
@Override
public void run() {
System.out.println(" b run begin timer="+System.currentTimeMillis());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(" b run end timer="+System.currentTimeMillis());
}
synchronized public void bService(){
System.out.println("打印了 bService timer="+System.currentTimeMillis());
}
}
public class ThreadA extends Thread{
private ThreadB threadB;
public ThreadA(ThreadB threadB){
this.threadB = threadB;
}
@Override
public void run() {
synchronized(threadB){
threadB.start();
try {
Thread.sleep(6000);
//Thread.sleep()方法不释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ThreadC extends Thread{
private ThreadB threadB;
public ThreadC(ThreadB threadB){
this.threadB = threadB;
}
@Override
public void run() {
threadB.bService();
}
}
public class Run {
public static void main(String[] args) throws InterruptedException {
ThreadB threadB = new ThreadB();
ThreadA threadA = new ThreadA(threadB);
threadA.start();
Thread.sleep(2000);
ThreadC threadC = new ThreadC(threadB);
threadC.start();
}
}
执行结果如下:
由于线程threadA使用了Thread.sleep(long)方法一直持有ThreadB的对象锁,时间达到6秒,所以线程ThreadC只有在ThreadA时间达到6秒后释放ThreadB的锁时,才可以调用ThreadB中的同步方法synchronized public void bService()。
此实验也说明Thread.sleep(long)方法不释放锁。
修改ThreadA类如下:
public class ThreadA extends Thread{
private ThreadB threadB;
public ThreadA(ThreadB threadB){
this.threadB = threadB;
}
@Override
public void run() {
synchronized(threadB){
threadB.start();
try {
//Thread.sleep(6000);
//Thread.sleep()方法不释放锁
threadB.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
执行结果如下:
由于线程ThreadA释放了ThreadB的锁,所以线程ThreadC可以调用线程ThreadB的同步方法synchronized public void bService()。
这个实验也说明join(long)方法具有释放锁的功能。
方法join()后面的代码提前运行:出现意外
如果不注意使用join(long)方法的使用,就会掉进“陷进”里。创建如下代码:
public class ThreadA extends Thread{
private ThreadB threadB;
public ThreadA(ThreadB threadB){
this.threadB = threadB;
}
@Override
public void run() {
synchronized(threadB){
System.out.println("begin A ThreadName="+Thread.currentThread().getName()+" "+System.currentTimeMillis());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end A ThreadName="+Thread.currentThread().getName()+" "+System.currentTimeMillis());
}
}
}
public class ThreadB extends Thread{
@Override
synchronized public void run() {
System.out.println("begin B ThreadName="+Thread.currentThread().getName()+" "+System.currentTimeMillis());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end B ThreadName="+Thread.currentThread().getName()+" "+System.currentTimeMillis());
}
}
public class Run {
public static void main(String[] args) throws InterruptedException {
ThreadB threadB = new ThreadB();
ThreadA threadA = new ThreadA(threadB);
threadA.start();
threadB.start();
threadB.join(2000);
System.out.println("main end "+System.currentTimeMillis());
}
}
执行结果如下:
或
为什么会出现不同的结果呢?
为了查看join方法在Run类中执行的时机,创建如下代码:
public class RunFirst {
public static void main(String[] args) {
ThreadB threadB = new ThreadB();
ThreadA threadA = new ThreadA(threadB);
threadA.start();
threadB.start();
System.out.println(" main end "+System.currentTimeMillis());
}
}
执行结果如下:
第一次运行结果:
第二次运行结果:
通过多次运行RunFirst类后,可以发现一个规律:main end往往都是第一个打印的。所以可以完全确定地得出一个结论:方法join(2000)大部分是先执行的,也就是先抢到ThreadB的锁,然后快速释放。
而执行Run类就会出现一些不同的结果。先看下面:
1、b.join(2000)方法先抢到B的锁,然后将B锁进行释放。
2、ThreadA抢到锁,打印ThreadA begin并且Thread.sleep(5000)。
3、ThreadA打印ThreadA end。
4、这时join(2000)和ThreadB争抢锁,而join(2000)再次获得锁,发现时间已过,释放锁后打印main end。
5、ThreadB获得锁,并且Thread.sleep(5000)。
6、5秒后打印ThreadB end。
再看下图
1、 join(2000)方法抢到B锁,然后马上释放。
2、 ThreadB抢到锁,打印ThreadB begin并且Thread.sleep(5000).
3、 5秒后打印ThreadB end,释放锁。
4、 这时join(2000)和ThreadA争抢锁,ThreadA获得锁,打印ThreadA begin并且Thread.sleep(5000)。
5、 5秒后打印ThreadA end。
6、 最后打印main end。