一、方法join
在很多情况下,主线程创建并启动子线程,如果子线程中需要进行大量的耗时运算,主线程往往将早于子线程结束之前结束。如果主线程想等待子线程执行完成之后在结束,比如子线程执行一个方法,主线程要取得这个方法的返回值,就要用到join()方法了。方法join的作用是等待线程对象销毁。线程Thread除了提供join方法之外,还提供了join(long millis)和join(long millis,int nanos)两个具有超时特性的方法。这两个超时方法表示,如果线程thread在给定的超时时间内没有销毁,那么将从该超时方法中返回。
1、简单demo
public class MyThread extends Thread {
@Override
public void run(){
try {
int secondValue=(int)(Math.random()*10000);
System.out.println(secondValue);
Thread.sleep(secondValue);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
MyThread threadTest=new MyThread();
threadTest.start();
//Thread.sleep(?)
threadTest.join();
System.out.println("我想当threadTest对象执行完毕后我再执行,我做到了");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
输出结果如下:
方法join的作用是使所属的线程对象x正常执行run方法中的任务,而使当前线程z进入阻塞状态,等待线程x销毁后再继续执行线程z后面的代码。
方法join具有使线程排队运行的作用,有些类似同步的效果。join与synchronized的区别是:join在内部使用wait()方法进行等待,而synchronized关键字使用的是“对象监视器”原理作为同步。
2、使线程按顺序执行
之前说过线程调用具有“无序性”的特点,那么 有三个线程T1,T2、T3,怎么确保它们按顺序执行?
public class Join {
public static void main(String[] args) {
Thread previous=Thread.currentThread();
for (int i = 0; i < 10; i++) {
//每个线程拥有前一个线程的引用,需要等待前一个线程终止,才能从等待中返回
Thread thread=new Thread(new Domino(previous),String.valueOf(i));
thread.start();
previous=thread;
}
}
//静态内部类
static class Domino implements Runnable{
private Thread thread;
public Domino(Thread thread){
this.thread=thread;
}
public void run(){
try {
thread.join();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" terminate.");
}
}
}
上述代码,创建类10个线程,编号0~9,每个线程调用前一个线程的join方法,也就是线程0结束了,线程1才能从join方法中返回,而线程0需要等待main线程结束。每个线程终止的前提是前驱线程的终止,每个线程等待前驱线程终止后,才从join方法返回,本质上仍是等待/通知机制(等待前驱线程结束,结束前驱线程结束通知)
join方法源码如下:
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;
}
}
}
二、ThreadLocal
之前【多线程】volatile关键字中提及了内存模型具有原子性、可见性、有序性。
Thread顾名思义可以理解为线程本地变量,是一个以ThreadLocal对象为键,任意对象为值的存储结构。这个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值。换句话说就是每个线程绑定自己的值。每个线程往这个ThreadLocal中读写是线程隔离的,互相之间不会影响。
通过set(T)方法来设置一个值,在当前线程下通过get方法获取到原先设定的值。
public class Run {
public static ThreadLocal t1=new ThreadLocal();
public static void main(String[] args) {
if (t1.get()==null) {
System.out.println("从未放过值");
t1.set(" 我的值 ");
}
System.out.println(t1.get());
}
}
接下来验证线程的隔离性。
public class Tools {
/**
* 验证线程变量的隔离性
*/
public static ThreadLocal t1=new ThreadLocal();
}
public class ThreadA extends Thread {
@Override
public void run(){
try {
for (int i = 0; i < 100; i++) {
Tools.t1.set("ThreadA"+(i+1));
System.out.println("ThreadA get Value="+Tools.t1.get());
Thread.sleep(200);
}
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
public class ThreadB extends Thread {
@Override
public void run(){
try {
for (int i = 0; i < 100; i++) {
Tools.t1.set("ThreadB"+(i+1));
System.out.println("ThreadB get Value="+Tools.t1.get());
Thread.sleep(200);
}
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
public class Run {
public static void main(String[] args) {
try {
ThreadA a=new ThreadA();
ThreadB b=new ThreadB();
a.start();
b.start();
for (int i = 0; i < 100; i++) {
Tools.t1.set("Main"+(i+1));
System.out.println("Main get Value="+Tools.t1.get());
Thread.sleep(200);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
虽然3个线程都向t1对象中set数据值,但每个线程都能取值自己对应的数据。(t1对象是单例的)
ThreadLocal和synchronized都能保证线程安全,synchronized是用时间换空间,synchronized是用空间换时间。