【Java】JUC下的常用接口和类

news2024/11/17 19:40:34

  • Callable接口
  • ReentrantLock
    • 常用的方法
    • 创建公平锁
    • 创建读写锁
    • 唤醒机制
    • ReentrantLock与synchronized的区别
  • 原子类
  • 工具类
    • Semaphore
    • CountDownLatch
    • CyclicBarrier-循环栅栏
  • 线程安全的集合类
    • CopyOnWriteArrayList
    • 多线程环境使用队列
    • 多线程环境使用哈希表
      • ConcurrentHashMap

java.util.concurrent包简称JUC,是JDK1.5之后对多线程的一种实现,这个包下的类都和多线程有关。

Callable接口

前面介绍过Runnable接口,它是java.long下的接口,在创建线程时可以使用。Callable是JUC包下描述线程任务的接口。
在这里插入图片描述
1.Callable实现的是call方法,Runnable实现的是run方法;
2.Callable可以返回一个结果,Runnable不能返回结果;
3.Callable要配合FurtureTask使用;
在这里插入图片描述

public class Demo03_Callable {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //先定义一个线程的任务
        Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int sum = 0;
                for (int i = 0; i < 10; i++) {
                    sum += i;
                    TimeUnit.SECONDS.sleep(1);
                    System.out.println("等待1秒");
                }
                //返回sum
                return  sum;
            }
        };

        //通过 FutureTaskl类来创建一个对象,这个对象持有callable
        FutureTask<Integer> futureTask =  new FutureTask<>(callable);
        //创建线程并指定任务
        Thread thread = new Thread(futureTask);
        //让线程执行定义好的任务
        thread.start();
        //获取线程执行的结果,抛出异常
        System.out.println("等待结果....");
        Integer result = futureTask.get();
        //打印结果
        System.out.println(result);
    }
}

4.Callable可以抛出异常,Runnable不可以。

public class Demo04_CallableException {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 先定义一个线程的任务
        Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int sum = 0;
                for (int i = 0; i < 5; i++) {
                    sum += i;
                    TimeUnit.SECONDS.sleep(1);
                    throw new Exception("业务出现异常");
                }
                // 返回结果
                return sum;
            }
        };

        // 通过FutureTask类来创建一个对象,这个对象持有callable
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        // 创建线程并指定任务
        Thread thread = new Thread(futureTask);
        // 让线程执行定义好的任务
        thread.start();
        // 获取线程执行的结果
        System.out.println("等待结果...");
        Integer result = null;
        // 捕获异常
        try {
            result = futureTask.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            System.out.println("处理异常" + e.getMessage());
            e.printStackTrace();
        }
        // 打印结果
        System.out.println(result);
    }
}

ReentrantLock

本身就是一个锁,是基于CAS实现的一个纯用户态的锁。
在这里插入图片描述

常用的方法

1.lock() : 加锁
2.tryLock() : 尝试加锁
3.unLock() :释放锁

    public static void demo01_lock() throws InterruptedException {
        // 创建一个ReentrantLock对象
        ReentrantLock reentrantLock = new ReentrantLock();
        // 加锁
        reentrantLock.lock();
        // 尝试加锁, 死等
        reentrantLock.tryLock();
        // 尝试加锁,有超时时间
        reentrantLock.tryLock(1, TimeUnit.SECONDS);

        // 释放锁
        reentrantLock.unlock();
    }

模拟业务中如果出现异常情况,如何释放锁?将释放锁的操作写在finally中,保证出现异常的时候也可以释放锁。

    public static void demo02 () throws Exception {
        // 创建一个ReentrantLock对象
        ReentrantLock reentrantLock = new ReentrantLock();
        // 加锁
        reentrantLock.lock();
        try {
            // TODO : 业务逻辑
            throw new Exception("业务出现异常");
        } finally {
            // 保证出现异常的时候也可以释放锁
            reentrantLock.unlock();
        }
    }

创建公平锁

通过构造方法中传入true时为公平锁,false为非公平锁,默认为false

    public static void demo03_fair () {
        // 通过构造方法,传入true时为公平锁,false为非公平锁,默认为false
        ReentrantLock reentrantLock = new ReentrantLock(true);
    }

创建读写锁

readLock()和writeLock()

    public static void demo04_ReadWriteLock () {
        // 创建
        ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        // 获取读锁, 共享锁,读与读可以同时进行
        readWriteLock.readLock();
        // 获取写锁,排他锁(互斥锁),读写,写读,写写不能共存
        readWriteLock.writeLock();
    }

唤醒机制

ReentrantLock可以根据不同的Condition去休眠或唤醒线程,同一把锁可以分为不同的休眠或唤醒条件。

public class Demo05_ReentrantLock {
    /**
     * ReentrantLock可以根据不同的Condition去休眠或唤醒线程
     * 同一把锁可以分为不同的休眠或唤醒条件
     */
    private static ReentrantLock reentrantLock = new ReentrantLock();
    // 定义不同的条件
    private static Condition boyCondition = reentrantLock.newCondition();
    private static Condition girlCondition = reentrantLock.newCondition();

    public static void demo05_Condition () throws InterruptedException {
        Thread threadBoy = new Thread(() -> {
            // 让处理男生任务的线程去休眠
            try {
                boyCondition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 唤醒处理女生任务的线程
            girlCondition.signalAll();
        });

        Thread threadGirl = new Thread(() -> {
            // 让处理女生任务的线程去休眠
            try {
                girlCondition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 唤醒处理男生任务的线程
            boyCondition.signalAll();
        });    
    }

ReentrantLock与synchronized的区别

①synchronized 是一个关键字,是 JVM 内部实现的(大概率是基于 C++ 实现).。ReentrantLock 是标准库的一个类,在 JVM 外实现的(基于 Java 实现)。
②synchronized 使用时不需要手动释放锁。ReentrantLock 使用时需要手动释放,使用起来更灵活, 但是也容易遗漏 unlock。
③synchronized在申请锁失败时,会死等。ReentrantLock 可以通过 trylock 的方式等待一段时间就放弃。
④synchronized是非公平锁,ReentrantLock 默认是非公平锁。可以通过构造方法传入一个 true 开启公平锁模式。

原子类

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

AtomicBoolean
AtomicInteger
AtomicIntegerArray
AtomicLong
AtomicReference
AtomicStampedReference

以AtomicInteger 为例

public class Demo01_Atomic {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger();

        // 相当于i++
        atomicInteger.getAndIncrement();
        // 相当于++i
        atomicInteger.incrementAndGet();
        // 相当于i--
        atomicInteger.getAndDecrement();
        // 相当于--i
        atomicInteger.decrementAndGet();
        // 相当于 i + 10
        atomicInteger.getAndAdd(10);
    }
}

工具类

Semaphore

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

可以把信号量想象成是停车场的展示牌:当前有车位 100 个,表示有 100 个可用资源;
当有车开进去的时候,就相当于申请一个可用资源,可用车位就 -1 (这个称为信号量的 P 操作);
当有车开出来的时候,就相当于释放一个可用资源,可用车位就 +1 (这个称为信号量的 V 操作);
如果计数器的值已经为 0 了,还尝试申请资源,就会阻塞等待,直到有其他线程释放资源

示例:定义20个线程模拟调用3个资源。

public class Demo02_Semaphore {
    // 定义一个信号变量,指定可用资源的个数
    private static Semaphore semaphore = new Semaphore(3);

    public static void main(String[] args) {
        // 定义一个任务
        Runnable runnable = new Runnable() {
            @Override
            public void run() {

                try {
                    System.out.println(Thread.currentThread().getName() + "[.]申请资源.");
                    // 调用acquire方法,让可用资源数 减 1
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "[+]申请到了资源");
                    // 模拟业务处理的过程
                    TimeUnit.SECONDS.sleep(1);
                    // 释放资源
                    semaphore.release();
                    System.out.println(Thread.currentThread().getName() + "[-]释放资源");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        // 创建多个线程
        for (int i = 0; i < 20; i++) {
            Thread thread = new Thread(runnable);
            thread.start();
        }

    }
}

acquire()和release()方法是成对出现的。
🎯应用场景:
在遇到业务中需要指定有限资源的个数时,可以考虑使用Semaphore来处理。比如,最多可以同时支持多少个并发…

CountDownLatch

在短跑比赛中,最终的颁奖操作必须要等待所有的运动员都到达终点才能进行。CountDownLatch可以设置所有的线程都到达某一个关键点之后才进行下一步操作

public class Demo03_CountDownLatch {
    // 定义一个CountDownLatch
    private static CountDownLatch countDownLatch = new CountDownLatch(10);

    public static void main(String[] args) throws InterruptedException {
        System.out.println("所有选手各就各位....");
        // 创建线程模拟比赛
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "出发");
                // 模拟比赛过程
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 到达终点,计数减1
                countDownLatch.countDown();
                System.out.println(Thread.currentThread().getName() + "到达终点");
            }, "player" + (i + 1));
            // 启动线程
            thread.start();
        }
        // 等待所有的线程执行完成
        countDownLatch.await();
        // 颁奖
        System.out.println("开始颁奖");

    }
}

countDown()方法每执行一次,构造时指定的值就会减1,直到为0;
await()方法一直会等到countDownLatch维护的值为0时,才会进行继续运行下面的操作。
🎯应用场景:
把一个大任务分为若干个小的任务,或是等待一些前置资源时,可以考虑使用CountDownLatch

CyclicBarrier-循环栅栏

CountDownLatch的进阶版,可以实现线程间的相互等待,计数重置。

线程安全的集合类

定义一个普通集合类,用多个线程同时对这个集合进行add操作,并打印集合:

    public static void main(String[] args) throws InterruptedException {
        // 定义一个普通集合类
        List<Integer> list = new ArrayList<>();

        // 用多个线程同时对这个集合进行add操作,并打印集合
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            Thread thread = new Thread(() -> {
                list.add(finalI);
                System.out.println(list);
            });
            thread.start();
        }
        TimeUnit.SECONDS.sleep(1);
        System.out.println("=================");
        System.out.println(list);
    }

运行这段代码可能会报错;并发修改异常。
在这里插入图片描述

如果出现这个异常首先要考虑是不是集合类使用的不恰当,也就是说在多线程环境下使用了线程不安全的的集合类,那么在多线程环境下如何使用线程安全的集合类呢?
1.使用Vector、Hashtable之类的,JDK中提供的线程安全的类。可以解决上述问题,但是不推荐使用。
2.自己使用同步机制。使用(synchronized或者ReentrantLock)。这种方式和上面的效果差不多,也不推荐。
3.通过工具类转换Collections.synchronizedList(new Arraylist)可以创建一个线程安全的类。

    public static void main (String[] args) throws InterruptedException {
        // 通过工具类来创建一个线程安全的集合类
        List<Object> list = Collections.synchronizedList(new ArrayList<>());

        // 用多个线程同时对这个集合进行add操作,并打印集合
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            Thread thread = new Thread(() -> {
                list.add(finalI);
                System.out.println(list);
            });
            thread.start();
        }
        TimeUnit.SECONDS.sleep(1);
        System.out.println("=================");
        System.out.println(list);
    }

它是通过在普通集合对象外层又包裹了一层synchronized完成的线程安全。也不推荐使用。
在这里插入图片描述

CopyOnWriteArrayList

这是JUC包下的类,使用的是一种写时复制技术来实现的。写时复制就是当要修改一个集合时,先复制一份这个集合的复本,修改复本的数据;修改完之后,用复本覆盖原始集合。

    public static void main(String[] args) throws InterruptedException {
        // 使用CopyOnWriteArrayList
        CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();

        // 用多个线程同时对这个集合进行add操作,并打印集合
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            Thread thread = new Thread(() -> {
                list.add(finalI);
                System.out.println(list);
            });
            thread.start();
        }
        TimeUnit.SECONDS.sleep(1);
        System.out.println("=================");
        System.out.println(list);
    }

优点:
在读多写少的场景下, 性能很高, 不需要加锁竞争;
缺点:
1、由于新复制了一份数据进行修改,所以占用内存较多;
2、新写的数据不能被第一时间读取到。

多线程环境使用队列

1)ArrayBlockingQueue:基于数组实现的阻塞队列
2)LinkedBlockingQueue:基于链表实现的阻塞队列
3)PriorityBlockingQueue:基于堆实现的带优先级的阻塞队列
4)TransferQueue:最多只包含一个元素的阻塞队列

多线程环境使用哈希表

1.Hashtable线程安全
实现方法是通过synchronized给this加锁,也就是给自己加锁,读写的时候都加锁,这样效率比较低,不推荐使用。
2.HashMap线程不安全
正常单线程环境下使用HashMap没有问题,由于本身没有加锁处理,在多线程环境下会产生线程安全问题。

ConcurrentHashMap

在多线程环境下推荐使用这种方式保证线程安全。在JDK1.8中,使用synchronized实现加锁。

既然Hashtable和 ConcurrentHashMap都是线程安全的,那它们有啥区别呢?

Hashtable是对所有操作全部加锁,必然会影响性能。一个Hashtable只有一把锁,两个线程访问Hashtable中的任意数据就会出现锁锁竞争。
在这里插入图片描述

1.ConcurrentHashMap优化了锁的粒度,它是对每个哈希桶加锁,意味着哈希桶的数组长度有多少,就可以支持多少个并发。只有当两个线程访问的恰好是同一个哈希桶上的数据才会出现锁冲突。
在这里插入图片描述
2.ConcurrentHashMap的读操作没有加锁(但是使用了 volatile 保证从内存读取结果), 只对写操作进行加锁,加锁方式使用synchronized。
在这里插入图片描述
3.充分利用了CAS机制。比如size的属性通过CAS来更新,避免出现重量级锁。
4.对扩容机制做了优化。对于需要扩容的操作,新建一个新的Hash桶,随后的每次操作都搬运一些元素去新的Hash桶。在扩容没有完成时,两个Hash桶同时存在,每次写入时只写入新的Hash桶,每次读取需要从新旧桶中同时读,哪个读到了就返回哪个,所有数据搬运完成后,把老的Hash桶删除。这是一个典型的以空间换时间的例子。


继续加油~

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/670584.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OpenResty 中的 Nginx 基础知识

Nginx 版本 OpenResty 的版本&#xff0c;落后于标准 Nginx 版本不少&#xff0c;所以较新的 Nginx 支持的功能&#xff0c;OpenResty 不一定支持。 Nginx 进程模型 当启动 Nginx 后我们使用 ps 来查看相关进程&#xff1a; $ ps -ef --forest | grep nginx root 32475…

OpenGL超级第12章学习笔记:管线监控

前言 本篇在讲什么 OpenGL蓝宝书第十二章学习笔记之管线监控 本篇适合什么 适合初学OpenGL的小白 本篇需要什么 对C语法有简单认知 对OpenGL有简单认知 最好是有OpenGL超级宝典蓝宝书 依赖Visual Studio编辑器 本篇的特色 具有全流程的图文教学 重实践&#xff0c…

C++编译过程

How the C Compiler works? 文章目录 How the C Compiler works?compilingExamples总结欢迎关注公众号【三戒纪元】 通过编程&#xff0c;是的text程序编程可执行文件&#xff0c;基本上主要有2个操作发生&#xff1a; compiling 编译linking 链接 compiling C 编辑器要做的…

VXLAN:数据中心网络的未来

概要 随着云计算和虚拟化技术的快速发展&#xff0c;数据中心网络正面临着越来越大的挑战。传统的网络架构在适应大规模数据中心的需求方面存在一些限制&#xff0c;如扩展性、隔离性和灵活性等方面。为了克服这些限制&#xff0c;并为数据中心网络提供更好的性能和可扩展性&am…

【好书精读】网络是怎样连接的 之 连接服务器

&#xff08;该图由AI制作 学习AI绘图 联系我&#xff09; 目录 1 连接是什么意思 1.1 连接实际上是通信双方交换控制信息 2 负责保存控制信息的头部 2.1 客户端与服务器之间交换的控制信息 连接操作的实际过程 1 连接是什么意思 创建套接字之后 &#xff0c; 应用程序 …

Selenium教程__使用execute_script执行JavaScript(11)

selenium的包含的方法已能完全满足UI自动化&#xff0c;但是有些时候又不得不用到执行JS的情况&#xff0c;比如在一个富文本框中输入1W个字&#xff0c;使用send_keys方法将经历漫长的输入过程&#xff0c;如果换成使用JS的innerHTML方法就能够很快的完成输入。 selenium执行…

Shell 函数实现Go语言多版本管理轻量级方案

现有的工具方案 https://github.com/moovweb/gvmhttps://github.com/voidint/g 我的方案 优点&#xff1a; 原生&#xff1a;基于 go 语言本身支持多版本的能力实现&#xff0c;可以下载任何官方发布的版本简单&#xff1a;shell 函数实现&#xff0c;直接集成到 bashrc 或…

软件测试技能,JMeter压力测试教程,HTTP Cookie管理器(四)

目录 前言 一、场景案例 二、HTTP Cookie管理器 三、302 重定向 前言 Web网站的请求大部分都有cookies&#xff0c;jmeter的HTTP Cookie管理器可以很好的管理cookies 我用的 jmeter5.1 版本&#xff0c;直接加一个HTTP Cookie管理器放到请求的最前面&#xff0c;就可以自…

用docker搭建selenium grid分布式环境实践

目录 前言&#xff1a; selenium jar包直接启动节点 用docker命令直接启动 docker-compose 启动 Hub和node在一台机器上 Hub和node不在一台机器上 遗留问题 总结 前言&#xff1a; Selenium是一个流行的自动化测试工具&#xff0c;支持多种编程语言和多种浏览器。Sele…

【微服务架构演进】一文读懂单片到微服务架构的模式和最佳实践

在本文中&#xff0c;我们将学习如何使用设计模式、原则和最佳实践来设计微服务架构。我们将使用正确的架构设计模式和技术。 在本文结束时&#xff0c;您将了解如何在微服务分布式架构上设计系统以实现高可用性、高可扩展性、低延迟和对网络故障的弹性&#xff0c;从而处理数百…

学习Spring之声明式事务

什么是事务&#xff1f; 一个业务有一组操作&#xff0c;要么都成功&#xff0c;要么都失败 事务的四大特性&#xff1a;ACID A 原子性&#xff1a;一组操作&#xff0c;要么都成功&#xff0c;要么都失败 C 一致性 &#xff1a;事务的前后要保证事务的一致性 I 隔离性 &…

QLabel的使用

QLabel介绍 QLabel 是 Qt 框架中的一个控件类&#xff0c;用于显示文本或图像。它可以在窗口或其他容器中显示静态文本&#xff0c;并且可以根据需要设置格式、对齐方式和尺寸。 主要作用如下&#xff1a; 显示文本内容&#xff1a;QLabel 可以显示文字内容&#xff0c;可以…

【每天40分钟,我们一起用50天刷完 (剑指Offer)】第二天

专注 效率 记忆 预习 笔记 复习 做题 欢迎观看我的博客&#xff0c;如有问题交流&#xff0c;欢迎评论区留言&#xff0c;一定尽快回复&#xff01;&#xff08;大家可以去看我的专栏&#xff0c;是所有文章的目录&#xff09;   文章字体风格&#xff1a; 红色文字表示&#…

Spring Boot 中使用 @EventListener 注解监听事件

Spring Boot 中使用 EventListener 注解监听事件 Spring Boot 是一个流行的 Java Web 框架&#xff0c;它提供了丰富的功能和工具来简化开发人员的工作。其中一个非常有用的功能是事件监听器。在 Spring Boot 中&#xff0c;我们可以使用 EventListener 注解来监听事件&#x…

一天时间完成Python入坑(开发环境搭建、excel解析、exe打包三步走)

0.为什么要入坑Python 早就知道Python好&#xff0c;Python妙&#xff0c;Python用起来呱呱叫。工作上一直没有什么用得上Python的必要性&#xff0c;就一直没有接触&#xff0c;本次终于来了机会&#xff1a;【图新地球桌面端要对外开放Python API】&#xff0c;开放图新地球的…

【mars3d】Cesium实现雷达放射波

Cesium实现雷达放射波 1、雷达放射波 先看效果图 说明&#xff1a;使用的是mars3d框架&#xff0c;原生的Cesium实现方式可以绕道~ 实现方式&#xff1a; <template><div id"mars3dContainer"></div> </template><script setup> i…

Fiddler抓包基础使用

目录 一、设置抓谷歌浏览器https数据包 1、选中以下选项即可 2、若是选中后还是未抓到HTTPS数据包&#xff0c;则可进行以下操作 二、设置抓取Firefox浏览器HTTPS数据包 1、Firefox的代理需设置系统代理&#xff0c;设置→高级→网络设置&#xff0c;设置为系统代理&#…

【深度学习】5-3 与学习相关的技巧 - Batch Normalization

如果为了使各层拥有适当的广度&#xff0c;“强制性”地调整激活值的分布会怎样呢&#xff1f;实际上&#xff0c;Batch Normalization 方法就是基于这个想法而产生的 为什么Batch Norm这么惹人注目呢?因为Batch Norm有以下优点&#xff1a; 可以使学习快速进行(可以增大学习…

广工赢清华,炸裂!

去年2022年广工对阵清华&#xff0c;我在知乎写了文章 清华赢球靠的是广东第一高中生邹阳和2022届CBA状元王岚嵚。 比分焦灼的第四节关键时刻&#xff0c;邹阳在左角底线持球高高举起篮球&#xff0c;那个球的弧度非常高&#xff0c;皮球以稳稳的抛物线弧度掉入篮筐。 之后&…

Python基础(21)——Python函数实战、递归、lambda、高阶函数

Python基础&#xff08;21&#xff09;——Python函数实战、递归、lambda、高阶函数 文章目录 Python基础&#xff08;21&#xff09;——Python函数实战、递归、lambda、高阶函数目标一. 应用&#xff1a;学员管理系统1.1 系统简介1.2 步骤分析1.3 需求实现1.3.1 显示功能界面…