面试题12解析-多线程之间的协调示例
2017-06-02 16:44
281 查看
Java面试那些事儿 2017-06-02 00:07
题目:多线程之间需要等待协调,才能完成某种工作,问怎么设计这种协调方案?如:子线程循环10次,接着主线程循环100,接着又回到子线程循环10次,接着再回到主线程又循环100,如此循环50次。
这个题目主要考查多线程之间的协调,关于线程的协调与通信怎么处理见面试题10。
在并发编程中经常会使用到一些并发工具类,来对线程的并发量、执行流程、资源依赖等进行控制。这里我们主要探讨三个经常使用的并发工具类:CountDownLatch,CyclicBarrier和Semaphore。
CountDownLatch
从CountDownLatch的字面意思就可以体现出其设计模型,countdown在英语里具有倒计时的(倒数)意思,Latch就是门闩的意思。CountDownLatch的构造函数接受一个int值作为计数器的初始值N,当程序调用countDown()的时候,N便会减1(体现出了倒数的意义),当N值减为0的时候,阻塞在await()的线程便会唤醒,继续执行。这里通过一个例子来说明其应用场景。
假设我们主线程需要创建5个工作线程来分别执行5个任务,主线程需要等待5个任务全部完成后才会进行后续操作,那么我们就可以声明N=5的CountDownLatch,来进行控制。
代码如下:
public class CountDownLatchDemo {
private static final CountDownLatch countDownLatch = new CountDownLatch(5);
public static void main(String[]args)throws InterruptedException{
//循环创建5个工作线程
for(int ix=0;ix!=5;ix++){
new Thread(new Runnable(){
public void run(){
try{
System.out.println(Thread.currentThread().getName()+"start");
Thread.sleep(1000);
countDownLatch.countDown();
System.out.println(Thread.currentThread().getName()+"stop");
}catch(InterruptedExceptionex){
}
}
},"Task-Thread-"+ix).start();
Thread.sleep(500);
}
//主线程等待所有任务完成
countDownLatch.await();
System.out.println("Alltaskhascompleted.");
}
}
运行结果:
Task-Thread-0 start
Task-Thread-1start
Task-Thread-0stop
Task-Thread-2start
Task-Thread-1stop
Task-Thread-3start
Task-Thread-2stop
Task-Thread-4start
Task-Thread-3stop
Task-Thread-4stop
Alltaskhascompleted.
在主线程创建了5个工作线程后,就会阻塞在countDownLatch.await(),等待5个工作线程全部完成任务后返回。任务的执行顺序可能会不同,但是任务完成的Log一定会在最后显示。CountDownLatch通过计数器值的控制,实现了允许一个或多个线程等待其他线程完成操作的并发控制。
CyclicBarrier
CyclicBarrier就字面意思是可循环的屏障,其体现了两个特点,可循环和屏障。调用CyclicBarrier的await()方法便是在运行线程中插入了屏障,当线程运行到这个屏障时,便会阻塞在await()方法中,直到等待所有线程运行到屏障后,才会返回。CyclicBarrier的构造函数同样接受一个int类型的参数,表示屏障拦截线程的数目。另一个特点循环便是体现出出了CyclicBarrier与CountDownLatch不同之处了,CyclicBarrier可以通过reset()方法,将N值重置,循环使用,而CountDownLatch的计数器是不能重置的。此外,CyclicBarrier还提供了一个更高级的用法,允许我们设置一个所有线程到达屏障后,便立即执行的Runnable类型的barrierAction(注意:barrierAction不会等待await()方法的返回才执行,是立即执行!)机会,这里我们通过以下代码来测试一下CyclicBarrier的特性。
代码如下:
public class CyclicBarrierDemo {
private final static CyclicBarrier cyclicBarrier = new CyclicBarrier(5,
new MyBarrierAction());
private final static AtomicInteger atclx = new AtomicInteger(1);
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + "start");
Thread.sleep(atclx.getAndIncrement() * 1000);
cyclicBarrier.await();
System.out.println(Thread.currentThread().getName() + "stop");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}, "Thread-" + i).start();
}
}
public static class MyBarrierAction implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("MyBarrierActioniscall.");
}
}
}
运行结果:
Thread-0start
Thread-1start
Thread-2start
Thread-3start
Thread-4start
MyBarrierActioniscall.
Thread-4stop
Thread-0stop
Thread-1stop
Thread-2stop
Thread-3stop
根据运行结果,我们可以看到一下几点:
首先在线程没有调用够N次cyclicBarrier.await()时,所有线程都会阻塞在cyclicBarrier.await()上,也就是说必须N个线程都到达屏障,所有线程才会越过屏障继续执行。
验证了BarrierAction的执行时机是所有阻塞线程都到达屏障之后,并且BarrierAction优先执行后,所有线程才会从await()方法返回,继续执行。
Semaphore
Semaphore信号量并发工具类,其提供了aquire()和release()方法来进行并发控制。Semaphore一般用于资源限流,限量的工作场景,例如数据库连接控制。假设数据库的最大负载在10个连接,而现在有100个客户端想进行数据查询,显然我们不能让100个客户端同时连接上来,找出数据库服务的崩溃。那么我们可以创建10张令牌,想要连接数据库的客户端,都必须先尝试获取令牌(Semaphore.aquire()),当客户端获取到令牌后便可以进行数据库连接,并在完成数据查询后归还令牌(Semaphore.release()),这样就能保证同时连接数据库的客户端不超过10个,因为只有10张令牌,这里给出该场景的模拟代码。
代码如下:
public class SemaphoreDemo {
private static final Semaphore semaphoreToken = new Semaphore(10);
public static void main(String[] args) {
for (int ix = 0; ix != 100; ix++) {
new Thread(new Runnable() {
public void run() {
try {
semaphoreToken.acquire();
System.out.println("select*fromxxx");
semaphoreToken.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
也许有同学会问,aquire()函数获取许可证的顺序和调用的先后顺序有关系吗,也就是说该例子中客户端是否是排队获取令牌的?答案不是,因为Semaphore默认是非公平的,当然其构造函数提供了设置为公平信号量的参数。
本题答案
public class Question12 {
public static void main(String[] args) throws InterruptedException {
final Object object = new Object();
new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 50; i++) {
synchronized (object) {
for (int j = 0; j < 10; j++) {
System.out.println("SubThread:" + (j + 1));
}
object.notify();
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}).start();
for (int i = 0; i < 50; i++) {
synchronized (object) {
// 主线程让出锁,等待子线程唤醒
object.wait();
for (int j = 0; j < 100; j++) {
System.out.println("MainThread:" + (j + 1));
}
object.notify();
}
}
}
}
这个题目例子比较多,最好自己去分析跑一下代码,结合代码来分析理解这些概念。
记得关注我们哦,这里全部都是干货!!!
题目:多线程之间需要等待协调,才能完成某种工作,问怎么设计这种协调方案?如:子线程循环10次,接着主线程循环100,接着又回到子线程循环10次,接着再回到主线程又循环100,如此循环50次。
这个题目主要考查多线程之间的协调,关于线程的协调与通信怎么处理见面试题10。
在并发编程中经常会使用到一些并发工具类,来对线程的并发量、执行流程、资源依赖等进行控制。这里我们主要探讨三个经常使用的并发工具类:CountDownLatch,CyclicBarrier和Semaphore。
CountDownLatch
从CountDownLatch的字面意思就可以体现出其设计模型,countdown在英语里具有倒计时的(倒数)意思,Latch就是门闩的意思。CountDownLatch的构造函数接受一个int值作为计数器的初始值N,当程序调用countDown()的时候,N便会减1(体现出了倒数的意义),当N值减为0的时候,阻塞在await()的线程便会唤醒,继续执行。这里通过一个例子来说明其应用场景。
假设我们主线程需要创建5个工作线程来分别执行5个任务,主线程需要等待5个任务全部完成后才会进行后续操作,那么我们就可以声明N=5的CountDownLatch,来进行控制。
代码如下:
public class CountDownLatchDemo {
private static final CountDownLatch countDownLatch = new CountDownLatch(5);
public static void main(String[]args)throws InterruptedException{
//循环创建5个工作线程
for(int ix=0;ix!=5;ix++){
new Thread(new Runnable(){
public void run(){
try{
System.out.println(Thread.currentThread().getName()+"start");
Thread.sleep(1000);
countDownLatch.countDown();
System.out.println(Thread.currentThread().getName()+"stop");
}catch(InterruptedExceptionex){
}
}
},"Task-Thread-"+ix).start();
Thread.sleep(500);
}
//主线程等待所有任务完成
countDownLatch.await();
System.out.println("Alltaskhascompleted.");
}
}
运行结果:
Task-Thread-0 start
Task-Thread-1start
Task-Thread-0stop
Task-Thread-2start
Task-Thread-1stop
Task-Thread-3start
Task-Thread-2stop
Task-Thread-4start
Task-Thread-3stop
Task-Thread-4stop
Alltaskhascompleted.
在主线程创建了5个工作线程后,就会阻塞在countDownLatch.await(),等待5个工作线程全部完成任务后返回。任务的执行顺序可能会不同,但是任务完成的Log一定会在最后显示。CountDownLatch通过计数器值的控制,实现了允许一个或多个线程等待其他线程完成操作的并发控制。
CyclicBarrier
CyclicBarrier就字面意思是可循环的屏障,其体现了两个特点,可循环和屏障。调用CyclicBarrier的await()方法便是在运行线程中插入了屏障,当线程运行到这个屏障时,便会阻塞在await()方法中,直到等待所有线程运行到屏障后,才会返回。CyclicBarrier的构造函数同样接受一个int类型的参数,表示屏障拦截线程的数目。另一个特点循环便是体现出出了CyclicBarrier与CountDownLatch不同之处了,CyclicBarrier可以通过reset()方法,将N值重置,循环使用,而CountDownLatch的计数器是不能重置的。此外,CyclicBarrier还提供了一个更高级的用法,允许我们设置一个所有线程到达屏障后,便立即执行的Runnable类型的barrierAction(注意:barrierAction不会等待await()方法的返回才执行,是立即执行!)机会,这里我们通过以下代码来测试一下CyclicBarrier的特性。
代码如下:
public class CyclicBarrierDemo {
private final static CyclicBarrier cyclicBarrier = new CyclicBarrier(5,
new MyBarrierAction());
private final static AtomicInteger atclx = new AtomicInteger(1);
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + "start");
Thread.sleep(atclx.getAndIncrement() * 1000);
cyclicBarrier.await();
System.out.println(Thread.currentThread().getName() + "stop");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}, "Thread-" + i).start();
}
}
public static class MyBarrierAction implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("MyBarrierActioniscall.");
}
}
}
运行结果:
Thread-0start
Thread-1start
Thread-2start
Thread-3start
Thread-4start
MyBarrierActioniscall.
Thread-4stop
Thread-0stop
Thread-1stop
Thread-2stop
Thread-3stop
根据运行结果,我们可以看到一下几点:
首先在线程没有调用够N次cyclicBarrier.await()时,所有线程都会阻塞在cyclicBarrier.await()上,也就是说必须N个线程都到达屏障,所有线程才会越过屏障继续执行。
验证了BarrierAction的执行时机是所有阻塞线程都到达屏障之后,并且BarrierAction优先执行后,所有线程才会从await()方法返回,继续执行。
Semaphore
Semaphore信号量并发工具类,其提供了aquire()和release()方法来进行并发控制。Semaphore一般用于资源限流,限量的工作场景,例如数据库连接控制。假设数据库的最大负载在10个连接,而现在有100个客户端想进行数据查询,显然我们不能让100个客户端同时连接上来,找出数据库服务的崩溃。那么我们可以创建10张令牌,想要连接数据库的客户端,都必须先尝试获取令牌(Semaphore.aquire()),当客户端获取到令牌后便可以进行数据库连接,并在完成数据查询后归还令牌(Semaphore.release()),这样就能保证同时连接数据库的客户端不超过10个,因为只有10张令牌,这里给出该场景的模拟代码。
代码如下:
public class SemaphoreDemo {
private static final Semaphore semaphoreToken = new Semaphore(10);
public static void main(String[] args) {
for (int ix = 0; ix != 100; ix++) {
new Thread(new Runnable() {
public void run() {
try {
semaphoreToken.acquire();
System.out.println("select*fromxxx");
semaphoreToken.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
也许有同学会问,aquire()函数获取许可证的顺序和调用的先后顺序有关系吗,也就是说该例子中客户端是否是排队获取令牌的?答案不是,因为Semaphore默认是非公平的,当然其构造函数提供了设置为公平信号量的参数。
本题答案
public class Question12 {
public static void main(String[] args) throws InterruptedException {
final Object object = new Object();
new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 50; i++) {
synchronized (object) {
for (int j = 0; j < 10; j++) {
System.out.println("SubThread:" + (j + 1));
}
object.notify();
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}).start();
for (int i = 0; i < 50; i++) {
synchronized (object) {
// 主线程让出锁,等待子线程唤醒
object.wait();
for (int j = 0; j < 100; j++) {
System.out.println("MainThread:" + (j + 1));
}
object.notify();
}
}
}
}
这个题目例子比较多,最好自己去分析跑一下代码,结合代码来分析理解这些概念。
记得关注我们哦,这里全部都是干货!!!
相关文章推荐
- 多线程之间的协调示例
- Android 开发中用到的几个多线程解析(代码示例)
- Android ApiDemos示例解析(12):App->Activity->Redirection
- 12 多线程 线程之间的局部变量与成员变量 | sleep
- c++多线程之死锁的发生的情况解析(包含两个归纳,6个示例)
- Java 集合系列12之 TreeMap详细介绍(源码解析)和使用示例
- C#与.NET程序员面试宝典 2.2.5 面试题12:如何解决因共享组件而导致的应用程序之间的冲突
- Java 集合系列12之 TreeMap详细介绍(源码解析)和使用示例
- Java 集合系列12之 TreeMap详细介绍(源码解析)和使用示例
- Java 集合系列12之 TreeMap详细介绍(源码解析)和使用示例
- Java 集合系列12之 TreeMap详细介绍(源码解析)和使用示例
- Android ApiDemos示例解析(167):Views->Layouts->TableLayout->12. Cell Spanning
- Android 开发中用到的几个多线程解析(代码示例)
- java面试题-用JAVA中的多线程示例火车站售票问题
- Python多线程编程,线程之间的协调
- Android 开发中用到的几个多线程解析(代码示例)
- Android ApiDemos示例解析(12):App->Activity->Redirection
- java面试题-用JAVA中的多线程示例银行取款问题标准版
- Android ApiDemos示例解析(7) Activity之间跳转 --又又哥
- 面试题10解析—多线程通讯与协作