JUC
可重入独占锁 ReentrantLock
可重入的意思是在同一线程中可用多次获取同一把锁而不阻塞,独占锁则是只能被一个线程使用。
如下代码所示,我们有8张票,要用10个线程去抢,肯定会出现两个线程抢不到,但是实际运行结果由于多个线程读到同一个值然后去执行业务操作,就会出现超卖的问题
public class Main {
private static Integer stockCount = 8;
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
buyStock();
}, "thread-" + i).start();
}
}
public static void buyStock() {
if (stockCount > 0) {
System.out.println(Thread.currentThread().getName() + "买到了票");
stockCount--;
System.out.println("剩余票数:" + stockCount);
} else {
System.out.println(Thread.currentThread().getName() + "抢票失败");
}
}
}
就可以使用ReentrantLock来解决这个问题,只要保证一个线程可以去抢票,其他线程等待即可。
package com.lyra;
import java.util.concurrent.locks.ReentrantLock;
public class Main {
private static Integer stockCount = 8;
private static final ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
buyStock();
}, "thread-" + i).start();
}
}
public static void buyStock() {
lock.lock();
try {
if (stockCount > 0) {
System.out.println(Thread.currentThread().getName() + "买到了票");
stockCount--;
System.out.println("剩余票数:" + stockCount);
} else {
System.out.println(Thread.currentThread().getName() + "抢票失败");
}
} finally {
lock.unlock();
}
}
}
锁的使用规范文档中也记录了需要在finally中进行解锁避免锁一直不释放
ReentrantLock的newCondtion可以创建一个Condition对象对线程进行协调。
Condition的两个方法:
- await: 将当前线程加入到Condition的等待队列中,线程暂停执行。
- signal: 从Condition等待队列中唤醒一个线程。
- signalAll: 唤醒等待队列中的所有线程。
使用ReentrantLock可以很好的实现一个生产者消费者的模型,如下这个例子,生产者生产数据,每生产一个数据,那么唤醒消费者进行消费,当队列满了,那么就暂停生产,如果队列空了,那么暂停消费,暂停消费:
Condition providerCondition = lock.newCondition();
Condition consumerCondition = lock.newCondition();
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);
AtomicInteger data = new AtomicInteger(0);
// 生产者线程
for (int i = 0; i < 10; i++) {
new Thread(() -> {
while (true) {
lock.lock();
try {
if (queue.size() != 10) {
System.out.println("生产者生产数据:" + data);
data.getAndIncrement();
queue.add(data.getAcquire());
// 数据生产完成后唤醒消费者进行消费
consumerCondition.signalAll();
} else {
System.out.println("生产队列已满,等待消费");
// 如果队列满了,那么生产者可以等待一下下
providerCondition.await();
providerCondition.signal();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
}, "provider-" + i).start();
}
// 消费者消费
for (int i = 0; i < 10; i++) {
new Thread(() -> {
while (true) {
lock.lock();
try {
if (!queue.isEmpty()) {
Integer ran = queue.poll();
System.out.println("消费者消费数据:" + ran);
// 如果数据消费完毕那么唤醒线程再进行生产
providerCondition.signalAll();
} else {
System.out.println("消费队列已空,等待生产");
// 队列空了,消费者暂停消费
consumerCondition.await();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
}, "consumer-" + i).start();
}
信号量 Semaphore
限制线程同时访问资源的数量,一般用来做资源池限流操作。
可以多个线程同时访问信号量资源。
信号量相关的方法:
- 构造方法: 资源数
- tryAcquire: 尝试获取许可,如果获取不到则返回false
- acquire: 获取许可,可以传参一次性获取多少个许可。
- release: 释放许可,可以传参一次性释放多少个许可。
我们可以实现一个简单的限流程序,首先我们定义了同时有两个线程执行业务处理,我们初始化资源数为2,然后线程内部尝试获取许可,如果获取不到表示被限流了,如果获取到了那么就在执行业务操作。
Semaphore semaphore = new Semaphore(2);
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
if (!semaphore.tryAcquire()) {
System.out.println("已被限流");
} else {
System.out.println("获取到资源,执行业务处理");
}
} finally {
semaphore.release();
}
}).start();
}
闭锁 CountDownLatch
允许线程等待,直到所有线程执行完成然后再继续执行,应用场景就是并行任务汇总,例如有些接口查询比较慢,可以根据操作来分线程进行执行,等到所有线程执行完成后统一进行返回。
他有以下几个方法:
- 构造方法: 初始化计数
- countDown: 线程计数-1
- await: 等待计数为0,所有线程执行完成
比如我有一个业务功能,为了节省查询效率,需要有两个线程同时执行完成业务操作,我们就可以同一让线程全部执行完成再进行返回。
CountDownLatch countDownLatch = new CountDownLatch(2);
new Thread(() -> {
System.out.println("执行业务操作1");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("业务操作1执行完毕");
countDownLatch.countDown();
}).start();
new Thread(() -> {
System.out.println("执行业务操作2");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("业务操作2执行完毕");
countDownLatch.countDown();
}).start();
try {
countDownLatch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("执行完成");
虽然道路是曲折的,但前途是光明的。