CountDownLatch倒计时器
CountDownLatch这个类能够使一个线程等待其他线程完成各自的工作后再执行。例如,应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行。
CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。
主要方法
public CountDownLatch(int count);
public void countDown();
public void await() throws InterruptedException
构造方法参数指定了计数的次数
countDown方法,当前线程调用此方法,则计数减一
awaint方法,调用此方法会一直阻塞当前线程,直到计时器的值为0
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CountDownLatchDemo implements Runnable {
private static final CountDownLatch end = new CountDownLatch(10);
private static final CountDownLatchDemo demo = new CountDownLatchDemo();
@Override
public void run() {
try {
//模拟检查任务
Thread.sleep(new Random().nextInt(10)*1000);
System.out.println(Thread.currentThread().getId()+"检查结束");
end.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService execuexecors = Executors.newFixedThreadPool(10);
for (int i = 0; i<10 ;i++) {
execuexecors.execute(demo);
}
//等待检查
end.await();
System.out.println("检查完毕");
execuexecors.shutdown();
}
}
结果:
/**
* 结果:
* 16检查结束
13检查结束
14检查结束
11检查结束
10检查结束
18检查结束
12检查结束
17检查结束
19检查结束
15检查结束
检查完毕
*/
CyclicBarrier循环栅栏
字面意思回环栅栏,通过它可以实现让一组线程等待至某个状态之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。我们暂且把这个状态就叫做barrier,当调用await()方法之后,线程就处于barrier了。
CyclicBarrier类位于java.util.concurrent包下,CyclicBarrier提供2个构造器:
public CyclicBarrier(int parties, Runnable barrierAction) {
}
public CyclicBarrier(int parties) {
}
参数parties指让多少个线程或者任务等待至barrier状态;参数barrierAction为当这些线程都达到barrier状态时会执行的内容。
然后CyclicBarrier中最重要的方法就是await方法,它有2个重载版本:
public int await() throws InterruptedException, BrokenBarrierException { };
public int await(long timeout, TimeUnit unit)throws InterruptedException,BrokenBarrierException,TimeoutException { };
第一个版本比较常用,用来挂起当前线程,直至所有线程都到达barrier状态再同时执行后续任务;
第二个版本是让这些线程等待至一定的时间,如果还有线程没有到达barrier状态就直接让到达barrier的线程执行后续任务。
举一个简单的例子,使用CyclicBarrier模拟士兵集合,执行任务两次事件.
(1)soldier.java
mport java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class Soldier implements Runnable {
private String soldier;
private final CyclicBarrier cyclicBarrier;
public Soldier(String soldier, CyclicBarrier cyclicBarrier) {
this.soldier = soldier;
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
try {
//等待士兵到齐
cyclicBarrier.await();
//等待士兵完成工作
doWork();
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
public void doWork() {
try {
Thread.sleep(Math.abs(new Random().nextInt()%10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(soldier + " :任务完成");
}
}
(2)BarrierRun.java
public class BarrierRun implements Runnable {
private boolean flag;
private int N;
public BarrierRun(boolean flag, int n) {
this.flag = flag;
N = n;
}
@Override
public void run() {
if (flag) {
System.out.println("士兵"+N+"个,任务完毕");
} else {
System.out.println("士兵"+N+"个,集合完成");
flag = true;
}
}
}
(3)CyclicBarrierDemo.java
public class CyclicBarrierDemo {
public static void main(String[] args) {
final int N = 5;
Thread[] allSoldier = new Thread[10];
boolean flag = false;
CyclicBarrier cyclicBarrier = new CyclicBarrier(N, new BarrierRun(flag, N));
System.out.println("集合士兵");
for (int i = 0; i<N; i++) {
System.out.println("士兵"+i+"报道!");
allSoldier[i] = new Thread(new Soldier( "士兵"+i,cyclicBarrier));
allSoldier[i].start();
}
}
}
结果:
public class CyclicBarrierDemo {
public static void main(String[] args) {
final int N = 5;
Thread[] allSoldier = new Thread[10];
boolean flag = false;
CyclicBarrier cyclicBarrier = new CyclicBarrier(N, new BarrierRun(flag, N));
System.out.println("集合士兵");
for (int i = 0; i<N; i++) {
System.out.println("士兵"+i+"报道!");
allSoldier[i] = new Thread(new Soldier( "士兵"+i,cyclicBarrier));
allSoldier[i].start();
}
}
}
CyclicBarrier与countdownlatch的区别
CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同:CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。
ReentrantLock重入锁
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReentrantLockDemo {
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
public void method1(){
try{
lock.lock();
System.out.println("当前线程:"+ Thread.currentThread().getName()+ "进入方法m1等待");
condition1.await();
System.out.println("当前线程:"+ Thread.currentThread().getName()+ "方法m1继续..");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void method2(){
try {
lock.lock();
System.out.println("当前线程:" +Thread.currentThread().getName() + "进入方法m2等待..");
condition1.await();
System.out.println("当前线程:" +Thread.currentThread().getName() + "方法m2继续..");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void method3(){
try {
lock.lock();
System.out.println("当前线程:" +Thread.currentThread().getName() + "进入方法m3等待..");
condition2.await();
System.out.println("当前线程:" +Thread.currentThread().getName() + "方法m3继续..");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void method4(){
try {
lock.lock();
System.out.println("当前线程:" +Thread.currentThread().getName() + "唤醒..");
condition1.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void method5(){
try {
lock.lock();
System.out.println("当前线程:" +Thread.currentThread().getName() + "唤醒..");
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ReentrantLockDemo lockDemo = new ReentrantLockDemo();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
lockDemo.method1();
}
},"thread1");
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
lockDemo.method2();
}
},"thread2");
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
lockDemo.method3();
}
},"thread3");
Thread thread4 = new Thread(new Runnable() {
@Override
public void run() {
lockDemo.method4();
}
},"thread4");
Thread thread5 = new Thread(new Runnable() {
@Override
public void run() {
lockDemo.method5();
}
},"thread5");
thread1.start(); // c1
thread2.start(); // c1
thread3.start(); // c2
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread4.start(); // c1
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread5.start(); // c2
}
}
结果:
当前线程:thread2进入方法m2等待..
当前线程:thread1进入方法m1等待
当前线程:thread3进入方法m3等待..
当前线程:thread4唤醒..
当前线程:thread2方法m2继续..
2前线程:thread1方法m1继续..
当前线程:thread5唤醒..
当前线程:thread3方法m3继续..
ReentrantLock与synchronized的区别
- 与synchronized相比,ReentrantLock提供了更多,更加全面的功能,具备更强的扩展性。例如:时间锁等候,可中断锁等候,锁投票。
- ReentrantLock还提供了条件Condition,对线程的等待、唤醒操作更加详细和灵活,所以在多个条件变量和高度竞争锁的地方,ReentrantLock更加适合。
- ReentrantLock提供了可轮询的锁请求。它会尝试着去获取锁,如果成功则继续,否则可以等到下次运行时处理,而synchronized则一旦进入锁请求要么成功要么阻塞,所以相比synchronized而言,ReentrantLock会不容易产生死锁些。
- ReentrantLock支持更加灵活的同步代码块,但是使用synchronized时,只能在同一个synchronized块结构中获取和释放。
- 注:ReentrantLock的锁释放一定要在finally中处理,否则可能会产生严重的后果。
- ReentrantLock支持中断处理,且性能较synchronized会好些。
ReadWriteLock读写锁
特点:
读读不互斥:读读之间不阻塞。
读写互斥:读阻塞写,写也会阻塞读。
写写互斥:写写阻塞。
适用于读操作远远大于写操作,则读写分离,可以发挥最大的功效。
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReetrantReadWriteLockDemo {
private int value;
private static Lock lock = new ReentrantLock();
private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
private static Lock readLock = reentrantReadWriteLock.readLock();
private static Lock writeLock = reentrantReadWriteLock.writeLock();
//模拟读操作
public Integer handleReadLock(Lock lock) throws InterruptedException{
try {
lock.lock();
Thread.sleep(1000); //读耗时越多,读写锁的优势就越明显
System.out.println(Thread.currentThread().getId()+"读到的数据为:"+value);
return value;
}
finally {
lock.unlock();
}
}
//模拟写操作
public void handleWriteLock(Lock lock, Integer index) throws InterruptedException {
try {
lock.lock();
Thread.sleep(1000);
value=index;
System.out.println(Thread.currentThread().getId()+"写入的数据为:"+value);
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ReetrantReadWriteLockDemo reetrantReadWriteLockDemo = new ReetrantReadWriteLockDemo();
Runnable readRunnable = new Runnable() {
@Override
public void run() {
try{
reetrantReadWriteLockDemo.handleReadLock(readLock);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Runnable writeRunnale = new Runnable() {
@Override
public void run() {
try{
reetrantReadWriteLockDemo.handleWriteLock(writeLock, new Random().nextInt());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
for (int i =0; i<18; i++) {
new Thread(readRunnable).start();
}
for (int i=18; i<20; i++) {
new Thread(writeRunnale).start();
}
}
}
结果:
/**
*10读到的数据为:0
14读到的数据为:0
15读到的数据为:0
17读到的数据为:0
18读到的数据为:0
19读到的数据为:0
20读到的数据为:0
21读到的数据为:0
22读到的数据为:0
23读到的数据为:0
24读到的数据为:0
25读到的数据为:0
26读到的数据为:0
27读到的数据为:0
13读到的数据为:0
12读到的数据为:0
11读到的数据为:0
16读到的数据为:0
29写入的数据为:-193559487
28写入的数据为:1126601655
*/
上述代码中,模拟了一个非常耗时的读和写操作。由于采用了读写锁,读线程完全并行,写线程会阻塞进行,所以该代码大概运行2s就结束了。
我们可以把读写线程传入的锁修改为普通的重入锁,重新运行一下程序,可以看到如下结果:
/**
10读到的数据为:0
11读到的数据为:0
12读到的数据为:0
13读到的数据为:0
14读到的数据为:0
15读到的数据为:0
16读到的数据为:0
17读到的数据为:0
18读到的数据为:0
19读到的数据为:0
20读到的数据为:0
21读到的数据为:0
22读到的数据为:0
23读到的数据为:0
24读到的数据为:0
25读到的数据为:0
26读到的数据为:0
27读到的数据为:0
28写入的数据为:810899509
29写入的数据为:571965387
**/
那么可以看到所有的读写操作都是串行的了,整个程序运行的执行时间达到20S。
Semaphore信号量
Semaphore类是一个计数信号量,必须由获取它的线程释放,通常用于限制可以访问某些资源(物理或逻辑的)线程数目。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class SemapDemo implements Runnable {
final Semaphore semaphore = new Semaphore(5);
@Override
public void run() {
try {
//获取许可
semaphore.acquire();
//模拟耗时操作
Thread.sleep(2000);
System.out.println(Thread.currentThread().getId()+":done!");
//释放许可
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
SemapDemo semapDemo = new SemapDemo();
for (int i=0; i<20;i++) {
executorService.submit(semapDemo);
}
executorService.shutdown();
}
}
打印出结果,可以看到线程任务是5次5次的执行。
Lock,synchronized和信号量的区别
Lock和synchronized是锁的互斥,一个线程如果锁定了一资源,那么其它线程只能等待资源的释放。也就是一次只有一个线程执行,这到这个线程执行完毕或者unlock。而Semaphore可以控制多个线程同时对某个资源的访问。Semaphore实现的功能就类似厕所有5个坑,假如有10个人要上厕所,那么同时只能有多少个人去上厕所呢?同时只能有5个人能够占用,当5个人中 的任何一个人让开后,其中等待的另外5个人中又有一个人可以占用了。另外等待的5个人中可以是随机获得优先机会,也可以是按照先来后到的顺序获得机会,这取决于构造Semaphore对象时传入的参数选项。当然单个信号量的Semaphore对象可以实现互斥锁的功能,并且可以是由一个线程获得了“锁”,再由另一个线程释放“锁”,这可应用于死锁恢复的一些场合。
信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作。也就是说Semaphore不一定是锁定某个资源,而是流程上的概念。比方说有A,B两个线程,B线程的操作可能要等A线程执行完毕之后才执行,这个任务 并不一定是锁定某一资源,还可以是进行一些计算或者数据处理之类,它们也许并不访问共享变量,只是逻辑上的先后顺序。
java中计数信号量(Semaphore)维护着一个许可集。调用acquire()获取一个许可,release()释放一个许可。 在java中,还可以设置该信号量是否采用公平模式,如果以公平方式执行,则线程将会按到达的顺序(FIFO)执行,如果是非公平,则可以后请求的有可能排在队列的头部。Semaphore当前在多线程环境下被扩放使用,操作系统的信号量是个很重要的概念,在进程控制方面都有应用。Java并发库Semaphore 可以很轻松完成信号量控制,Semaphore可以控制某个资源可被同时访问的个数。
信号量和线程池的区别:
线程池控制的是线程数量,而信号量控制的是并发数量,虽然说这个看起来一样,但是还是有区别的。
信号量的调用,当达到数量后,线程还是存在的,只是被挂起了而已。而线程池,同时执行的线程数量是固定的,超过了数量的只能等待。