聊一聊Java中那些常见的并发控制手段

复制代码

1.3 锁

jdk本身提供了不少的锁,为了实现单实例的并发控制,我们需要选择写锁;如果支持多读,单实例写,则可以考虑读写锁;一般使用姿势也比较简单

private void doSome(ReentrantReadWriteLock.WriteLock writeLock) {

try {

writeLock.lock();

System.out.println("持有锁成功 " + Thread.currentThread().getName());

Thread.sleep(1000);

System.out.println("执行完毕! " + Thread.currentThread().getName());

writeLock.unlock();

} catch (Exception e) {

e.printStackTrace();

}

}

@Test

public void lock() throws InterruptedException {

ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();

new Thread(()->doSome(reentrantReadWriteLock.writeLock())).start();

new Thread(()->doSome(reentrantReadWriteLock.writeLock())).start();

new Thread(()->doSome(reentrantReadWriteLock.writeLock())).start();

Thread.sleep(20000);

}

复制代码

1.4 阻塞队列

借助同步阻塞队列,也可以实现并发控制的效果,比如队列中初始化n个元素,每次消费从队列中获取一个元素,如果拿不到则阻塞;执行完毕之后,重新塞入一个元素,这样就可以实现一个简单版的并发控制

demo版演示,下面指定队列长度为2,表示最大并发数控制为2;设置为1时,可以实现单线程的访问控制

AtomicInteger cnt = new AtomicInteger();

private void consumer(LinkedBlockingQueue queue) {

try {

// 同步阻塞拿去数据

int val = queue.take();

Thread.sleep(2000);

System.out.println("成功拿到: " + val + " Thread: " + Thread.currentThread());

} catch (InterruptedException e) {

e.printStackTrace();

} finally {

// 添加数据

System.out.println("结束 " + Thread.currentThread());

queue.offer(cnt.getAndAdd(1));

}

}

@Test

public void blockQueue() throws InterruptedException {

LinkedBlockingQueue queue = new LinkedBlockingQueue<>(2);

queue.add(cnt.getAndAdd(1));

queue.add(cnt.getAndAdd(1));

new Thread(() -> consumer(queue)).start();

new Thread(() -> consumer(queue)).start();

new Thread(() -> consumer(queue)).start();

new Thread(() -> consumer(queue)).start();

Thread.sleep(10000);

}

复制代码

1.5 信号量Semaphore

上面队列的实现方式,可以使用信号量Semaphore来完成,通过设置信号量,来控制并发数

private void semConsumer(Semaphore semaphore) {

try {

//同步阻塞,尝试获取信号

semaphore.acquire(1);

System.out.println("成功拿到信号,执行: " + Thread.currentThread());

Thread.sleep(2000);

System.out.println("执行完毕,释放信号: " + Thread.currentThread());

semaphore.release(1);

} catch (Exception e) {

e.printStackTrace();

}

}

@Test

public void semaphore() throws InterruptedException {

Semaphore semaphore = new Semaphore(2);

new Thread(() -> semConsumer(semaphore)).start();

new Thread(() -> semConsumer(semaphore)).start();

new Thread(() -> semConsumer(semaphore)).start();

new Thread(() -> semConsumer(semaphore)).start();

new Thread(() -> semConsumer(semaphore)).start();

Thread.sleep(20_000);

}

复制代码

1.6 计数器CountDownLatch

计数,应用场景更偏向于多线程的协同,比如多个线程执行完毕之后,再处理某些事情;不同于上面的并发数的控制,它和栅栏一样,更多的是行为结果的统一

这种场景下的使用姿势一般如下

  • 重点:countDownLatch 计数为0时放行

@Test

public void countDown() throws InterruptedException {

CountDownLatch countDownLatch = new CountDownLatch(2);

new Thread(() -> {

try {

System.out.println("do something in " + Thread.currentThread());

Thread.sleep(2000);

} catch (InterruptedException e) {

e.printStackTrace();

} finally {

countDownLatch.countDown();

}

}).start();

new Thread(() -> {

try {

System.out.println("do something in t2: " + Thread.currentThread());

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

} finally {

countDownLatch.countDown();

}

}).start();

countDownLatch.await();

System.out.printf(“结束”);

}

复制代码

1.7 栅栏 CyclicBarrier

CyclicBarrier的作用与上面的CountDownLatch相似,区别在于正向计数+1, 只有达到条件才放行; 且支持通过调用reset()重置计数,而CountDownLatch则不行

一个简单的demo

private void cyclicBarrierLogic(CyclicBarrier barrier, long sleep) {

// 等待达到条件才放行

try {

System.out.println("准备执行: " + Thread.currentThread() + " at: " + LocalDateTime.now());

Thread.sleep(sleep);

int index = barrier.await();

System.out.println("开始执行: " + index + " thread: " + Thread.currentThread() + " at: " + LocalDateTime.now());

} catch (Exception e) {

e.printStackTrace();

}

}

@Test

public void testCyclicBarrier() throws InterruptedException {

// 到达两个工作线程才能继续往后面执行

CyclicBarrier barrier = new CyclicBarrier(2);

// 三秒之后,下面两个线程的才会输出 开始执行

new Thread(() -> cyclicBarrierLogic(barrier, 1000)).start();

new Thread(() -> cyclicBarrierLogic(barrier, 3000)).start();

Thread.sleep(4000);

// 重置,可以再次使用

barrier.reset();

new Thread(() -> cyclicBarrierLogic(barrier, 1)).start();

new Thread(() -> cyclicBarrierLogic(barrier, 1)).start();

Thread.sleep(10000);

}

复制代码

1.8 guava令牌桶

guava封装了非常简单的并发控制工具类RateLimiter,作为单机的并发控制首选

一个控制qps为2的简单demo如下:

private void guavaProcess(RateLimiter rateLimiter) {

try {

// 同步阻塞方式获取

面试准备+复习分享:

为了应付面试也刷了很多的面试题与资料,现在就分享给有需要的读者朋友,资料我只截取出来一部分哦

秋招|美团java一面二面HR面面经,分享攒攒人品

加入社区:https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0
000);

}

复制代码

1.8 guava令牌桶

guava封装了非常简单的并发控制工具类RateLimiter,作为单机的并发控制首选

一个控制qps为2的简单demo如下:

private void guavaProcess(RateLimiter rateLimiter) {

try {

// 同步阻塞方式获取

面试准备+复习分享:

为了应付面试也刷了很多的面试题与资料,现在就分享给有需要的读者朋友,资料我只截取出来一部分哦

[外链图片转存中…(img-jPEvN0o9-1725615044393)]

加入社区:https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值