当前位置:   article > 正文

JUC(Java.util.concurrent)的常见类_juc下以concurrent开头的集合类有哪些

juc下以concurrent开头的集合类有哪些

1.ReentrantLock

可重入互斥锁。和synchronized定位类似,都是使用实现互斥效果,保证线程安全。

ReentrantLock的用法:

  • lock():加锁,如果获取不到锁就会死等。
  • trylock(超时时间):加锁,如果获取不到锁,等待一定的时间之后就放弃加锁。
  • unlock:解锁。
  1. import java.util.concurrent.locks.ReentrantLock;
  2. /**
  3. * Describe:ReentrantLock中lock的使用
  4. * User:lenovo
  5. * Date:2023-03-30
  6. * Time:15:58
  7. */
  8. public class TestDemo1 {
  9. public static ReentrantLock lock = new ReentrantLock();
  10. public static int count = 0;
  11. public static void main(String[] args) throws InterruptedException {
  12. Thread t1 = new Thread(() -> {
  13. lock.lock();
  14. try {
  15. for (int i = 0; i < 1000; i++) {
  16. count++;
  17. }
  18. }finally {
  19. lock.unlock();
  20. }
  21. });
  22. Thread t2 = new Thread(() -> {
  23. lock.lock();
  24. try {
  25. for (int i = 0; i < 1000; i++) {
  26. count++;
  27. }
  28. }finally {
  29. lock.unlock();
  30. }
  31. });
  32. t1.start();
  33. t2.start();
  34. t1.join();
  35. t2.join();
  36. System.out.println(count);
  37. }
  38. }

ReentrantLock和synchronized的区别

  • synchronized是一个关键字,是JVM内部实现的(大概率是基于C++实现的)。ReentrantLock是标准库中的一个类,在JVM外实现的(基于Java实现的)
  • synchronized使用时不用手动释放锁。ReentrantLock使用时需要手动释放锁。使用起来更加灵活,但是也容易遗漏unlock.(如果程序抛出异常,或中途跳出,容易导致忘记释放锁)
  • synchronized在申请锁失败的时候,会出现死等的情况。ReentrantLock可以通过trylock的方式等待一段时间就放弃
  • synchronized是非公平锁ReentrantLock默认是非公平锁。可以通过构造方法传入一个true开启公平锁模式。
  •  更强大的唤醒机制。synchronized是通过Object的wait/notify实现等待-唤醒。每次唤醒的是一个随机等待的线程。ReentrantLock搭配Condition类实现等待-唤醒,可以更精准控制唤醒某个指定的线程。

如何选择那个锁呢?

  • 锁竞争不激烈的时候,使用synchronized,效率更高,自动释放更方便。
  • 锁竞争激烈的时候,使用ReentrantLock,搭配trylock更加灵活控制加锁的行为,而不是死等。
  • 如果需要使用公平锁,使用ReentrantLock.

2.原子类

原子类内部使用的是CAS实现,所以性能要比加锁实现i++高了好多。原子类有:

  • AtomicBoolean
  • AtomicInteger
  • AtomicIntegerArray
  • AtomicIntegerLong
  • AtomicReference
  • AtomicStampedReference

AtomicInteger举例,常见的方法有:

addAndGet(int delta);       i += delta;

decrementAndGet();        --i;

getAndDecrement();        i--;

incrementAndGet();         ++i;

getAndIncrement();          i++;

  1. public class TestDemo3 {
  2. public static void main(String[] args) {
  3. AtomicInteger a = new AtomicInteger(0);
  4. System.out.println(a.incrementAndGet());//++a
  5. System.out.println(a.getAndIncrement());//a++
  6. System.out.println(a.decrementAndGet());//--a
  7. System.out.println(a.getAndDecrement());//a--
  8. }
  9. }

 3.线程池

虽然创建销毁线程比创建销毁进程更轻量,但是在频繁的创建和销毁线程的时候还是会比较低效的

线程池就是为了解决这个问题。如果某个线程不在使用了,并不是真正的把线程释放了,而是放到一个池子里。如果需要用到线程就直接从池子中取,不必通过系统来创建。

3.1ExecutorService 和 Executors

代码示例

  • ExecutorService表示一个线程池示例。
  • Executors是一个工厂类,能够创建及几种不同风格的线程池
  • ExecutorService的submit方法能够向线程池中提交若干个任务。
  1. public class TestDemo4 {
  2. public static void main(String[] args) {
  3. ExecutorService pool = Executors.newFixedThreadPool(10);
  4. pool.submit(new Runnable() {
  5. @Override
  6. public void run() {
  7. System.out.println("hello");
  8. }
  9. });
  10. }
  11. }

Executors创建线程池的几种方式

  • newFixedThreadPool:创建固定线程数量的线程池;
  • newCachedThreadPool:创建线程数量动态增长的线程池;
  • newSingleThreadExecutor:创建只包含单个线程的线程池;
  • newScheduledThreadPool:设定延迟时间后执行命令,或者定期执行命令。是进阶版的Timer

Executors本质上是ThreadPoolExecutor类的封装。

3.2ThreadPoolExecutor

ThreadPoolExecutor提供了更多的可选参数,可以进一步细化线程池行为的设定。

构造方法】 

理解ThreadPoolExecutor构造方法的参数

把创建一个线程可以想象为开个公司,每个员工相当于一个线程。

  • corePoolSize:正式员工的数量(线程一旦被创建就不会销毁)
  • maximumPoolSize:正式员工 + 临时工(线程一段时间不使用,就会销毁)
  • keepAliveTime:临时工允许的空闲时间;
  • unit:keepAliveTime的时间单位,是秒,分钟,还是其他的值;
  • workQueue:传递任务的阻塞队列
  • threadFacktory:创建线程的工厂,参与具体的创建线程工作;
  • RejectedExecutionHandle:拒绝策略,如果任务量超过公司的接下来怎么处理

拒绝策略

  1. AbortPolicy():超过负荷,直接抛出异常;
  2. CallerRunsPolicy:调用者负责处理;
  3. DiscardOldestPolicy():丢弃队列中最老的任务;
  4. DiscardPolicy():丢弃最新的任务。
  1. public class TestDemo5 {
  2. public static void main(String[] args) {
  3. ExecutorService pool = new ThreadPoolExecutor(4, 8, 1000, TimeUnit.MICROSECONDS,
  4. new SynchronousQueue<Runnable>(),
  5. Executors.defaultThreadFactory(),
  6. new ThreadPoolExecutor.AbortPolicy());
  7. for (int i = 0; i < 9; i++) {
  8. pool.submit(new Runnable() {
  9. @Override
  10. public void run() {
  11. System.out.println("hello");
  12. }
  13. });
  14. }
  15. }
  16. }

4.信号量

信号量:用来表示可用资源的个数。本质上是一个计数器。

理解信号量】可以把信号量理解为停车场的指示牌:当前由100个车位,表示有100个车位是空闲的。当有一辆车进入的时候,相当于申请一个资源,数量就会-1;当有一辆车出来的时候,相当于释放资源,数量就会+1。同样的栗子,有火车票剩余数量,酒店空闲房间等。

Semaphore的PV操作中加减计数器操作都是原子的,可以在多线程的环境下使用。

代码示例

  • 创建Semaphore示例,初始化为1,表示有1个可用的资源;
  • acquire方法表示申请资源(P操作),release方法表示释放资源(V操作)
  • 创建20个线程,每个线程都尝试申请资源,sleep1秒之后释放资源。
  1. public class TestDemo6 {
  2. public static void main(String[] args) {
  3. Semaphore semaphore = new Semaphore(1);
  4. Runnable runnable = new Runnable() {
  5. @Override
  6. public void run() {
  7. try {
  8. System.out.println("申请资源");
  9. semaphore.acquire();
  10. System.out.println("获取到资源了");
  11. Thread.sleep(1000);
  12. semaphore.release();
  13. System.out.println("释放资源了");
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. }
  18. };
  19. for (int i = 0; i < 20; i++) {
  20. Thread t = new Thread(runnable);
  21. t.start();
  22. }
  23. }
  24. }

 5.CountDownLatch

同时等待N个任务执行结束

【举个栗子】好像跑步比赛,10个选手依次就位,哨声响起同时出发;所有选手到达终点,比赛此结束。

  • 构造CountDownLatch,初始化10,表示有10个任务需要完成。
  • 每个任务执行完毕,都调用latch.countDown().在CountDownLatch内部的计数器同时自减;
  • 主线程中使用latch.await();阻塞等待所有任务执行完成。相当于计数器为0了。
  1. public class TestDemo7 {
  2. public static void main(String[] args) throws InterruptedException {
  3. CountDownLatch latch = new CountDownLatch(10);
  4. Runnable r = new Runnable() {
  5. @Override
  6. public void run() {
  7. System.out.println("hello");
  8. try {
  9. Thread.sleep(1000);
  10. latch.countDown();
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. }
  15. };
  16. for (int i = 0; i < 10; i++) {
  17. new Thread(r).start();
  18. }
  19. latch.await();
  20. System.out.println("比赛结束");
  21. }
  22. }

6.相关面试题

1)线程同步的方式有哪些?

synchronized,ReentrantLock,Semaphore等都可以用于线程同步。

2)为什么有了synchronized,还需要juc下的lock?

以juc的ReentrantLock为例,

  • synchronized使用时不需要手动释放锁。ReentrantLock需要手动释放锁,使用起来更灵活。
  • synchronized在申请锁失败的时候,会死等。ReentrantLock可以通过trylock的方式等待一段时间就放弃了。
  • synchronized是非公平锁。ReentrantLock默认是非公平锁,可以通过构造方法传入一个true开启公平锁模式。
  • synchronized是通过Object的wait/notify实现等待唤醒。每次唤醒的是一个随机等待的线程。ReentrantLock搭配Condition类实现等待-唤醒,更加精确控制某个指定的线程

3)AtomicInteger实现的原理是什么?

基于CAS机制来实现

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号