面试官问我怎么实现多个线程顺序执行,我一连说5种方法!

前言

昨天晚上面试一家公司,问的问题都比较基础,就想考考基础扎不扎实。 其中有一个问题是老掉牙 的怎么实现多线程顺序执行~ 首先要理解多线程的几个生命周期,当调用Thread的start()方法,不代表这个线程就立刻执行了,而是这个线程进入了就绪状态,要执行的话还需要由CPU调度才能进入运行状态,其余的状态就不多说了……

声明: 本文示例参照了

让线程按顺序执行8种方法

让多线程按顺序执行的几种方法

一、Thread的join()方法

当然,thread的join方法实现多线程顺序执行有两种方式,一种是在子线程内部调用join()方法,另一种是直接在主线程调用join()方法;写法上是主线程上调用join()更直观,谁先执行谁后执行一目了然。子线程内部调用的话,相对的要注意调用的顺序。

1.子线程内部调用join()

public class ThreadJoinDemo {

public static void main(String[] args) throws InterruptedException {

final Thread thread1 = new Thread(new Runnable() {

@Override

public void run() {

System.out.println("打开冰箱!");

}

});

final Thread thread2 = new Thread(new Runnable() {

@Override

public void run() {

try {

thread1.join();

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println("拿出一瓶牛奶!");

}

});

final Thread thread3 = new Thread(new Runnable() {

@Override

public void run() {

try {

thread2.join();

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println("关上冰箱!");

}

});

//下面三行代码顺序可随意调整,程序运行结果不受影响,因为我们在子线程中通过“join()方法”已经指定了运行顺序(还有一个原因就是之前说的多线程的生命周期中有就绪状态,需要等待cpu调度,start并不意味着运行状态)。

thread3.start();

thread2.start();

thread1.start();

}

}

运行结果

打开冰箱!

拿出一瓶牛奶!

关上冰箱!

2.在主线程中通过join()方法指定顺序

public class ThreadMainJoinDemo {

public static void main(String[] args) throws InterruptedException {

final Thread thread1 = new Thread(new Runnable() {

@Override

public void run() {

System.out.println("打开冰箱!");

}

});

final Thread thread2 = new Thread(new Runnable() {

@Override

public void run() {

System.out.println("拿出一瓶牛奶!");

}

});

final Thread thread3 = new Thread(new Runnable() {

@Override

public void run() {

System.out.println("关上冰箱!");

}

});

thread1.start();

thread1.join();

thread2.start();

thread2.join();

thread3.start();

}

}

运行结果

打开冰箱!

拿出一瓶牛奶!

关上冰箱!

注意: 这里得控制好顺序了,为什么要控制顺序呢,跟join()方法的源码有关,一起看看吧:

public final void join() throws InterruptedException {

join(0);

}

查看Thread源码可知,join()方法内部调用了join(0); 进入如下方法,内部实现是调用了Object的wait(0),根据调用了join()方法的线程实例的存活状态,不断的循环判断是否让获得cpu资源的线程进入等待状态。

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;

}

}

}

通过join()方法的源码,其实可以自己去利用wait()实现如何利用wait()完成线程顺序执行,这里不赘述。

二、使用jdk5推出的线程池Executors创建单例的方法

public class ThreadPoolDemo {

static ExecutorService executorService = Executors.newSingleThreadExecutor();

public static void main(String[] args) {

final Thread thread1 = new Thread(new Runnable() {

@Override

public void run() {

System.out.println("打开冰箱!");

}

});

final Thread thread2 =new Thread(new Runnable() {

@Override

public void run() {

System.out.println("拿出一瓶牛奶!");

}

});

final Thread thread3 = new Thread(new Runnable() {

@Override

public void run() {

System.out.println("关上冰箱!");

}

});

executorService.submit(thread1);

executorService.submit(thread2);

executorService.submit(thread3);

executorService.shutdown(); //使用完毕记得关闭线程池

}

}

下面进入j.u.c包下的并发类,分水岭。。。

三、CountDownLatch(倒计数)类

CountDownLatch类的特点就是可以初始化设置一个state的值,每次调用countDown()方法进行减一,如果state没有减小到0就会被await()方法一直阻塞。利用CountDownLatch方法可以实现和join()方法子线程运用一样的妙处。当然,CountDownLatch类的缺点是state不能重置,只能初始化一次,意思就是减小到0后,就没用了。。。不能加回去。

public class ThreadCountDownLatchDemo {

private static CountDownLatch countDownLatch1 = new CountDownLatch(1);

private static CountDownLatch countDownLatch2 = new CountDownLatch(1);

public static void main(String[] args) {

final Thread thread1 = new Thread(new Runnable() {

@Override

public void run() {

System.out.println("打开冰箱!");

countDownLatch1.countDown();

}

});

final Thread thread2 = new Thread(new Runnable() {

@Override

public void run() {

try {

countDownLatch1.await();

System.out.println("拿出一瓶牛奶!");

countDownLatch2.countDown();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

});

final Thread thread3 = new Thread(new Runnable() {

@Override

public void run() {

try {

countDownLatch2.await();

System.out.println("关上冰箱!");

} catch (InterruptedException e) {

e.printStackTrace();

}

}

});

//下面三行代码顺序可随意调整,程序运行结果不受影响

thread3.start();

thread1.start();

thread2.start();

}

}

四、CyclicBarrier(回环栅栏)类

这个类对CountDownLatch不能重复计数进行了改进,实现了可重复计数,不用向上面例子那样创建多个CountDownLatch实例,这个只需创建一个实例,设置state的大小就可以了。 当然,CyclicBarrier放在这也是大材小用,CyclicBarrier一般的使用场景是控制一组线程同时开始执行,就像跑步比赛一样,当所有运动员准备就绪才可以开始跑。

import java.util.concurrent.BrokenBarrierException;

import java.util.concurrent.CyclicBarrier;

/**

* @author wwj

* 使用CyclicBarrier(回环栅栏)实现线程按顺序运行

*/

public class CyclicBarrierDemo {

static CyclicBarrier barrier1 = new CyclicBarrier(2);

static CyclicBarrier barrier2 = new CyclicBarrier(2);

public static void main(String[] args) {

final Thread thread1 = new Thread(new Runnable() {

@Override

public void run() {

try {

System.out.println("产品经理规划新需求");

//放开栅栏1

barrier1.await();

} catch (InterruptedException e) {

e.printStackTrace();

} catch (BrokenBarrierException e) {

e.printStackTrace();

}

}

});

final Thread thread2 = new Thread(new Runnable() {

@Override

public void run() {

try {

//放开栅栏1

barrier1.await();

System.out.println("开发人员开发新需求功能");

//放开栅栏2

barrier2.await();

} catch (InterruptedException e) {

e.printStackTrace();

} catch (BrokenBarrierException e) {

e.printStackTrace();

}

}

});

final Thread thread3 = new Thread(new Runnable() {

@Override

public void run() {

try {

//放开栅栏2

barrier2.await();

System.out.println("测试人员测试新功能");

} catch (InterruptedException e) {

e.printStackTrace();

} catch (BrokenBarrierException e) {

e.printStackTrace();

}

}

});

thread3.start();

thread1.start();

thread2.start();

}

}

public class CyclicBarrierDemo {

static class TaskThread extends Thread {

CyclicBarrier barrier;

public TaskThread(CyclicBarrier barrier) {

this.barrier = barrier;

}

@Override

public void run() {

try {

Thread.sleep(1000);

System.out.println(getName() + " 到达栅栏 A");

barrier.await();

System.out.println(getName() + " 冲破栅栏 A");

Thread.sleep(2000);

System.out.println(getName() + " 到达栅栏 B");

barrier.await();

System.out.println(getName() + " 冲破栅栏 B");

} catch (Exception e) {

e.printStackTrace();

}

}

}

public static void main(String[] args) {

int threadNum = 5;

CyclicBarrier barrier = new CyclicBarrier(threadNum, new Runnable() {

@Override

public void run() {

System.out.println(Thread.currentThread().getName() + " 完成最后任务");

}

});

for(int i = 0; i < threadNum; i++) {

new TaskThread(barrier).start();

}

}

}

打印结果

Thread-1 到达栅栏 A

Thread-3 到达栅栏 A

Thread-0 到达栅栏 A

Thread-4 到达栅栏 A

Thread-2 到达栅栏 A

Thread-2 完成最后任务

Thread-2 冲破栅栏 A

Thread-1 冲破栅栏 A

Thread-3 冲破栅栏 A

Thread-4 冲破栅栏 A

Thread-0 冲破栅栏 A

Thread-4 到达栅栏 B

Thread-0 到达栅栏 B

Thread-3 到达栅栏 B

Thread-2 到达栅栏 B

Thread-1 到达栅栏 B

Thread-1 完成最后任务

Thread-1 冲破栅栏 B

Thread-0 冲破栅栏 B

Thread-4 冲破栅栏 B

Thread-2 冲破栅栏 B

Thread-3 冲破栅栏 B

五、Sephmore(信号量)类

Sephmore类它是类似于回环栅栏,但是又不大一样,它里面的计数值叫许可,可以初始化许可的值,每个线程实例可以获取指定的许可个数,当这个线程执行完毕可以归还许可;让其他线程继续去获取许可执行线程内容;当获取不到许可的线程就会阻塞住,等待其他线程执行完,释放了许可,才能够往下执行。

acquire():当前线程尝试去阻塞的获取1个许可证,此过程是阻塞的,当前线程获取了1个可用的许可证,则会停止等待,继续执行。

release():当前线程释放1个可用的许可证。

除了这两个方法外,Sephmore还有获取多个许可的方法,可以自行去查看源码。 当然,Sephmore放在这里用确实是大材小用了,它一般的使用场景都是类似于数据库连接池的并发控制,取完后归还一样。

import java.util.concurrent.Semaphore;

/**

* 使用Sephmore(信号量)实现线程按顺序运行

*/

public class SemaphoreDemo {

private static Semaphore semaphore1 = new Semaphore(1);

private static Semaphore semaphore2 = new Semaphore(1);

public static void main(String[] args) {

final Thread thread1 = new Thread(new Runnable() {

@Override

public void run() {

System.out.println("产品经理规划新需求");

semaphore1.release();

}

});

final Thread thread2 = new Thread(new Runnable() {

@Override

public void run() {

try {

semaphore1.acquire();

System.out.println("开发人员开发新需求功能");

semaphore2.release();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

});

Thread thread3 = new Thread(new Runnable() {

@Override

public void run() {

try {

semaphore2.acquire();

thread2.join();

semaphore2.release();

System.out.println("测试人员测试新功能");

} catch (InterruptedException e) {

e.printStackTrace();

}

}

});

System.out.println("早上:");

System.out.println("测试人员来上班了...");

thread3.start();

System.out.println("产品经理来上班了...");

thread1.start();

System.out.println("开发人员来上班了...");

thread2.start();

}

}

执行结果

早上:

测试人员来上班了...

产品经理来上班了...

开发人员来上班了...

产品经理规划新需求

开发人员开发新需求功能

测试人员测试新功能

六、其实还有很多

比如更高大上的AQS,锁,ReentrantLock配合Condition,涉及了两个队列,我比较头疼,一个是虚拟同步双向队列,一个是Condition的队列,只是我不太熟,理解不深刻,不敢拿出来卖,不过能使用后面三种基本上是对并发编程有一定理解了的。。。。。 有兴趣可以去看看,这个可重入锁弄懂可是很加分的