1.等待其他线程完成之CountDownLatch
CountDownLatch的特点就是允许一个或多个线程等待其他线程完成操作。在我看来其实就是一个以计数器的方式来控制将要执行线程等待的时间,然后计数器减到0就开始执行当前阻塞的线程。
引入:
如果我们想要实现线程t2先于线程t1执行,并且在不使用CountDownLatch工具的前提下,用之前博文中所述的join();方法可以实现的。
实现:
public class JoinTest {
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("执行线程t1");
}
});
Thread t2=new Thread(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("执行了线程t2");
}
});
t1.start();
t2.start();
t1.join();
System.out.println("所有线程执行完毕!");
}
}
结果:
执行了线程t2
执行线程t1
所有线程执行完毕!
解释:使用join();方法的线程,必须之前先已经执行了start();方法就是必须得先启动线程才可以去检查线程是否存活。而join的原理就是不停地去检查t2是否存活,如果t2线程存活则让当前线程等待。join实际上也是通过wait实现的。详细看我的 java多线程之线程间通信
CountDownLatch的实现:
public class CountDownLatchTest {
private static CountDownLatch cdl=new CountDownLatch(3);
public static void main(String[] args) {
new Thread(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
try {
cdl.await();
System.out.println("执行线程t1");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable(){
@Override
public void run() {
for(int i=3;i>=0;i--){
cdl.countDown();
System.out.println("计数器状态:"+i);
if(i==1){
System.out.println("执行线程t2");
}
}
}
}).start();
}
}
结果:
计数器状态:3
计数器状态:2
计数器状态:1
执行线程t2
执行线程t1
计数器状态:0
解释:CountDownLatch提供了一个int型的参数的构造方法作为计数的大小,当countDown();方法每调用一次就将计数减一,直到减到0时,将直接退出当前计数的线程,所以输出 "执行线程2" 的时候,应当if判断的是i==1而不是i==0,当等于0直接就退出了,虽然循环满足条件,但是线程t2已经不执行了,线程t1将从等待态变为运行态,当t1执行完,处于阻塞态的线程t2才去执行,所以在最后输出的 "计数器状态:0"。
2.同步屏障之CyclicBarrier
CyclicBarrier特点就是让一组线程到达屏障时被阻塞,直到最后一个线程到达屏障时,这组线程才可以执行操作。
引入:这个就类似于生活中公司组织员工集体出游,肯定得租辆大巴车,大巴车停在公司门口,CEO通知员工在公司门口集合,如果一个员工不到就不能开这辆大巴车,先到的员工就得等待,直到最后一个员工到齐了,大巴车才会开走。这就是CyclicBarrier(篱栅)。
实现:
public class CyclicBarrierTest {
private static CyclicBarrier cb=new CyclicBarrier(3);
private static Executor executor=Executors.newFixedThreadPool(3);
private volatile static int personNum=0;
public static void main(String[] args) {
executor.execute(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
personNum++;
System.out.println("员工1到达指定地点等待,此时人数为:"+personNum);
try {
cb.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (BrokenBarrierException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("员工1上车");
}
});
executor.execute(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
personNum++;
System.out.println("员工2到达指定地点等待,此时人数为:"+personNum);
try {
cb.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (BrokenBarrierException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("员工2上车");
}
});
executor.execute(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
personNum++;
System.out.println("员工3到达指定地点等待,此时人数为:"+personNum);
try {
cb.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (BrokenBarrierException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("员工3上车");
}
});
}
}
结果:
员工1到达指定地点等待,此时人数为:1
员工2到达指定地点等待,此时人数为:2
员工3到达指定地点等待,此时人数为:3
员工3上车
员工1上车
员工2上车
-----------------
CyclicBarrier还提供了另一个构造方法,优先执行指定的线程。
以及方法reset();方法可以将屏障设置为初始状态以及getNumberWaiting();方法获得阻塞线程的数量。
3.控制并发线程数量之Semaphore
Semaphore的特点就是用来控制同时访问特定资源的线程数量。
引入:
生活中,我们过桥,如果桥就能过3个人,那么一次就只能走三个人,如果多了,那么就会有人掉河里了。这就是Semaphore控制人数过桥。
实现:
public class SemaphoreTest {
private static final int PERSON_NUM=4;
private static ExecutorService es=Executors.newFixedThreadPool(PERSON_NUM);
private static Semaphore s=new Semaphore(3,true);
public static void release(Semaphore s,String name){
s.release();
System.out.println(name+"已经离开桥了!");
}
public static void main(String[] args) {
es.execute(new Runnable(){
@Override
public void run() {
try {
s.acquire();
System.out.println("甲上桥了!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
es.execute(new Runnable(){
@Override
public void run() {
try {
s.acquire();
System.out.println("乙上桥了!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
es.execute(new Runnable(){
@Override
public void run() {
try {
s.acquire();
System.out.println("丙上桥了!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
es.execute(new Runnable(){
@Override
public void run() {
try {
release(s,"甲");
s.acquire();
System.out.println("丁上桥了!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
结果:
甲上桥了!
乙上桥了!
丙上桥了!
甲已经离开桥了!
丁上桥了!
解释:虽然线程池里面有甲乙丙丁四个线程准备过桥Semaphore,但是Semaphore只能让三个人过桥,所以,甲没离开桥上时,丁是无法上桥的,所以甲离开之后,丁就可以上桥了。
方法:
boolean hasQueuedThreads():查询是否有线程正等待获取。
Collection<Thread> getQueuedThreads():返回一个Collection,包含可能等待获取的线程。
---------------------------------------
4.线程间交换数据之Exchanger
Exchanger的特点就是一个用于线程间协作的工具类,可以用于数据交换。Exchanger就是设置了一个同步点,如果第一个线程先执行了exchange();方法,它会一直等待第二个线程也执行exchange();方法交换数据,当两个线程都到达同步点时,两个线程就可以交换数据了。
引入:
例如生活中我们的对账,分公司和总公司对于用户余额的对账的功能。分公司线程A先把账放到那里,总公司线程B再去账的位置,也就是对账点,然后对账。这就是交换数据使用Exchanger的过程。
实现:
public class ExchangerTest {
private static final Exchanger<Map<String,String>> e=new Exchanger<Map<String,String>>();
private static ExecutorService es=Executors.newFixedThreadPool(2);
public static void main(String[] args) {
es.execute(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
try{
Map<String,String> mapA=new HashMap<String,String>();
mapA.put("123", "¥5000");
mapA.put("456", "¥7000");
e.exchange(mapA);
}catch(InterruptedException e){
e.printStackTrace();
}
}
});
es.execute(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
try {
Map<String,String> mapB=new HashMap<String,String>();
mapB.put("123", "¥5000");
mapB.put("456", "¥4000");
Map<String,String> mapA=e.exchange(mapB);
System.out.println("流水123是否相同:"+mapA.get("123").equals(mapB.get("123")));
System.out.println("流水456是否相同:"+mapA.get("456").equals(mapB.get("456")));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
es.shutdown();
}
}
结果:
流水123是否相同:true
流水456是否相同:false
解释:如果使用Exchanger让线程A等待时间太长,那么也可以使用:
V | exchange(V x, long timeout, TimeUnit unit) |