一、Thread.join()
若一个线程A执行了thread.join()语句,含义为:线程A需要等待thread线程终止//销毁之后才会从join()方法返回。
方法:
join();
join(long millis);
join(long millis, int nanos);
如,两个线程顺序执行,有两个线程A、B,线程B需要在线程A打印完毕后才执行打印操作:
private static void printInOrder() {
Thread A = new Thread(new Runnable() {
@Override
public void run() {
printData("A");//打印A线程数据
}
});
Thread B = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("B开始等待A");
try {
A.join(); //线程A执行结束,线程B从join()方法返回
} catch (InterruptedException e) {
e.printStackTrace();
}
printData("B");//打印B线程数据
}
});
B.start();
A.start();
}
上述代码会先打印线程A数据,待线程A执行结束,再打印线程B数据。
这里其实涉及到等待/通知机制(等待前驱线程结束,接收前驱线程的结束通知),具体可查看Thread.join()方法源码。
二、等待/通知机制
1、等待/通知方法是任意java对象都具备的,这些方法都定义在所有对象的超类Object上,如下:
notify();
notifyAll();
wait();
wait(long);
wait(long, int);
线程A调用了对象O的wait()方法,线程B调用了对象O的notify()或notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续的操作。两个线程通过对象O完成交互,对象O上的wait()和notify()就像开关一样,用来完成等待方和通知方之间的交互工作。
使用wait()/notify()时需要先对调用对象加锁。
调用了wait()方法之后,线程状态从runnable变为waiting,并将当前线程放在对象的等待队列中。
调用了notify()方法之后,等待线程不会从wait()返回,需要调用notify()的线程释放锁之后,等待线程才有机会从wait()返回。从wait()方法返回的前提是等待线程获得了调用对象的锁。
如,两个线程有序交叉执行,线程A和线程B交替打印各自的数据:
private static void printInTurn(){
int count = 1;
Object lock = new Object();
Thread A = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
while(count<=100) {
try {
System.out.println("进入线程A,count: "+count);
lock.notify();
System.out.println("线程A唤醒lock,线程B在线程A释放锁后从wait()返回. 线程A print count: "+count);
count++;
lock.wait();
System.out.println("线程A等待,share count value is "+count);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
Thread B = new Thread(new Runnable(){
@Override
public void run() {
synchronized (lock) {
while(count<=100) {
try {
System.out.println("进入线程B, count: "+count);
lock.notify();
System.out.println("线程B唤醒lock,线程A在线程B释放锁后从wait()方法返回,线程B print count:"+count);
count++;
lock.wait();
System.out.println("线程B等待,share count value is "+count);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
A.start();
TimeUnit.SECONDS.sleep(10);
B.start();
}
上述代码会依次交替打印100以内的奇偶数,其中线程A打印奇数,线程B打印偶数。
三、java中的并发工具类
java并发包中的并发工具类有CountDownLatch、CyclicBarrier、Semaphore。
1、CountDownLatch允许一个或多个线程等待其他线程执行完操作。
CountDownLatch构造函数传入一个int型参数N,表示需要等待N个点完成,这里N个点可以表示N个线程,也可以表示1个线程里的N个步骤。执行countDown(),N会减1;await()方法会阻塞当前线程,直到N变为0。另外,如果某个线程处理较慢,不能让等待线程一直等待,使用await(long time, TimeUnit unit),表示在等待特定时间后,就不会阻塞当前线程。
如,线程D需要等待线程A,B,C同步执行完毕后再执行:
private static void countDownLatchTest(){
CountDownLatch countDownLatch = new CountDownLatch(3);
Thread D = new Thread(new Runnable(){
@Override
public void run() {
System.out.println("线程D等待其他三个线程");
try {
countDownLatch.await();
System.out.println("其他三个线程执行完毕,D开始执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
D.start();
for(char name='A';name<='C';name++){
final String n = String.valueOf(threadName);
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("执行线程:"+n);
countDownLatch.countDown();
}
}).start();
}
}
2、CyclicBarrier
让一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。
比如,三个线程A,B,C,都需要做一些准备工作,待这三个线程都准备完毕后,它们才会在同一时间继续执行:
private static void CyclicBarrierTest() {
CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
for (char name='A'; name<= 'C'; name++) {
final String n= String.valueOf(name);
new Thread(new Runnable() {
@Override
public void run() {
int prepareTime = (int)(Math.random()*1000);
System.out.println(n+ " prepare time:" + prepareTime);
try {
Thread.sleep(prepareTime);
} catch (Exception e) {
e.printStackTrace();
}
try {
System.out.println(n+ " is prepared, waiting for others");
cyclicBarrier.await(); //当前线程准备完毕,等待其他线程准备完成
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(n+ " starts running"); //所有线程准备完成,这些线程开始同时执行
}
}).start();
}
}