文章目录
- 1. 独占锁
- 2. 共享锁
- 2.1 Semaphore
- 2.2 CountDownLatch
- 2.3 CyclicBarrie
问题:
1、Semaphore有什么作用?
2、CyclicBarrier和CountDownLatch的用法及区别
3、三个线程a、b、c并发运行,b,c需要a线程的数据怎么实现?
4、怎么控制同一时间只有 3 个线程运行?
1. 独占锁
独占锁也叫排他锁、互斥锁、独享锁,是指锁在同一时刻只能被一个线程所持有。一个线程加锁后,任何其他试图再次加锁的线程都会被阻塞,直到持有锁线程解锁。通俗来说,就是共享资源某一时刻只能有一个线程访问,其余线程阻塞等待。
如果是公平地独占锁,在持有锁线程解锁时,如果有一个以上的线程在阻塞等待,那么最先抢锁的线程被唤醒变为就绪状态去执行加锁操作,其他的线程仍然阻塞等待。
java中的Synchronized内置锁和ReentrantLock显式锁都是独占锁。
2. 共享锁
共享锁就是在同一时刻允许多个线程持有的锁。当然,获得共享锁的线程只能读取临界区的数据,不能修改临界区的数据。
JUC中的共享锁包括Semaphore(信号量)、ReadLock(读写锁)中的读锁、CountDownLatch倒数闩。
2.1 Semaphore
Semaphore用来限制能同时访问共享资源的线程上限。
Semaphore可以用来控制在同一时刻访问共享资源的线程数量,通过协调各个线程以保证共享资源的合理使用。Semaphore维护了一组虚拟许可,它的数量可以通过构造器的参数指定。线程在访问共享资源前必须调用Semaphore的acquire()方法获得许可,如果许可数量为0,该线程就一直阻塞。线程访问完资源后,必须调用Semaphore的release()方法释放许可。更形象的说法是:Semaphore是一个许可管理器。
Semaphore类的主要方法大致如下:
(1) Semaphore(permits)
:构造一个Semaphore实例,初始化其管理的许可数量为permits参数值。
(2) Semaphore(permits,fair)
:构造一个Semaphore实例,初始化其管理的许可数量为permits参数值,以及是否以公平模式(fair参数是否为true)进行许可的发放。
Semaphore和ReentrantLock类似,Semaphore发放许可时有两种模式:公平模式和非公平模式,默认情况下使用非公平模式。
(3) availablePermits()
:获取Semaphore对象可用的许可数量。
(4) acquire()
:当前线程尝试获取Semaphore对象的一个许可。此过程是阻塞的,线程会一直等待Semaphore发放一个许可,直到发生以下任意一件事:
- 当前线程获取了一个可用的许可。
- 当前线程被中断,就会抛出InterruptedException异常,并停止等待,继续往下执行。
(5) acquire(permits)
:当前线程尝试阻塞地获取permits个许可。此过程是阻塞的,线程会一直等待Semaphore发放permits个许可。如果没有足够的许可而当前线程被中断,就会抛出InterruptedException异常并终止阻塞。
(6) acquireUninterruptibly()
:当前线程尝试阻塞地获取一个许可,阻塞的过程不可中断,直到成功获取一个许可。
(7) acquireUninterruptibly(permits)
:当前线程尝试阻塞地获取permits个许可,阻塞的过程不可中断,直到成功获取permits个许可。
(8) tryAcquire()
:当前线程尝试获取一个许可。此过程是非阻塞的,它只是进行一次尝试,会立即返回。如果当前线程成功获取了一个许可,就返回true;如果当前线程没有获得许可,就返回false
(9) tryAcquire(permits)
:当前线程尝试获取permits个许可。此过程是非阻塞的,它只是进行一次尝试,会立即返回。如果当前线程成功获取了permits个许可,就返回true;如果当前线程没有获得permits个许可,就返回false。
(10) tryAcquire(timeout,TimeUnit)
:限时获取一个许可。此过程是阻塞的,会一直等待许可,直到发生以下任意一件事:
- 当前线程获取了一个许可,则会停止等待,继续执行,并返回true。
- 当前线程等待timeout后超时,则会停止等待,继续执行,并返回false。
- 当前线程在timeout时间内被中断,则会抛出InterruptedException异常,并停止等待,继续执行。
(11) tryAcquire(permits,timeout,TimeUnit)
:与tryAcquire(timeout,TimeUnit)方法在逻辑上基本相同,不同之处在于:在获取许可的数量上不同,此方法用于获取permits个许可。
(12) release()
:当前线程释放一个可用的许可。
(13) release(permits)
:当前线程释放permits个可用的许可。
(14) drainPermits()
:当前线程获得剩余的所有可用许可。
(15) hasQueuedThreads()
:判断当前Semaphore对象上是否存在正在等待许可的线程。
(16) getQueueLength()
:获取当前Semaphore对象上正在等待许可的线程数量。
共享锁使用示例 :
@Slf4j
public class TestSemaphore {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);
for(int i=0;i<10;i++){
new Thread(()->{
try {
// 获取许可
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 睡眠1s
try {
log.debug("running.....");
Thread.sleep(1000);
log.debug("end.....");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
}).start();
}
}
}
执行结果:
17:36:28.044 [Thread-1] DEBUG com.example.aa.TestSemaphore - running.....
17:36:28.047 [Thread-0] DEBUG com.example.aa.TestSemaphore - running.....
17:36:28.047 [Thread-2] DEBUG com.example.aa.TestSemaphore - running.....
17:36:29.047 [Thread-0] DEBUG com.example.aa.TestSemaphore - end.....
17:36:29.047 [Thread-2] DEBUG com.example.aa.TestSemaphore - end.....
17:36:29.047 [Thread-3] DEBUG com.example.aa.TestSemaphore - running.....
17:36:29.047 [Thread-4] DEBUG com.example.aa.TestSemaphore - running.....
17:36:29.047 [Thread-1] DEBUG com.example.aa.TestSemaphore - end.....
17:36:29.047 [Thread-5] DEBUG com.example.aa.TestSemaphore - running.....
17:36:30.047 [Thread-4] DEBUG com.example.aa.TestSemaphore - end.....
17:36:30.047 [Thread-5] DEBUG com.example.aa.TestSemaphore - end.....
17:36:30.047 [Thread-6] DEBUG com.example.aa.TestSemaphore - running.....
17:36:30.047 [Thread-3] DEBUG com.example.aa.TestSemaphore - end.....
17:36:30.047 [Thread-7] DEBUG com.example.aa.TestSemaphore - running.....
17:36:30.047 [Thread-8] DEBUG com.example.aa.TestSemaphore - running.....
17:36:31.047 [Thread-7] DEBUG com.example.aa.TestSemaphore - end.....
17:36:31.047 [Thread-8] DEBUG com.example.aa.TestSemaphore - end.....
17:36:31.047 [Thread-6] DEBUG com.example.aa.TestSemaphore - end.....
17:36:31.047 [Thread-9] DEBUG com.example.aa.TestSemaphore - running.....
17:36:32.048 [Thread-9] DEBUG com.example.aa.TestSemaphore - end.....
2.2 CountDownLatch
用来进行线程同步协作,等待所有线程完成倒计时。 其中构造参数用来初始化等待计数值,await() 用来等待计数归零,countDown() 用来让计数减一。
CountDownLatch可以指定一个计数值,在并发环境下由线程进行减一操作,当计数值变为0之后,被await方法阻塞的线程将会唤醒。通过CountDownLatch可以实现线程间的计数同步。
@Slf4j
public class TestCountDownLatch {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(3);
new Thread(()->{
log.debug("begin...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 计数减1
countDownLatch.countDown();
log.debug("end...");
}).start();
new Thread(()->{
log.debug("begin...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 计数减1
countDownLatch.countDown();
log.debug("end...");
}).start();
new Thread(()->{
log.debug("begin...");
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 计数减1
countDownLatch.countDown();
log.debug("end...");
}).start();
log.debug("waiting...");
// await() 用来等待计数归零
countDownLatch.await();
// 当计数值变为0之后,被await方法阻塞的线程将会唤醒
log.debug("waiting end....");
}
}
执行结果:
19:23:34.930 [pool-1-thread-2] DEBUG com.example.aa.TestCountDownLatch - begin...
19:23:34.930 [pool-1-thread-3] DEBUG com.example.aa.TestCountDownLatch - begin...
19:23:34.930 [pool-1-thread-1] DEBUG com.example.aa.TestCountDownLatch - begin...
19:23:34.932 [pool-1-thread-4] DEBUG com.example.aa.TestCountDownLatch - waiting...
19:23:35.933 [pool-1-thread-1] DEBUG com.example.aa.TestCountDownLatch - end...2
19:23:36.432 [pool-1-thread-3] DEBUG com.example.aa.TestCountDownLatch - end...1
19:23:36.933 [pool-1-thread-2] DEBUG com.example.aa.TestCountDownLatch - end...0
19:23:36.933 [pool-1-thread-4] DEBUG com.example.aa.TestCountDownLatch - waiting end....
可以配合线程池使用,改进如下:
@Slf4j
public class TestCountDownLatch {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(3);
ExecutorService executorService = Executors.newFixedThreadPool(4);
executorService.submit(()->{
log.debug("begin...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 计数减1
countDownLatch.countDown();
log.debug("end...{}",countDownLatch.getCount());
});
executorService.submit(()->{
log.debug("begin...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 计数减1
countDownLatch.countDown();
log.debug("end...{}",countDownLatch.getCount());
});
executorService.submit(()->{
log.debug("begin...");
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 计数减1
countDownLatch.countDown();
log.debug("end...{}",countDownLatch.getCount());
});
executorService.submit(()->{
log.debug("waiting...");
// await() 用来等待计数归零
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 当计数值变为0之后,被await方法阻塞的线程将会唤醒
log.debug("waiting end....");
});
}
}
结合上述示例的运行结果,梳理一下CountDownLatch的使用步骤:
(1)创建倒数闩,初始化CountDownLatch时设置倒数的总次数,比如为3。
(2)等待线程调用倒数闩的await()方法阻塞自己,等待倒数闩的计数器数值为0(倒数线程全部执行结束)。
(3)倒数线程执行完,调用CountDownLatch.countDown()方法将计数器数值减一。
2.3 CyclicBarrie
用来进行线程协作,等待线程满足某个计数。构造时设置『计数个数』,每个线程执 行到某个需要“同步”的时刻调用 await() 方法进行等待,当等待的线程数满足『计数个数』时,继续执行
@Slf4j
public class TestCountDownLatch {
public static void main(String[] args) throws InterruptedException {
CyclicBarrier cb = new CyclicBarrier(2); // 个数为2时才会继续执行
new Thread(()->{
System.out.println("线程1开始.."+new Date());
try {
cb.await(); // 当个数不足时,等待
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("线程1继续向下运行..."+new Date());
}).start();
new Thread(()->{
System.out.println("线程2开始.."+new Date());
try { Thread.sleep(2000); } catch (InterruptedException e) { }
try {
cb.await(); // 2 秒后,线程个数够2,继续运行
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("线程2继续向下运行..."+new Date());
}).start();
}
}
执行结果:
线程1开始..Mon Mar 28 19:32:21 CST 2022
线程2开始..Mon Mar 28 19:32:21 CST 2022
线程2继续向下运行...Mon Mar 28 19:32:23 CST 2022
线程1继续向下运行...Mon Mar 28 19:32:23 CST 2022
CyclicBarrier 与 CountDownLatch 的主要区别在于 CyclicBarrier 是可以重用的,CyclicBarrier 可以被比 喻为『人满发车』
Semaphore、CountDownLatch二者都是基于共享锁实现的,用于在线程之间进行操作同步的工具类。JUC的同步工具类一共有3个:Semaphore、CountDownLatch和CyclicBarrier。