> 文章列表 > JUC(Java.util.concurrent)的常见类

JUC(Java.util.concurrent)的常见类

JUC(Java.util.concurrent)的常见类

1.ReentrantLock

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

ReentrantLock的用法:

  • lock():加锁,如果获取不到锁就会死等。
  • trylock(超时时间):加锁,如果获取不到锁,等待一定的时间之后就放弃加锁。
  • unlock:解锁。
import java.util.concurrent.locks.ReentrantLock;/* Describe:ReentrantLock中lock的使用* User:lenovo* Date:2023-03-30* Time:15:58*/
public class TestDemo1 {public static ReentrantLock lock = new ReentrantLock();public static int count = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {lock.lock();try {for (int i = 0; i < 1000; i++) {count++;}}finally {lock.unlock();}});Thread t2 = new Thread(() -> {lock.lock();try {for (int i = 0; i < 1000; i++) {count++;}}finally {lock.unlock();}});t1.start();t2.start();t1.join();t2.join();System.out.println(count);}
}

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++;

public class TestDemo3 {public static void main(String[] args) {AtomicInteger a = new AtomicInteger(0);System.out.println(a.incrementAndGet());//++aSystem.out.println(a.getAndIncrement());//a++System.out.println(a.decrementAndGet());//--aSystem.out.println(a.getAndDecrement());//a--}
}

 3.线程池

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

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

3.1ExecutorService 和 Executors

代码示例

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

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():丢弃最新的任务。
public class TestDemo5 {public static void main(String[] args) {ExecutorService pool = new ThreadPoolExecutor(4, 8, 1000, TimeUnit.MICROSECONDS,new SynchronousQueue<Runnable>(),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());for (int i = 0; i < 9; i++) {pool.submit(new Runnable() {@Overridepublic void run() {System.out.println("hello");}});}}
}

4.信号量

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

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

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

代码示例

  • 创建Semaphore示例,初始化为1,表示有1个可用的资源;
  • acquire方法表示申请资源(P操作),release方法表示释放资源(V操作)
  • 创建20个线程,每个线程都尝试申请资源,sleep1秒之后释放资源。
public class TestDemo6 {public static void main(String[] args) {Semaphore semaphore = new Semaphore(1);Runnable runnable = new Runnable() {@Overridepublic void run() {try {System.out.println("申请资源");semaphore.acquire();System.out.println("获取到资源了");Thread.sleep(1000);semaphore.release();System.out.println("释放资源了");} catch (InterruptedException e) {e.printStackTrace();}}};for (int i = 0; i < 20; i++) {Thread t = new Thread(runnable);t.start();}}}

 5.CountDownLatch

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

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

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

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机制来实现