假设有只有的一个场景:每个线程代表一个跑步运动员,当运动员都准备好后,才一起出发,只要有一个人没有准备好,大家都等待。
工作中场景:比如你多台机器想同时准备好了,再去干分布式计算等等。
案例:
public class UseCyclicBarrier {
static class Runner implements Runnable {
private CyclicBarrier barrier;
private String name;
public Runner(CyclicBarrier barrier, String name) {
this.barrier = barrier;
this.name = name;
}
@Override
public void run() {
try {
//随机休眠0-5秒
Thread.sleep(1000 * (new Random()).nextInt(5));
System.out.println(name + " 准备OK.");
//等待
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(name + " Go!!");
}
}
public static void main(String[] args) throws IOException, InterruptedException {
CyclicBarrier barrier = new CyclicBarrier(3); // 3
//3个固定的线程池
ExecutorService executor = Executors.newFixedThreadPool(3);
executor.submit(new Thread(new Runner(barrier, "hfbin-01")));
executor.submit(new Thread(new Runner(barrier, "hfbin-02")));
executor.submit(new Thread(new Runner(barrier, "hfbin-03")));
executor.shutdown();
}
}
打印答案(注意看打印的顺序):
hfbin-01 准备OK.
hfbin-02 准备OK.
hfbin-03 准备OK.
hfbin-03 Go!!
hfbin-01 Go!!
hfbin-02 Go!!
他经常用于监听某些初始化操作,等初始化执行完毕后,通知主线程继续工。
案例:
public class UseCountDownLatch {
public static void main(String[] args) {
final CountDownLatch countDown = new CountDownLatch(2);
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("进入线程t1" + "等待其他线程处理完成...");
//等待countDown发出通知即可继续执行下面代码
countDown.await();
System.out.println("t1线程继续执行...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("t2线程进行初始化操作...");
Thread.sleep(3000);
System.out.println("t2线程初始化完毕,通知t1线程继续...");
countDown.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("t3线程进行初始化操作...");
Thread.sleep(4000);
System.out.println("t3线程初始化完毕,通知t1线程继续...");
countDown.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
t2.start();
t3.start();
}
}
打印答案:
进入线程t1等待其他线程处理完成...
t2线程进行初始化操作...
t3线程进行初始化操作...
t2线程初始化完毕,通知t1线程继续...
t3线程初始化完毕,通知t1线程继续...
t1线程继续执行...
t1使用 await() 这个方法会处于阻塞状态 需要等待t2 t3 countDown()执行完成后才能继续执行 t1 被阻塞后面的代码
也就是:t1 需要等 t2 t3 执行完成之后才能执行
注意:CountDownLatch countDown = new CountDownLatch(2); 这里用到多少个countDown(),CountDownLatch()里面参数就写多少个。
注意:CyclicBarrier和CountDownLacth的区别
CountDownLacth 属于一个线程等待,其他n个线程发出通知 ,然后再执行等待的线程。(针对一个线程)
CyclicBarrier 多个线程参与阻塞。(针对多个线程)
这个例子其实是我们之前实现的Future 模式 。jdk给予我们一个实现的封装类,使用非常简单。
Future 模式非常适合在处理很耗时很长的业务逻辑时进行使用,可以有效的减少系统的响应时间,提高系统的吞吐量。
案例
public class UseFuture implements Callable<String>{
private String para;
public UseFuture(String para){
this.para = para;
}
/**
* 这里是真实的业务逻辑,其执行可能很慢
*/
@Override
public String call() throws Exception {
//模拟执行耗时
Thread.sleep(5000);
String result = this.para + "处理完成";
return result;
}
//主控制函数
public static void main(String[] args) throws Exception {
String queryStr = "query-1";
String queryStr2 = "query-2";
//构造FutureTask,并且传入需要真正进行业务逻辑处理的类,该类一定是实现了Callable接口的类
FutureTask future = new FutureTask(new UseFuture(queryStr));
FutureTask future2 = new FutureTask(new UseFuture(queryStr2));
//创建一个固定线程的线程池且线程数为2,
ExecutorService executor = Executors.newFixedThreadPool(2);
//这里提交任务future,则开启线程执行RealData的call()方法执行
//submit和execute的区别: 第一点是submit可以传入实现Callable接口的实例对象, 第二点是submit方法有返回值
//执行完成后 f1 f2 都会为空
Future f1 = executor.submit(future); //单独启动一个线程去执行的
Future f2 = executor.submit(future2);
System.out.println("f1 请求完毕 = "+f1.get());
System.out.println("请求完毕");
try {
//这里可以做额外的数据操作,也就是主程序执行其他业务逻辑
System.out.println("处理实际的业务逻辑...");
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
//调用获取数据方法,如果call()方法没有执行完成,则依然会进行等待
System.out.println("数据:" + future.get());
System.out.println("数据:" + future2.get());
executor.shutdown();
}
}
打印结果
f1 请求完毕 = null
请求完毕
处理实际的业务逻辑...
数据:query-1处理完成
数据:query-2处理完成
在Semaphone信号量非常适合高并发访问,新系统在上线之前,要对系统的访问量进行评估,当然这个值肯定不是随便拍拍脑袋就能想出来的,是经过以往的经验、数据、历年的访问量,己经推广力度讲行一个合理的评估,当然评估标推不能太大也不能太小,太大的话投入的资源达不到实际效果,纯粹浪费资源,太小的话,某个时间点一个高峰值的访问量上来直接可以压垮系统
相关概念:
PV(view)网站的总访何量,页面浏览量或点击量,用户每刷新一,次就会被记录一次。
UV (unique Visitor)访网网站的一台申客户端为一个访客,一般来讲,时间上以00:00-24:00之内相同ip的客户端只记录一次。
QPS(query per second)即每秒查询数,qps很大程度上代表了系统业务上的繁忙程度,每次请求的背后,可能对应着多次磁盘I/O,多次网络请求,多个cpu时间片等。我们通过qps可以非常直观的了解当前系统业务情况,一旦当前超过所设定的预警阀值,可以考虑加机器对集群扩容,以免压力过大导致宕机,可以根据前期的压力测试得到估值,在结合后期综合运维清况,估算出值。
RT (response time)即请求的响应时间,这个指标非常关键,直接说明前端用户的体验,因此任何系统都想降低rt时间。当然还涉及cpu、内存、网络、磁盘等情况,更细节的问很多,如select、update、delete/ps等数据库层面的统计。
容量评估:一般来说通过开发、运维、测试、以及业务等相关人员.綜合出系统的一系列阈值,然后我们根据关键阈值如qps、rt等,对系统讲行有效的变更。一般来讲.我们讲行多轮压力测试以后,可以对系统讲行峰值评估,采用所谓的80/20原则,即80%的访网请求将在20%的时间内达到。这样我们可以根据系统对应PV计算出峰值 qps。
峰值qps= (总PV ×80%)/(60 × 60× 24 ×20%)
然后在将总的峰值qps除以单台机器所能承受的最高的qps值,就是所需要机器的数量:机器数=总的峰值qps /压测得出的单机极限qps
当然不排除系统在上线前进行大型促销活动,或者双十一、双十二热点事件、遭受到DDos攻击等情况,系统的开发和运维人员急需要了解当前系统运行的状态和负载情况,一般都会有后台系统去维护。
Semaphone可以控制系统的流量:
拿到信号量的钱程可以讲入,否则就等待.通过acquire()和rekease()获取和释放访同许可。
案例:
public class UseSemaphore {
public static void main(String[] args) {
// 线程池
ExecutorService exec = Executors.newCachedThreadPool();
// 只能5个线程同时访问
final Semaphore semp = new Semaphore(5);
// 模拟20个客户端访问
for (int index = 0; index < 20; index++) {
final int NO = index;
Runnable run = new Runnable() {
public void run() {
try {
// 获取许可
semp.acquire();
System.out.println("Accessing: " + NO);
//模拟实际业务逻辑
Thread.sleep((long) (Math.random() * 10000));
// 访问完后,释放
semp.release();
} catch (InterruptedException e) {
}
}
};
exec.execute(run);
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//System.out.println(semp.getQueueLength());
// 退出线程池
exec.shutdown();
}
}
打印的结果:
Accessing: 0
Accessing: 1
Accessing: 2
Accessing: 3
Accessing: 4
Accessing: 5
Accessing: 6
Accessing: 7
Accessing: 8
Accessing: 9
Accessing: 10
Accessing: 11
Accessing: 12
Accessing: 13
Accessing: 14
Accessing: 15
Accessing: 16
Accessing: 19
Accessing: 17
Accessing: 18
源代码:https://github.com/hfbin/Thread_Socket/tree/master/Thread/concurrent019