1.CountDownLatch概念及使用
1.1概念
倒计时锁CountDownLatch
* 需要合理设计锁的数量,在每个线程中都需要调用countDown()方法
* 主线程调用await()方法,只有当 锁的数量变为 0 的时候,主线程才会继续向下推进
核心方法:
countDown:
- 功能:将计数器的值减1。
- 用途:通常在一个线程完成其工作后调用,以通知其他线程可以继续执行。
- 计数器:当计数器减到零时,所有在
await()
方法上等待的线程将被唤醒。 - 线程安全:此方法是线程安全的,多个线程可以同时调用。
await:
- 功能:使当前线程等待,直到计数器的值为零。
- 用途:当一个线程需要等待其他线程完成某些工作时,可以调用这个方法。
- 阻塞:调用
await()
的线程会被阻塞,直到其他线程调用countDown()
方法将计数器减到零。 - 异常:如果线程在等待时被中断,则会抛出
InterruptedException
。
1.2Demo
public class CountDownLatchDemo01 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(3);
long start = System.currentTimeMillis();
new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("任务1执行了");
countDownLatch.countDown();
}).start();
new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("任务2执行了");
countDownLatch.countDown();
}).start();
new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("任务3执行了");
countDownLatch.countDown();
}).start();
countDownLatch.await();
long end = System.currentTimeMillis();
System.out.println("主线程执行, time = " + (end - start));
}
}
1.3执行结果
首先定义CountDownLatch的数量为3(内部AQS的state=3),同时启动三个线程,每个线程执行完成后,调用countDown方法。
主线程调用await()方法等待,当三个线程执行都调用countDown后,CountDownLatch为0,此时主线程继续执行。time约等于1s。
2.Semaphore概念及使用
2.1概念
Semaphore是一种信号量,它有一个初始的许可数量。线程可以获取一个许可来访问共享资源,释放资源时返回一个许可。(默认是非公平的)
* 特点:
* 许可数量:可以设置信号量的初始许可数量。
* 获取许可:线程可以获取一个许可来访问共享资源。
* 释放许可:线程完成访问后释放一个许可。
核心方法:
1.acquire()
- 功能:获取一个许可(permit)。如果当前没有可用的许可,调用线程会被阻塞,直到有可用的许可。
- 用途:当一个线程需要访问受限资源时,调用此方法来获取许可。
- 阻塞:如果没有可用的许可,调用线程会阻塞,直到其他线程调用
release()
方法释放许可。 - 异常:如果线程在等待许可时被中断,则会抛出
InterruptedException
。
2.release()
- 功能:释放一个许可。增加计数器的值,允许其他等待的线程获取许可。
- 用途:当线程完成对共享资源的访问后,调用此方法以释放许可。
- 注意事项:调用
release()
方法的次数必须与acquire()
方法的次数相匹配。否则,可能会导致IllegalStateException
。 - 线程安全:
release()
是线程安全的,可以被多个线程同时调用。
2.2Demo
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore lock = new Semaphore(3);// 定义三个信号量
for (int i = 0; i < 10; i++){// 同时启动10个线程任务,每秒只能同时有三个任务执行
new Thread(() -> {
try {
lock.acquire();// 获取锁
System.out.println(Thread.currentThread().getName() + " 执行了任务");
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.release();// 释放锁
}
}, "Thread - " + i).start();
}
}
}
2.3执行结果
在执行的过程中,每次都是三个线程同时执行;只有当有一个线程释放信号量后,才会唤醒一个等待线程。(测试代码中,每个线程模拟睡眠1s)
3.CyclicBarrier概念及使用
3.1概念
CyclicBarrier
是 Java 并发包中的一个同步工具,用于使一组线程在某个共同点(即障碍)上相互等待,直到所有线程都到达这个点。它可以重复使用,适合在多次迭代中需要同步的场景。
主要概念
- CyclicBarrier:允许一组线程互相等待,直到到达某个公共屏障。线程在到达屏障时会被阻塞,直到所有参与的线程都到达该屏障。
- 重用性:与
CountDownLatch
不同,CyclicBarrier
可以在所有线程都通过屏障后重置并重新使用。 - 计数器:
CyclicBarrier
维护一个计数器,表示需要到达的线程数量。
主要方法
await()
:线程调用此方法,表示它到达了屏障。如果不是所有参与的线程都调用了await()
,那么调用此方法的线程会被阻塞,直到所有线程都到达屏障。getNumberWaiting()
:返回当前在等待到达屏障的线程数量。getParties()
:返回此屏障需要等待的线程数。
3.2Demo
public class CyclicBarrierDemo {
public static void main(String[] args) {
int numberOfThreads = 5;
CyclicBarrier barrier = new CyclicBarrier(numberOfThreads, () -> {
System.out.println("All parties have arrived at the barrier, let's proceed.");
});
for (int i = 0; i < 7; i++) {// 模拟7个线程 但是只有5个线程可以执行屏障点后的代码
new Thread(new Task(barrier)).start();
}
}
}
class Task implements Runnable {
private CyclicBarrier barrier;
public Task(CyclicBarrier barrier) {
this.barrier = barrier;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " is waiting at the barrier.");
try {
barrier.await();
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " has crossed the barrier.");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
}
3.3执行结果
在这个例子中,模拟启动7个线程,当有5个线程执行await方法后,屏障被打开,5个线程可以继续执行;而另外2个线程只能等待。(如果还存在3个线程调用await,那么屏障会被再次打开,否则这两个线程就一直处于阻塞状态)