• JUC常见类,线程池基础及死锁问题


    目录

    JUC常见类

    ReentranctLock

    synchronized和ReentrantLock 的区别

    Callable接口

    线程池

    线程池重要参数

    线程池的工作流程

     创建线程的几种方式

    Executors 和ExecutorService

    线程池的优点

    在使用线程池时,线程数目如何设置 ?? 设置多少合适??

    模拟实现线程池

    信号量(semaphore)

    CountDownLatch

    多线程集合类

    多线程使用ArrayList

    多线程环境使用队列

    多线程使用哈希表


    JUC常见类

    什么是JUC呢??

    JUC是java的一个包--java.util.current包里的类.

    ReentranctLock

    ReentractLock是一种可重入锁,来保证线程安全

    ReentranctLock是JUC包下的一个类,通常会有三种操作,lock,unlcok,trylock

    • lock : 加锁,如果获取不到锁就一直等其他线程释放锁
    • unlock : 解锁
    • tryLock : 加锁,如果获取不到锁,可以设置等待时间,过了时间就不在等待

    synchronized和ReentrantLock 的区别

    • synchronized是关键字,将需要加锁的内容放到synchronized代码块内部,而ReentranctLock是一个JUC包下的类,通过调用方法来加锁解锁
    • 内容在synchronized代码块内部相当于加锁,出了代码块相当于解锁,而ReentranctLock需要调用lock方法进行加锁,unlock方法解锁,还可以使用tryLock设置加锁时间,超过时间,则放弃加锁
    • synchronized是非公平锁,ReentranctLock默认也是非公平锁,而在ReentranctLock关键字的构造方法内部设置true来表示公平锁
    • synchronized锁的通知唤醒调用wait/notify,notify是随机唤醒一个,而ReentranctLock通过调用condition方法来唤醒,既可以随机唤醒,也可以指定唤醒
    • synchronized不需要手动释放锁,而ReentranctLock需要手动释放锁调用Unlock

    Callable接口

    Callable是一个接口,也是用来描述任务的,callable与RUNNABLE不相同RUNNABLE不带有返回值,而Callable带有返回值,通过Callable接口的call方法来计算结果,将任务计算的结果保存到FutureTask对象中,然后Thread构造方法里加上FutureTask对象,最后通过调用FutureTask对象的get方法得到返回值

    • 使用Callable计算1+......+1000
    1. public static void main(String[] args) {
    2. Callable callable = new Callable () {
    3. @Override
    4. public Integer call() throws Exception {
    5. int sum =0;
    6. for(int i =0;i<=1000;++i){
    7. sum+=i;
    8. }
    9. return sum;
    10. }
    11. };
    12. FutureTask futureTask = new FutureTask<>(callable);
    13. Thread t = new Thread(futureTask);
    14. t.start();
    15. }
    • 使用Callable接口调用call方法来执行任务,这个任务带有返回值,返回计算结果
    • callable接口搭配Future对象使用,将返回结果加入到FutureTask对象里面
    • 将FutureTask对象传入线程构造参数中
    • 通过调用FutureTask对象的get方法来返回最终计算结果

    线程池

    什么是线程池 ???

    线程池就是将已创建好的线程放到线程池里,使用线程从池子里取,用完了就放回池子中,重复利用已经创建好的线程.

    线程池重要参数

    •  corePoolSize : 核心线程数----->(简单理解为正式员工)
    • maimumPoolSize : 最大线程数 ---- >(可以理解为总员工数量)
    • keepAlieveTime : 临时线程存活时间--->(允许临时工的空闲时间)
    • unit : 时间的单位
    • workQueue : 任务队列
    • threadFactory : 线程工厂
    • handler : 拒绝策略

    拒绝策略 : 当任务满了,在尝试添加任务,线程池该怎么做

    • AbortPolicy() : 超过负荷,直接抛出异常
    • CallerRunPolicy :告诉调用者该如何处理
    • DisCardOldestPolicy : 丢弃线程中最老的任务
    • DisCardPolicy : 丢弃新来的任务

    线程池的工作流程

     创建线程的几种方式

    • newFixedThreadPool : 创建固定数量线程池
    • newCatchThreadPool : 创建动态增长数量线程池
    • newSingleThreadPool: 创建单一线程的线程池
    • newScheduleThreadPool : 创建定期执行命令

    newFixedThreadPool : 创建固定数量线程池

    1. public static void main1(String[] args) {
    2. //创建固定线程池数量
    3. ExecutorService erService = Executors.newFixedThreadPool(10);
    4. for(int i =0;i<10;++i){
    5. erService.submit(new TimerTask() {
    6. @Override
    7. public void run() {
    8. System.out.println(Thread.currentThread().getName());
    9. }
    10. });
    11. }
    12. }

    1. //submit可以执行有返回值的和无返回值的
    2. public static void main2(String[] args) throws ExecutionException, InterruptedException {
    3. ExecutorService service = Executors.newFixedThreadPool(10);
    4. Future futureTask = service.submit(new Callable(){
    5. @Override
    6. public Integer call(){
    7. int sum =0;
    8. for(int i =0;i<100;++i){
    9. sum+=i;
    10. }
    11. return sum;
    12. }
    13. });
    14. //返回任务结果
    15. System.out.println(futureTask.get());
    16. }

    newCatchThreadPool : 创建动态增长数量线程池

    1. public static void main3(String[] args) {
    2. ExecutorService service = Executors.newCachedThreadPool();
    3. //自定义线程池--->动态增长的线程池--会根据任务的数量,创建线程数量,一定时间内可以重复使用某线程
    4. for(int i =0;i<10;++i){
    5. int finalSz = i;
    6. service.submit(()->{
    7. System.out.println("第" + finalSz + "次" + "线程名"+ Thread.currentThread().getName());
    8. });
    9. }
    10. }

    newSingleThreadPool: 创建单一线程的线程池

    1. public static void main6(String[] args) {
    2. //执行单线程任务线程池
    3. ExecutorService executor = Executors.newSingleThreadExecutor();
    4. for(int i =0;i<10;++i){
    5. executor.submit(new Runnable() {
    6. @Override
    7. public void run() {
    8. System.out.println("线程名"+Thread.currentThread().getName());
    9. }
    10. });
    11. }
    12. }

    newScheduleThreadPool : 创建定期执行命令的线程池

    1. public static void main(String[] args) {
    2. //执行定时任务的线程池
    3. ScheduledExecutorService executor = Executors.newScheduledThreadPool(5);//5个线程
    4. System.out.println("添加任务时间" + LocalDate.now());
    5. executor.schedule(new Runnable() {
    6. @Override
    7. public void run() {
    8. System.out.println("执行子任务"+ LocalDate.now());
    9. }
    10. },2,TimeUnit.SECONDS);
    11. }

    Executors 和ExecutorService

    ExecutorsService是一个工厂类,Executors是静态方法-->利用静态方法创建的实例-->工厂方法

    可以调用Executors的submit方法用来提交多份任务.

    线程池的优点

    • 降低资源消耗;线程池可以重复利用已创建的线程
    • 提高响应速度.任务到达是不需要等到线程创建,就可以立即执行任务
    • 提高线程的可管理性

    在使用线程池时,线程数目如何设置 ?? 设置多少合适??

    答案是不确定的.不同的程序,不同的CPU,不同的主机配置,线程数目是不能确定的.

    但是可以通过压测-->性能测试,根据性能测试,测试出程序使用时间,CPU占用,内存占用来选择合适的线程数目.程序分为IO密集型和CPU密集型,大部分我们既需要IO密集型也需要CPU密集型,两者不同的时间比例也会影响线程数目.

    模拟实现线程池

    1. //实现一个线程池
    2. public class MyThreadPool {
    3. //队列用来存放任务同时要保证线程安全
    4. private BlockingQueue queue = new LinkedBlockingQueue<>();
    5. private void submit(Runnable runnable){
    6. try {
    7. queue.put(runnable);
    8. } catch (InterruptedException e) {
    9. e.printStackTrace();
    10. }
    11. }
    12. private Runnable runnable;
    13. public MyThreadPool(int nThreads){
    14. for(int i =0;i
    15. Thread t = new Thread(()->{
    16. while(!Thread.currentThread().isInterrupted()){
    17. try {
    18. Runnable runnable = queue.take();
    19. runnable.run();
    20. } catch (InterruptedException e) {
    21. e.printStackTrace();
    22. }
    23. }
    24. });
    25. t.start();
    26. }
    27. }
    28. public static int cnt =0;
    29. public static void main(String[] args) {
    30. // ExecutorService e = Executors.newFixedThreadPool(10);
    31. MyThreadPool e = new MyThreadPool(10);
    32. for(int i =0;i<100;++i){
    33. e.submit(new Runnable() {
    34. @Override
    35. public void run() {
    36. System.out.println("hello");
    37. }
    38. });
    39. cnt++;
    40. }
    41. System.out.println("总共执行的hello 数量" + cnt);
    42. }
    43. }

    信号量(semaphore)

    信号量是用来表示可用资源的个数.

    • 申请资源相当于P操作,会使得可用资源个数+1-->acquire
    • 释放资源相当于V操作,会使得可用资源个数-1--->release
    • 当可用资源个数为0时,想要申请资源,就会阻塞等待其他线程释放资源

    信号量类似于停车场的车位,,当有一个车进来时就占上了一个车位(相当于P操作,--申请资源),当有一个车开走了就相当于空出一个车位(P操作,--释放资源),而当停车场车位满了,如果还有人想停车只能等待其他人把车开走空出一个车位.

    代码示例

    • 创建 Semaphore 示例, 初始化为 4, 表示有 4 个可用资源.
    • acquire 方法表示申请资源(P操作), release 方法表示释放资源(V操作)
    • 创建 20 个线程, 每个线程都尝试申请资源, sleep 1秒之后, 释放资源. 观察程序的执行效果.

    CountDownLatch

    CountDownLatch要等待所有子任务都完成,整个任务才算完成

    • CountDownLatch 调用countDown方法代表每个子任务已经完成
    • CountDownLatch 调用 await()方法 等待所有子任务全部完成.

    相当于举行跑步比赛.当发令枪响的时候,所有比赛人员一起跑,当所有人到达终点的时候,这场跑步比赛才结束.

    • 构造 CountDownLatch 实例, 初始化 10 表示有 10 个任务需要完成.
    • 每个任务执行完毕, 都调用 latch.countDown() . 在 CountDownLatch 内部的计数器同时自减.
    • 主线程中使用 latch.await(); 阻塞等待所有任务执行完毕. 相当于计数器为 0 了.

    多线程集合类

    其中我们以前学过一些集合其中有的是线程安全的,有的不是线程安全的.

    eg : Stack,Vector,HashTable  currentHashMap-----是线程安全的

    eg : HashMap, ArrayList,LinkedList.....都是线程不安全的.

    多线程使用ArrayList

    在java.util.current包里也有线程安全的ArrayList的类.

    • Collection.synchronizedList,Collection集合里的类synchronizedList,在关键方法中加了synchronized
    • 还可以在使用ArrayList时加上ReentrantLock或者加上synchronized
    • copyOnWriteArrayList--写时复制的容器

    copyOnwriteArrayList是一种写时复制的容器,当进行修改数据时,会将原有数据拷贝到一个新容器里,然后在新容器里执行修改操作,然后将原有容器指向新的容器.

    CopyOnriteArrayList 适合并发的读操作,因为在修改数据时需要完成大量的拷贝工作,拷贝成本会很高.

    多线程环境使用队列

    • ArrayBlockingQueue--------基于数组实现的阻塞队列

    • LinkedBlockingQueue--------基于链表实现的阻塞队列

    • PriorityBlockingQueue--------基于堆实现的带优先级的阻塞队列

    • TransferQueue   --------  最多只包含一个元素的阻塞队列

    多线程使用哈希表

    单线程环境下我们使用的是HashMap ,而多线程有HashTable (不使用),ConCurrentHashMap(常常使用).

    • HashTable

    HashTable与JDK1.7以前HashMap的底层数据结构类似 数组 + 链表,HashTable是对里面的几个重要方法加锁,相当于对HashTable本身加了一把大锁,在多线程下调用方法使用都会产生锁竞争,这就会导致向不同的哈希桶里添加元素都会导致发生锁竞争,导致效率低下.

    • ConCurrentHashMap

    ConCurrentHashMap在JDK1.7以前采用的是分段锁,每一把锁,只锁一部分数据,访问不同数据段的数据不会产生锁竞争,只有访问相同数据段数据才会产生锁竞争.在JDK1.8底层数据结构采用数组+链表+红黑树,在并发时使用synchronized和CAS机制.

    • ConCurrentHashMap是把锁细化,在每一个哈希桶上都加上一把锁,又因为哈希桶的数量可能会很多,这就导致锁竞争的概率就会很小
    • ConCurrentHashMap采用CAS机制来维护size
    • ConCurrentHashMap优化了扩容机制,比如原来的HashMap执行put操作一口气扩容,而ConCurrentHashMap每次只扩容一小部分,ConCurrentHashMap而是在查询时,使用新表和老表一起查询,当要插入元素时,只往新表中插入,搬运完最后一个元素再把老表删除.
    • ConCurrentHashMap只有写加锁,读不加锁(加了volatile关键字)

    • Hashtable和HashMap、ConcurrentHashMap 之间的区别?

    1. 首先,HashTable和ConCurrentHashMap是线程安全的,而HashMap不是线程安全的
    2. HashTable是对HashTable本身加了一把大锁,导致多个线程修改任意数据都会产生锁冲突,效率低下,而ConCurrentHashMap在JDK 1.8以后将锁的粒度细分,每一个哈希桶都加上一把锁,由于可能桶数量会很多,所以锁冲突会减少,ConcurrentHashMap对扩容机制做了更好地改进,ConCurrentHashMap不是一口气全部完成扩容,而是化整为零,查询时新表旧表一起查询,插入时只往新表中插入,搬运完最后一个元素再把每次只扩容一小部分,最终完成整个扩容过程,同时CurrentHashMap并发时使用synchronized进行加锁并且使用CAS机制维护size
    3. HashTable,HashMap允许key值为null,而ConCurrentHashMap不允许key值为null
  • 相关阅读:
    实战PyQt5: 158-QChart图表之线条缩放显示
    【论文阅读】DPLVO: Direct Point-Line Monocular Visual Odometry
    java线程局部变量ThreadLocal的作用和理解
    设计模式学习笔记(二十一)访问者模式及其实现
    企业网络“卫生”实用指南
    【代码精读】中断路由代码导读:当cpu运行在TEE来了一个Group0中断
    第四章字符串_反转字符串里的单词
    Python批量绘制遥感影像数据的直方图
    Redis内存耗尽后会发生什么?
    【未写完】笔记本电脑Windows7怎么省电
  • 原文地址:https://blog.csdn.net/m0_61210742/article/details/126278471