CountDownLatch
CountDownLatch介绍
在日常开发中经常会遇到需要在主线程中开启多个线程去并行执行任务,并且主线程需要等待所有子线程执行完毕后再进行汇总的场景。再CountDownLatch出现之前一般都使用线程的join()方法来实现这一点,但是join方法不够灵活,不能够满足不同场景的需要,所以JDK开发组提供了CountDownLatch这个类。
CountDownLatch使用
package com.wxw.juc;
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(3);
Thread thread1 = new Thread(()->{
System.out.println("线程1执行!!!!");
countDownLatch.countDown();
});
Thread thread2 = new Thread(()->{
System.out.println("线程2执行!!!!");
countDownLatch.countDown();
});
Thread thread3 = new Thread(()->{
System.out.println("线程3执行!!!!");
countDownLatch.countDown();
});
thread1.start();
thread2.start();
thread3.start();
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程最终执行汇总!!!!");
}
}
在以上代码中创建了一个CountDownLatch倒计时计数器实例,计数器的值为3。
同时创建了3个线程,每个线程运行完成后调用了计数器的countDown()方法。
最后在主线程中调用了计数器的await()方法。
当主线程调用计数器的await()方法后会被阻塞,子线程调用countDwon()方法后会让计数器的值减去1,当计数器的值为0的时候主线程的await()方法才会返回。
CountDownLatch与join方法的区别
在项目实践中一般都避免直接操作线程,而是使用ExecutorService线程池来管理线程。使用ExecutorService时传递的参数时Runnable或者Callable对象,这时候是没有办法直接调用线程的join()方法的,这就需要使用CountDownLatch了。
package com.wxw.juc;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CountDownLatchDemoTwo {
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(2);
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println("子线程A正在执行!!!");
countDownLatch.countDown();
}
});
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println("子线程B正在执行!!!");
countDownLatch.countDown();
}
});
try {
countDownLatch.await();
System.out.println("子线程全部执行完毕!!!!!!!!!!!!!!!");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程最终执行汇总!!!!");
executorService.shutdown();
}
}
join()方法在调用后,该线程会一直被阻塞,直到子线程运行完毕。
而CountDownLatch则允许子线程在任何时候调用countDown()方法在递减计数,不需要等到子线程运行结束。
CyclicBarrier
CyclicBarrier介绍
从字面意思理解,CyclicBarrier是循环屏障,它可以让一组线程全部达到一个屏障点后再一起往下执行。当一组线程都突破一个屏障点后,状态会被重置,下一个屏障点仍然可以生效。
CyclicBarrier使用
package com.wxw.juc;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public static void main(String[] args) {
// 声明一个CyclicBarrier对象,屏障点为2,并且指定一个突破屏障点后的汇总线程
CyclicBarrier cyclicBarrier = new CyclicBarrier(2, new Runnable() {
@Override
public void run() {
System.out.println("当前屏障点已突破!!!!所有线程被唤醒!!!!");
}
});
Thread thread1 = new Thread(()->{
System.out.println("线程1正在执行");
System.out.println("线程1到达屏障点A");
try {
cyclicBarrier.await();
System.out.println("线程1突破屏障点A,继续往下执行");
System.out.println("---------------------------");
System.out.println("线程1达到屏障点B");
cyclicBarrier.await();
System.out.println("线程1突破屏障点B,执行完毕!");
System.out.println("---------------------------");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
});
Thread thread2 = new Thread(()->{
System.out.println("线程2正在执行");
System.out.println("线程2到达屏障点A");
try {
cyclicBarrier.await();
System.out.println("线程2突破屏障点A,继续往下执行");
System.out.println("---------------------------");
System.out.println("线程2达到屏障点B");
cyclicBarrier.await();
System.out.println("线程2突破屏障点B,执行完毕!");
System.out.println("---------------------------");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
});
thread1.start();
thread2.start();
}
}
以上代码创建了一个CyclicBarrier的实例,构建函数指定了2个屏障点并设置了突破屏障点后要执行的线程。
创建了2个线程,在线程中调用cyclicBarrier.await()方法。
当线程运行到cyclicBarrier.await()方法时,表示当前线程到达了屏障点,计数器会减去1,如果计数器大于0表示还有其他的线程没有到达屏障点,则会使当前线程阻塞。如果计数器等于0则表示所有线程都已经到达屏障点,运行CyclicBarrier构建函数中执行的线程,并唤醒其他的线程继续执行。且计数器会还原为初始值,下一个屏障点仍然可以继续执行。
Semaphore
Semaphore介绍
Semaphore信号量也是java中的一个同步器,与CountDownLatch和CyclicBarrire不同的是,它内部的计数器是递增的,并且在一开始初始化Semaphore时可以指定一个初始值,但是并不需要直到需要同步的线程个数,而是在需要同步的地方调用acquire方法时指定需要同步的线程个数。
Semaphore使用
package com.wxw.juc;
import java.util.concurrent.Semaphore;
public class SemaphoreDemo {
public static void main(String[] args) {
// 实例化一个Semaphore对象,初始值为0
Semaphore semaphore = new Semaphore(0);
Thread thread1 = new Thread(()->{
System.out.println("线程1执行.....");
semaphore.release();
});
Thread thread2 = new Thread(()->{
try {
Thread.sleep(2000);
System.out.println("线程2执行.....");
} catch (InterruptedException e) {
e.printStackTrace();
}
semaphore.release();
});
thread1.start();
thread2.start();
try {
semaphore.acquire(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程执行....");
}
}
如上代码首先创建了一个信号量实例,构造函数的入参为0,说明当前信号量计数器的值为0。
然后运行了两个线程,每个线程内部调用了信号量的release方法,相当于让计数器的值递增1.
最后在main线程里面调用信号量的acquire方法,传参为2。
在调用该方法时线程会处于阻塞状态,直到信号量的计数器变为2时才会返回。
Semaphore也可以像CyclicBarrier一样可以循环计数,在调用acquire方法返回后会重新设置当前信号量为0。
Condition
Condition介绍
在多线程进行线程间的通信时,如果使用Synchronized进行加锁,可以使用wait\notify\notifyAll的方式进行阻塞和唤醒。那么如果使用J.U.C下的Lock类型进行加锁时,如何实现线程之间的通信呢?所以Java提供了Condition来支持在使用Lock类型进行加锁时进行线程间的通信。
Condition使用
package com.wxw.juc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
public class ConditionWait extends Thread {
private Lock lock;
private Condition condition;
public ConditionWait(Lock lock, Condition condition) {
this.lock = lock;
this.condition = condition;
}
@Override
public void run() {
try {
lock.lock();
System.out.println("begin ---->>>> ConditionWait!!!!");
condition.await();
System.out.println("end ---->>>> ConditionWait!!!!");
} catch (InterruptedException e){
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
package com.wxw.juc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
public class ConditionNotify extends Thread {
private Lock lock;
private Condition condition;
public ConditionNotify(Lock lock, Condition condition) {
this.lock = lock;
this.condition = condition;
}
@Override
public void run() {
try {
lock.lock();
System.out.println("begin ---->>>> ConditionNotify!!!!");
condition.signal();
System.out.println("end ---->>>> ConditionNotify!!!!");
} finally {
lock.unlock();
}
}
}
package com.wxw.juc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionTest {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
ConditionWait conditionWait = new ConditionWait(lock,condition);
ConditionNotify conditionNotify = new ConditionNotify(lock,condition);
conditionWait.start();
conditionNotify.start();
}
}
运行以上代码得到的结果是:
begin ---->>>> ConditionWait!!!!
begin ---->>>> ConditionNotify!!!!
end ---->>>> ConditionNotify!!!!
end ---->>>> ConditionWait!!!!
当ConditionWait线程先运行时,抢到了锁,然后调用了await方法使当前线程进入阻塞状态并释放锁。
ConditionNotify线程运行时,因为锁已经被占用,所以进入阻塞状态,等到ConditionWait线程调用await方法时,当前线程获得锁资源并调用了signal方法唤醒处于阻塞状态下的线程。
最终ConditionWait线程被唤醒,继续执行剩下的操作。