当前位置: 首页 > news >正文

Java EE初阶启程记13---JUC(java.util.concurrent) 的常见类

 🔥个人主页:寻星探路

🎬作者简介:Java研发方向学习者

📖个人专栏: 、《

⭐️人生格言:没有人生来就会编程,但我生来倔强!!!



目录

一、Callable 接口

二、ReentrantLock

1、原子类

2、线程池

2.1ExecutorService 和 Executors

2.2ThreadPoolExecutor

2.3线程池的工作流程

3、信号量Semaphore

4、CountDownLatch

5、相关面试题


一、Callable 接口

        Callable 是一个interface,相当于把线程封装了一个"返回值",方便程序猿借助多线程的方式计算结果。

代码示例:创建线程计算1+2+3+...+1000,不使用Callable版本

• 创建一个类Result,包含一个sum表示最终结果,lock表示线程同步使用的锁对象。

• main方法中先创建Result实例,然后创建一个线程 t ,在线程内部计算1+2+3+...+1000。

• 主线程同时使用wait等待线程 t 计算结束。(注意,如果执行到wait之前,线程 t 已经计算完了,就不必等待了)。

• 当线程 t 计算完毕后,通过notify唤醒主线程,主线程再打印结果。

 static class Result {public int sum = 0;public Object lock = new Object();}public static void main(String[] args) throws InterruptedException {Result result = new Result();Thread t = new Thread() {@Overridepublic void run() {int sum = 0;for (int i = 1; i <= 1000; i++) {sum += i;}synchronized (result.lock) {result.sum = sum;result.lock.notify();}}};t.start();synchronized (result.lock) {while (result.sum == 0) {result.lock.wait();}System.out.println(result.sum);}}

        可以看到,上述代码需要一个辅助类Result,还需要使用一系列的加锁和waitnotify操作,代码复杂,容易出错。

代码示例:创建线程计算1+2+3+...+1000,使用Callable版本

• 创建一个匿名内部类,实现Callable接口,Callable带有泛型参数,泛型参数表示返回值的类型。

• 重写Callable的call方法,完成累加的过程,直接通过返回值返回计算结果。

• 把callable实例使用FutureTask包装一下。

• 创建线程,线程的构造方法传入FutureTask,此时新线程就会执行FutureTask内部的Callable的 call 方法,完成计算,计算结果就放到了FutureTask对象中。

• 在主线程中调用 futureTask.get() 能够阻塞等待新线程计算完毕,并获取到FutureTask中的结果。

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;public class Main {public static void main(String[] args) throws ExecutionException, InterruptedException {Callable<Integer> callable = new Callable<Integer>() {@Overridepublic Integer call() throws Exception {int sum = 0;for (int i = 1; i <= 1000; i++) {sum += i;}return sum;}};FutureTask<Integer> futureTask = new FutureTask<>(callable);Thread t = new Thread(futureTask);t.start();int result = futureTask.get();System.out.println(result);}
}

可以看到,使用Callable和FutureTask之后,代码简化了很多,也不必手动写线程同步代码了。

理解Callable

        Callable 和 Runnable相对,都是描述一个"任务",Callable描述的是带有返回值的任务,Runnable 描述的是不带返回值的任务。

        Callable 通常需要搭配FutureTask来使用,FutureTask用来保存Callable的返回结果,因为Callable 往往是在另一个线程中执行的,啥时候执行完并不确定。

        FutureTask 就可以负责这个等待结果出来的⼯作。

理解FutureTask

        想象去吃麻辣烫,当餐点好后,后厨就开始做了。同时前台会给你一张"小票",这个小票就是 FutureTask,后⾯我们可以随时凭这张小票去查看自己的这份麻辣烫做出来了没。

二、ReentrantLock

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

ReentrantLock 也是可重入锁,Reentrant这个单词的原意就是"可重入"。

ReentrantLock 的用法:

• lock():加锁,如果获取不到锁就死等。

• trylock(超时时间):加锁,如果获取不到锁,等待一定的时间之后就放弃加锁。

• unlock():解锁。

ReentrantLock lock = new ReentrantLock();
----------------------------------------lock.lock();   
try {    // working    
} finally {    lock.unlock()    
}  

ReentrantLock 和 synchronized 的区别:

• synchronized是一个关键字,是JVM内部实现的(大概率是基于C++实现),ReentrantLock是标准 库的一个类,在JVM外实现的(基于Java实现)。

• synchronized使用时不需要手动释放锁,ReentrantLock使用时需要手动释放,使用起来更灵活,但是也容易遗漏unlock。

• synchronized在申请锁失败时,会死等,ReentrantLock可以通过trylock的方式等待一段时间就放 弃。

• synchronized是非公平锁,ReentrantLock默认是非公平锁,可以通过构造方法传入一个true开启 公平锁模式。

// ReentrantLock 的构造⽅法 
public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();
}

• 更强大的唤醒机制,synchronized是通过Object的wait/notify实现等待---唤醒,每次唤醒的是一个 随机等待的线程,ReentrantLock搭配Condition类实现等待---唤醒,可以更精确控制唤醒某个指定 的线程。

如何选择使用哪个锁?

• 锁竞争不激烈的时候,使用synchronized,效率更高,自动释放更方便。

• 锁竞争激烈的时候,使用ReentrantLock,搭配trylock更灵活控制加锁的行为,而不是死等。

• 如果需要使用公平锁,使用ReentrantLock。

1、原子类

原子类内部用的是CAS实现,所以性能要比加锁实现i++高很多。原子类有以下几个

• AtomicBoolean

• AtomicInteger

• AtomicIntegerArray

• AtomicLong

• AtomicReference

• AtomicStampedReference

以AtomicInteger 举例,常见方法有

addAndGet(int delta);   i += delta;
decrementAndGet();                --i;
getAndDecrement();                i--;
incrementAndGet();                ++i;
getAndIncrement();                i++;

2、线程池

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

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

2.1ExecutorService 和 Executors

代码示例:

• ExecutorService 表示一个线程池实例。

• Executors是一个工厂类,能够创建出几种不同风格的线程池。

• ExecutorService 的 submit 方法能够向线程池中提交若干个任务。

 ExecutorService pool = Executors.newFixedThreadPool(10);pool.submit(new Runnable() {@Overridepublic void run() {System.out.println("hello");}});

Executors 创建线程池的几种方式:

• newFixedThreadPool:创建固定线程数的线程池。

• newCachedThreadPool:创建线程数目动态增长的线程池。

• newSingleThreadExecutor:创建只包含单个线程的线程池。

• newScheduledThreadPool:设定延迟时间后执行命令,或者定期执行命令,是进阶版的Timer。

Executors 本质上是ThreadPoolExecutor类的封装。

2.2ThreadPoolExecutor

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

ThreadPoolExecutor 的构造方法

**理解ThreadPoolExecutor构造方法的参数**

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

• corePoolSize:正式员工的数量。(正式员工,一旦录用,永不辞退)

• maximumPoolSize:正式员工 + 临时工的数目。(临时工:一段时间不干活,就被辞退)

• keepAliveTime:临时工允许的空闲时间。

• unit:keepaliveTime 的时间单位、是秒、分钟,还是其他值。

• workQueue:传递任务的阻塞队列。

• threadFactory:创建线程的工厂,参与具体的创建线程工作。

• RejectedExecutionHandler:拒绝策略,如果任务量超出公司的负荷了接下来怎么处理。

        ◦ AbortPolicy():超过负荷,直接抛出异常。

        ◦ CallerRunsPolicy():调用者负责处理。

        ◦ DiscardOldestPolicy():丢弃队列中最老的任务。

        ◦ DiscardPolicy():丢弃新来的任务。

代码示例:

 ExecutorService pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS,new SynchronousQueue<Runnable>(),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicyfor(int i=0;i<3;i++) {pool.submit(new Runnable() {@Overridevoid run() {System.out.println("hello");}});}

2.3线程池的工作流程

3、信号量Semaphore

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

理解信号量

可以把信号量想象成是停车场的展示牌:当前有车位100个,表示有100个可用资源。

当有车开进去的时候,就相当于申请一个可用资源,可用车位就-1(这个称为信号量的P操作)

当有车开出来的时候,就相当于释放一个可用资源,可用车位就+1(这个称为信号量的V操作)

如果计数器的值已经为0了,还尝试申请资源,就会阻塞等待,直到有其他线程释放资源。

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

代码示例

• 创建Semaphore示例,初始化为4,表示有4个可用资源。

• acquire方法表示申请资源(P操作),release方法表示释放资源(V操作)。

• 创建20个线程,每个线程都尝试申请资源,sleep1秒之后,释放资源,观察程序的执行效果。

 Semaphore semaphore = new Semaphore(4);Runnable runnable = new Runnable() {@Overridepublic void run() {try {System.out.println("申请资源");semaphore.acquire();System.out.println("我获取到资源了");Thread.sleep(1000);System.out.println("我释放资源了");semaphore.release();} catch (InterruptedException e) {e.printStackTrace();}}};for (int i = 0; i < 20; i++) {Thread t = new Thread(runnable);t.start();}

4、CountDownLatch

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

好像跑步比赛,10个选⼿依次就位,哨声响才同时出发;所有选手都通过终点,才能公布成绩。

• 构造CountDownLatch实例,初始化10表示有10个任务需要完成。

• 每个任务执行完毕,都调用 latch.countDown() 。在CountDownLatch内部的计数器同时自减。

• 主线程中使用 latch.await();阻塞等待所有任务执行完毕,相当于计数器为0了。

 public class Demo {public static void main(String[] args) throws Exception {CountDownLatch latch = new CountDownLatch(10);Runnable r = new Runable() {@Overridepublic void run() {try {Thread.sleep(Math.random() * 10000);latch.countDown();} catch (Exception e) {e.printStackTrace();}}};for (int i = 0; i < 10; i++) {new Thread(r).start();}// 必须等到 10 ⼈全部回来 latch.await();System.out.println("⽐赛结束");}}

5、相关面试题

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机制,伪代码如下:

 class AtomicInteger {private int value;public int getAndIncrement() {int oldValue = value;while ( CAS(value, oldValue, oldValue+1) != true) {oldValue = value;}return oldValue;}}

4)信号量听说过么?之前都用在过哪些场景下?

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

使用信号量可以实现"共享锁",比如某个资源允许3个线程同时使用,那么就可以使用P操作作为加锁,V操作作为解锁,前三个线程的P操作都能顺利返回,后续线程再进行P操作就会阻塞等待,直到前面的线程执行了V操作。

http://www.dtcms.com/a/477583.html

相关文章:

  • 25.负载均衡-Nginx、HAProxy、LVS 全解析
  • ubantu的adb命令(首次安装adb)
  • 辽宁平台网站建设哪里好电商网站怎样优化
  • 万商惠网站建设系统开发人才网站建设经费用途
  • 欧普建站做网站需要apache
  • 天津做网站企业天津定制网站建设商店设计
  • 天猫网站设计大连市那里做网站宣传的好
  • Linux curl 与 wget 区别
  • Centos7详细安装过程
  • SpringBoot 集成 LangChain4j RAG PostgreSQL 搜索
  • 扫地机器人算法分析
  • C语言——深入解析C语言指针:从基础到实践从入门到精通(二)
  • JSAR 空间小程序开发全指南:从环境搭建到跨场景应用落地
  • 驻马店网站建设价格上海人才市场招聘网
  • http 长链接和短链接
  • Java:将 Word 文档转换为密码保护的 PDF 文件
  • 267-基于Django的携程酒店数据分析推荐系统
  • Redis中Geospatial 实际应用指南
  • React水合技术:优化SSR和CSR的完美结合
  • 【六级】全国大学英语六级历年真题及答案解析PDF电子版(2015-2025年6月)
  • Adware Zap - Malware Cleaner for Mac v2.12.0 轻量级广告和恶意软件清理工具
  • 从底层到上层的“外挂”:deque、stack、queue、priority_queue 全面拆解
  • 淘客网站做弹窗广告注册公司的网址是什么
  • 域名是否就是网站网站建站网站建站
  • 李宏毅机器学习笔记21
  • 自动化脚本快速批量处理
  • 哈尔滨建工建设有限公司织梦网站后台如何做百度优化
  • 第 96 场周赛:三维形体投影面积、救生艇、索引处的解码字符串、细分图中的可到达节点
  • 网站建设宁夏凤凰云什么是电子商务系统
  • 用php做电子商务网站微信做商城网站