CountDownLatch (同步计数器)
Latch闭锁的意思,是一种同步的工具类。类似于一扇门:在闭锁到达结束状态之前,这扇门一直是关闭着的,不允许任何线程通过,当到达结束状态时,这扇门会打开并允许所有的线程通过。且当门打开了,就永远保持打开状态。
作用:可以用来确保某些活动直到其他活动都完成后才继续执行。
使用场景:
1、例如我们上例中所有人都到达饭店然后吃饭;
2、某个操作需要的资源初始化完毕
3、某个服务依赖的线程全部开启等等…
CountDowmLatch是一种灵活的闭锁实现,包含一个计数器,该计算器初始化为一个正数,表示需要等待事件的数量。countDown方法递减计数器,表示有一个事件发生,而await方法等待计数器到达0,表示所有需要等待的事情都已经完成。
不推荐的做法:
package threadTest;
public class Snippet {
// volatile做同步,可能因为并发造成一定的问题,毕竟i--不是原子操作
private static volatile int i = 3;
// 使用synchronized做同步
private static int n = 3;
/**
* 模拟爸爸去饭店
*/
public static void fatherToRes()
{
System.out.println("爸爸步行去饭店需要3小时。");
}
/**
* 模拟我去饭店
*/
public static void motherToRes()
{
System.out.println("妈妈挤公交去饭店需要2小时。");
}
/**
* 模拟妈妈去饭店
*/
public static void meToRes()
{
System.out.println("我乘地铁去饭店需要1小时。");
}
/**
* 模拟一家人到齐了
*/
public static void togetherToEat()
{
System.out.println("一家人到齐了,开始吃饭");
}
public static void main(String[] args)
{
new Thread()
{
public void run()
{
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
fatherToRes();
synchronized (Snippet.class) {
n--;
}
};
}.start();
new Thread()
{
public void run()
{
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
motherToRes();
synchronized (Snippet.class) {
n--;
}
};
}.start();
new Thread()
{
public void run()
{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
meToRes();
synchronized (Snippet.class) {
n--;
}
};
}.start();
// 在主线程使用了一个忙等,一直等待所有人到达。忙等这样的代码对于CPU的消耗太巨大了
// while (i != 0);
while (n != 0);
togetherToEat();
}
}
推荐使用CountDownLatch的做法:
package threadTest;
import java.util.concurrent.CountDownLatch;
public class Snippet {
private static CountDownLatch latch = new CountDownLatch(3);
/**
* 模拟爸爸去饭店
*/
public static void fatherToRes()
{
System.out.println("爸爸步行去饭店需要3小时。");
}
/**
* 模拟我去饭店
*/
public static void motherToRes()
{
System.out.println("妈妈挤公交去饭店需要2小时。");
}
/**
* 模拟妈妈去饭店
*/
public static void meToRes()
{
System.out.println("我乘地铁去饭店需要1小时。");
}
/**
* 模拟一家人到齐了
*/
public static void togetherToEat()
{
System.out.println("一家人到齐了,开始吃饭");
}
public static void main(String[] args) throws InterruptedException
{
new Thread()
{
public void run()
{
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
fatherToRes();
latch.countDown();// 调用一次,计数减1
};
}.start();
new Thread()
{
public void run()
{
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
motherToRes();
latch.countDown();
};
}.start();
new Thread()
{
public void run()
{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
meToRes();
latch.countDown();
};
}.start();
// latch会一直阻塞
latch.await();
togetherToEat();
}
}
当然,join也能实现:
package threadTest;
public class Snippet {
/**
* 模拟爸爸去饭店
*/
public static void fatherToRes()
{
System.out.println("爸爸步行去饭店需要3小时。");
}
/**
* 模拟我去饭店
*/
public static void motherToRes()
{
System.out.println("妈妈挤公交去饭店需要2小时。");
}
/**
* 模拟妈妈去饭店
*/
public static void meToRes()
{
System.out.println("我乘地铁去饭店需要1小时。");
}
/**
* 模拟一家人到齐了
*/
public static void togetherToEat()
{
System.out.println("一家人到齐了,开始吃饭");
}
public static void main(String[] args) throws InterruptedException
{
Thread thread1 = new Thread()
{
public void run()
{
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
fatherToRes();
};
};
Thread thread2 = new Thread()
{
public void run()
{
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
motherToRes();
};
};
Thread thread3 = new Thread()
{
public void run()
{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
meToRes();
};
};
thread1.start();
thread2.start();
thread3.start();
thread1.join();
thread2.join();
thread3.join();
togetherToEat();
}
}
输出结果:
参考:http://blog.csdn.net/lmj623565791/article/details/26626391
Semaphore(信号量、互斥)
模拟数据库连接池的实现,假设现在数据库连接池最大连接数为3,当3个连接都分配出去以后,现在有用户继续请求连接,可能的处理:
a、手动抛出异常,用户界面显示,服务器忙,稍后再试
b、阻塞,等待其他连接的释放
从用户体验上来说,更好的选择当然是阻塞,等待其他连接的释放,用户只会觉得稍微慢了一点,并不影响他的操作。
常用例子:
package threadTest;
import java.util.concurrent.Semaphore;
public class Test {
public static void main(String[] args) {
int N = 8; // 工人数
Semaphore semaphore = new Semaphore(5); // 机器数目
for (int i = 0; i < N; i++)
new Worker(i, semaphore).start();
}
static class Worker extends Thread {
private int num;
private Semaphore semaphore;
public Worker(int num, Semaphore semaphore) {
this.num = num;
this.semaphore = semaphore;
}
@Override
public void run() {
try {
semaphore.acquire();
System.out.println("工人" + this.num + "占用一个机器在生产...");
Thread.sleep(2000);
System.out.println("工人" + this.num + "释放出机器");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
执行结果:
模拟线程池例子:
package threadTest;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Semaphore;
/**
* 使用Semaphore模拟数据库链接池的使用
*
* @author zhy
*
*/
public class ConnectPool {
private final List<Conn> pool = new ArrayList<Conn>(3);
private final Semaphore semaphore = new Semaphore(3);// 定义由信号量所允许的最大线程数为3
/**
* 初始化分配3个连接
*/
public ConnectPool() {
pool.add(new Conn());
pool.add(new Conn());
pool.add(new Conn());
}
/**
* 请求分配连接
*
* @return
* @throws InterruptedException
*/
public Conn getConn() throws InterruptedException {
semaphore.acquire();// 请求许可 。要么通过然后将信号量减一,要么一直等下去,直到信号量大于一或超时
Conn c = null;
synchronized (pool) {
c = pool.remove(0);
}
System.out.println(Thread.currentThread().getName() + " get a conn "
+ c);
return c;
}
/**
* 释放连接
*
* @param c
*/
public void release(Conn c) {
pool.add(c);
System.out.println(Thread.currentThread().getName()
+ " release a conn " + c);
semaphore.release();// 释放许可。也就是释放了由信号量守护的资源
}
public static void main(String[] args) {
final ConnectPool pool = new ConnectPool();
/**
* 第一个线程占用1个连接3秒
*/
new Thread() {
public void run() {
try {
Conn c = pool.getConn();
Thread.sleep(3000);
pool.release(c);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
}.start();
/**
* 开启3个线程请求分配连接
*/
for (int i = 0; i < 3; i++) {
new Thread() {
public void run() {
try {
Conn c = pool.getConn();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
}.start();
}
}
private class Conn {
}
}
运行结果:
参考:http://blog.csdn.net/lmj623565791/article/details/26810813
FutureTask (获取线程执行结果)
FutureTask与两个具体的接口相关:
Callable: Runnable接口的一种替代方案,位于java.util.concurrent包下,在它里面也只声明了一个call()方法,可以返回任何结果。
Future:对具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。
单独使用Runnable时:
无法获得返回值
单独使用Callable时:
无法在新线程中(new Thread(Runnable r))使用,只能使用ExecutorService
Thread类只支持Runnable
FutureTask:
实现了Runnable和Future,所以兼顾两者优点
既可以使用ExecutorService,也可以使用Thread
FutureTask是一种可以取消的异步的计算任务。它的计算是通过Callable实现的,它等价于可以携带结果的Runnable
FutureTask是为了弥补Thread的不足而设计的,它可以让程序员准确地知道线程什么时候执行完成并获得到线程执行完成后返回的结果(如果有需要)。
Future 主要定义了5个方法:
1) boolean cancel(boolean mayInterruptIfRunning):试图取消对此任务的执行。如果任务已完成、或已取消,或者由于某些其他原因而无法取消,则此尝试将失败。当调用 cancel 时,如果调用成功,而此任务尚未启动,则此任务将永不运行。如果任务已经启动,则 mayInterruptIfRunning 参数确定是否应该以试图停止任务的方式来中断执行此任务的线程。此方法返回后,对 isDone() 的后续调用将始终返回 true。如果此方法返回 true,则对 isCancelled() 的后续调用将始终返回 true。
2)boolean isCancelled():如果在任务正常完成前将其取消,则返回 true。
3)boolean isDone():如果任务已完成,则返回 true。 可能由于正常终止、异常或取消而完成,在所有这些情况中,此方法都将返回 true。
4)V get()throws InterruptedException,ExecutionException:如有必要,等待计算完成,然后获取其结果。 只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态
5)V get(long timeout,TimeUnit unit) throws InterruptedException,ExecutionException,TimeoutException:如有必要,最多等待为使计算完成所给定的时间之后,获取其结果(如果结果可用)。
一般FutureTask多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。例如:使用FutureTask预先加载一些可能用到资源,然后要用的时候,调用get方法获取(如果资源加载完,直接返回;否则继续等待其加载完成)。
例子:模拟一个会计算账的过程,主线程已经获得其他帐户的总额了,为了不让主线程等待 PrivateAccount类的计算结果的返回而启用新的线程去处理, 并使用 FutureTask对象来监控,这样,主线程还可以继续做其他事情, 最后需要计算总额的时候再尝试去获得privateAccount 的信息。
package threadTest;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
*
* @author Administrator
*
*/
@SuppressWarnings("all")
public class FutureTaskDemo {
public static void main(String[] args) throws InterruptedException {
// 初始化一个Callable对象和FutureTask对象
Callable pAccount = new PrivateAccount();
FutureTask futureTask = new FutureTask(pAccount);
// 使用futureTask创建一个线程
Thread pAccountThread = new Thread(futureTask);
System.out.println("===futureTask线程现在开始启动,启动时间为:" + System.nanoTime());
pAccountThread.start();
System.out.println("主线程开始执行其他任务");
// 从其他账户获取总金额
Thread.sleep(1000);
int totalMoney = new Random().nextInt(100000);
System.out.println("现在你在其他账户中的总金额为" + totalMoney);
System.out.println("等待私有账户总金额统计完毕...");
// 测试后台的计算线程是否完成,如果未完成则等待
while (!futureTask.isDone()) {
try {
Thread.sleep(500);
System.out.println("===私有账户计算未完成继续等待...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("===futureTask线程计算完毕,此时时间为" + System.nanoTime());
Integer privateAccountMoney = null;
try {
privateAccountMoney = (Integer) futureTask.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("您现在的总金额为:" + totalMoney + privateAccountMoney.intValue());
}
}
@SuppressWarnings("all")
class PrivateAccount implements Callable {
Integer totalMoney;
@Override
public Object call() throws Exception {
System.out.println("===开始查看私有账户:");
Thread.sleep(5000);
totalMoney = new Integer(new Random().nextInt(10000));
System.out.println("===您当前有" + totalMoney + "在您的私有账户中");
return totalMoney;
}
}
运行结果:
参考:http://uule.iteye.com/blog/1539084#
CyclicBarrier(回环栅栏)
通过它可以实现让一组线程等待至某个状态之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。我们暂且把这个状态就叫做barrier,当调用await()方法之后,线程就处于barrier了。
public CyclicBarrier(int parties, Runnable barrierAction) ;
参数parties指让多少个线程或者任务等待至barrier状态;参数barrierAction为当这些线程都达到barrier状态时会执行的内容。
例子:需求是这样的:学生一个人走太危险,现在门卫放学在门口守着,让学生3个一组的走,不到3个人不允许走。
package threadTest;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierTest {
/**
* 学生总数
*/
private final int STUDENT_COUNT = 11;
/**
* 当人到齐,自动开门程序
*/
final CyclicBarrier barrier = new CyclicBarrier(3,
new Runnable() {
@Override
public void run() {
System.out.println("有3个学生到齐了,放行....");
}
});
public void goHome() throws InterruptedException, BrokenBarrierException {
System.out.println(Thread.currentThread().getName() + "已刷卡,等待开门回家~");
barrier.await();
System.out.println(Thread.currentThread().getName() + "回家了~");
}
public static void main(String[] args) throws InterruptedException,
BrokenBarrierException {
final CyclicBarrierTest instance = new CyclicBarrierTest();
/**
* 每个线程代表一个学生
*/
for (int i = 1; i <= instance.STUDENT_COUNT; i++) {
new Thread("学生" + i + " ") {
public void run() {
try {
instance.goHome();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
};
}.start();
}
}
}
执行结果: