【多线程进阶二】JUC工具类 线程安全的集合类 死锁

news2024/11/17 14:34:52

目录

一、JUC工具类     

🍅1、Callable接口

🍅2、ReentrantLock

🍅3、原子类

🍅4、Semaphore信号量

🍅5、CountDownLatch

二、线程安全的集合类

​🍅1、多线程环境下,怎么使用线程安全的类?

🍅2、多线程环境下使用队列

🍅3、多线程环境下使用哈希表

三、死锁

🍅1、死锁是什么?

🍅2、发生死锁的原因及解决方案

🍅补充:ThreadLocal


一、JUC工具类     

   JUC是java.util.concurrent包的简称,在JDK1.5之后对多线程的一种实现,这个包下存放的类都和多线程有关,提供了很多工具类。

🍅1、Callable接口

        1、演示用Callable接口进行变量的累加。(1 + 2 + 3 + ... + 5)

public static void main(String[] args) throws ExecutionException, InterruptedException {
        //1、定义一个线程的任务,重写Callable接口中的call方法
        Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                //2、对变量进行累加
                int sum = 0;
                for (int i = 0; i < 5; i++) {
                    sum = sum + i;
                }
                //3、返回结果
                return sum;
            }
        };
        //4、通过FutureTask类来创建一个对象,这个对象持有callable
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        //5、创建线程并指定任务
        Thread thread = new Thread(futureTask);
        //6、启动线程,让线程执行定义好的任务
        thread.start();
        //7、获取线程执行的结果
        System.out.println("等待结果...");
        Integer result = futureTask.get();
        //8、打印结果
        System.out.println(result);
    }

总结上述过程:

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

(2)重写Callable接口的call方法,实现累加过程,并返回计算结果;

(3)将callable实例用FutureTask对象包装一下;

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

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

❓ 面试题1:Callable接口与Runnable接口的区别?

(1)Callable接口实现的是call方法,Runnable实现的是run方法;

(2)Callable可以返回一个结果,Runnable不可以;

(3)Callable要配合FutureTask一起使用;

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


 ❓ 面试题2:创建线程有几种方式?

(1)继承Thread类,实现run方法;

(2)实现Runnable接口,实现run方法;

(3)实现Callable接口,实现call方法

(4)通过线程池创建线程。

 🍅2、ReentrantLock

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

ReentrantLock 的用法:

       (1) lock():加锁,如果获取不到锁就死等.
       (2) trylock(long): 尝试加锁。如果获取不到锁, 等待一定的时间之后就放弃加锁。
       (3) unlock(): 解锁

代码演示: 

 ​​

❓面试题3:ReentrantLock与synchronized的区别?

ReentrantLocksynchronized
使用的时候需要手动释放锁。使用更灵活,但经常容易忘掉unlock。不需要手动释放锁。
在申请锁失败的时候一直等待锁资源。死等。通过trylock的方式等待一段时间就放弃
是否为公平锁默认是非公平锁。可以通过构造方法传入True开启公平锁模式。是非公平锁。
实现方式不同是标准库的一个类,基于Java JUC实现。是一个关键字,JVM内部实现。
是否是可重入锁是可重入锁是可重入锁
唤醒机制搭配Condition类实现唤醒等待,可以更精确的控制唤醒某个指定的线程。搭配Object类的wait/notify实现唤醒等待,每次唤醒的都是一个随机等待的线程。

问题:如何选择使用哪个锁?

(1)锁竞争不激烈的时候,使用synchronized,效率更高,自动释放更加方便;

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

(3)如果需要使用公平锁,使用RenntrantLock。

🍅3、原子类

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

 

🍅4、Semaphore信号量

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

🌰理解信号量

        —停车场:当前车位有100个,表示有100个可用资源。

(1)当停车场进入一辆车,车位的个数就-1;(信号量的V操作,表示释放资源,资源数+1)

(2)当停车场开出一辆车,车位的个数就+1;(信号量的P操作,表示申请资源,资源数-1)

(3)停车场中的所有车位就是可以显示的最大有效值。

        当申请资源的时候,如果资源已经被用完没有资源了(当前停车场的车位为0 ),那么当前申请资源的线程必须要阻塞等待,直到其他线程释放资源。 

 信号量的使用演示:

       (1)定义:private static Semaphore semaphore = new Semaphore(容量);

       (2) acquire表示申请资源

       (3)release表示释放资源(一般搭配使用)。


应用场景:

        如果以后的业务中需要指定有限的资源个数的时候,可以考虑使用Semaphore来进行处理。

//1、定义一个信号变量,表示可用资源的个数
    private static Semaphore semaphore = new Semaphore(3);
    public static void main(String[] args){
        //2、定义任务:用来申请资源和释放资源
        //创建 20 个线程, 每个线程都尝试申请资源, sleep 1秒之后, 释放资源. 观察程序的执行效果.
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    //3、任务是:让每个线程都尝试申请资源
                    System.out.println(Thread.currentThread().getName()+"[.]申请资源");
                    //4、调用acquire,让可用资源数-1
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+"[+]申请到了资源");
                    //5、休眠1s,释放资源
                    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();
        }
    }

 

 🍅5、CountDownLatch

        CountDownLatch的作用是:可以设置所有的线程都必须到达某一个关键点之后再执行后续的操作。

CountDownLatch的使用:

(1)定义:CountDownLatch countDownLatch = new CountDownLatch(指定容量);

(2)指定容量计数-1:countDownLatch.countDown();

(3)一直等待countDownLatch维护的值为0,才会继续运行后面的代码:countDownLatch.await();       


应用场景:

        当把一个大任务分成若干个小任务,或者是等待一些前置资源的时候,就可以考虑使用CountDownLatch。

补充: CyclicBarrier是CountDownLatch的进阶版,表示循环栅栏,可以实现进程之间的相互等待,计数重置。

        模拟实现运动员比赛过程,10个运动员必须都到达终点后才会执行后面的颁奖仪式~ 

//1、定义一个CountDownLatch
    private static CountDownLatch countDownLatch = new CountDownLatch(10);
    public static void main(String[] args) throws InterruptedException {
        System.out.println("所有选手已经就位...");
        //2、创建线程模拟跑步比赛
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(()->{
                //3、10个线程开始出发
                System.out.println(Thread.currentThread().getName()+"出发");
                try{
                    //4、等待5s开始陆续到达终点
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //5、每到达一个线程,计数-1
                System.out.println(Thread.currentThread().getName()+"到达终点");
                countDownLatch.countDown();
            },"player"+(i+1));
            //6、启动线程
            thread.start();
        }
        //7、等待所有的线程都到达终点后,开始执行后面的任务
        countDownLatch.await();
        //后面的任务就是颁奖啦~
        System.out.println("颁奖中...");
    }


、线程安全的集合类

原来的集合类,大多都不是线程安全的。

演示:

 🍅1、多线程环境下,怎么使用线程安全的类?

问题:在多线程环境下,怎么使用线程安全的集合类?

(1)Vector,Stack,HashTable是线程安全的,是JDK中提供的线程安全的类,但是强烈不推荐使用。

 

(2)自己使用同步机制(synchronized或者ReentrantLock)。和(1)的效果差不多,也不推荐。 

(3)使用工具类转换Collections.synchronizedList(new ArrayList),也不推荐。

 实现方式是在普通集合类对象外面又包裹了一层synchronized完成的线程安全。

(4)使用CopyOnWriteArrayList。(多线程环境下使用集合类优先考虑使用)

        它是JUC下面的一个类,使用的是“写时复制技术”来实现的。

写时复制技术指的是:

当要修改一个集合时,先复制这个集合的副本;

修改副本的数据,修改完成之后,用副本来覆盖原始集合。

 优点:在读多写少的场景下,性能很高,不需要加锁竞争;

 缺点:(1)占用的内存较多:因为复制了一份新的数据进行修改;

            (2)新写的数据不能第一时间被读取到。

🍅2、多线程环境下使用队列

🍅3、多线程环境下使用哈希表

❓问题2:HashMap,Hashtable与ConcurrentHashMap的区别?

1、HashMap在正常的单线程情况下使用HashMap是没有问题的。但是由于没有加锁处理,在多线程环境下是线程不安全的。(不推荐)key允许为null。

2、Hashtable是线程安全的。(不推荐)key不允许为null。

实现方式:通过synchronized来加锁实现线程安全。给this加锁,也就是自己加锁。不过在进行读写的时候由于都加锁了,所以效率就很低。

(1)当多线程访问同一个Hashtable时,就会造成锁冲突;

(2)size属性也是通过synchronized来同步控制,也比较慢;

(3)一旦触发扩容,就由该线程完成整个扩容机制,这个扩容过程中会涉及到大量的元素拷贝,效率会非常低。

(3)ConcurrentHashMap是线程安全的(推荐)key不允许为null。

多线程环境下强烈推荐使用这种方式来保证线程安全,它与HashTable不同,不是通过synchronized关键字来实现加锁的,而是通过JUC包下的ReentrantLock来实现加锁。

                                                                也就是使用CSA,用户态来实现加锁。

ConcurrentHashMap对Hashtable做出的优化:

(1)更小的锁力度

Hashtable加锁的方式:是对所有的操作全部加锁,必然会影响到性能。

 ConcurrentHashMap,是对每一个哈希桶来进行加锁,提高并发能力。也就是说,每次只锁定一个桶位,就意味着,Hash桶的数组长度有多少个,就可以支持多少个并发。

 (2)只给写加锁,不给读加锁。加锁的方式使用的是ReentrantLock,大量运用的是CAS操作。并且共享变量使用的volatile修饰。

(3)充分利用CAS操作。比如size属性通过CAS来更新,避免出现重量级锁的情况。

(4)对扩容机制进行了优化。

对于需要扩容的操作,新建一个Hash桶,随后的每次操作都搬运一些元素去新的Hash桶。在扩容还没有完成的时候,两个Hash桶同时存在;

每次写入时只写入新的Hash桶;

每次读取时需要新旧的Hash桶同时读取;

等所有的元素都搬运完成之后,将旧的Hash桶删除。(是一个典型的空间换时间的用例)

注意:ConcurrentHashMap在jdk1.8中做出的优化:

取消了分段锁,直接给每个哈希桶(每个链表)分配了一个锁,将原来的数组+链表的实现方式改进为数组+链表/红黑树的方式。当链表较长的时候(大于等于8个元素的时候)就会转化为红黑树。


、死锁

🍅1、死锁是什么?

        死锁就是一个线程加上锁之后不运行也不释放锁,导致程序无法继续运行,是一个非常严重的BUG。

🌰举例

1、一个线程一把锁:一个线程对一把锁加锁两次:如果是可重入锁就不会产生死锁;如果是不可重入锁,就会产生死锁。(但其实如果该锁是不可重入锁,那么也没有办法加两次锁,也就谈不上死锁)。

2、两个线程两把锁:比如车钥匙锁在家里了,家里的钥匙锁车里了。就是一个死锁。

🍅2、发生死锁的原因及解决方案

❓问题3:发生死锁的原因?

(1)互斥使用:锁A被线程1占用了,线程2就不能用了;

(2)不可抢占:锁A被线程1占用了,线程2不能主动将锁A抢过来,除非线程1主动释放;

(3)请求保持:有多把锁,线程1拿到了锁A之后,不释放而且还要继续拿锁B;

(4)循环等待:线程1等待线程2释放锁,线程2要释放锁要先等待线程3释放锁,线程3释放锁要先等待线程1释放锁...形成了循环关系。


❓问题4:怎么避免死锁?

以上四条是形成死锁的必要条件,打破以上4条任意一个就行,逐一分析:

(1)互斥使用:这个不能打破,这个是锁的基本性质;

(2)不可抢占:这个不能打破,这个是锁的基本性质;

(3)请求保持:这个有可能打破,取决于代码的写法;

(4)循环等待:约定好加锁顺序就可以将循环打破。


🌰小丑吃瓜问题

 如果所有人都先拿左手的筷子再拿右手的筷子,就会死锁。

 现在,重新安排拿筷子的顺序:

 

 

(1)要求每个人先去拿编号小的筷子,拿到之后再拿编号大的筷子;

(2)此时125号都会拿到一个筷子,3号和4号会抢占筷子1号,而筷子1号只有一个人能够拿到,拿不到的那个人就要等待;

(3)如果4号小丑拿到了筷子1,那左边的5号筷子没人拿,4号小丑就把5号筷子也拿上;

(4)等4号吃完之后,放下筷子,3号再拿筷子1号吃瓜。


注意:在操作系统课程中针对死锁给出的解决方案是“银行家算法”,将所有的资源进行统筹分配,也可以避免死锁。

补充:ThreadLocal

1、ThreadLocal类用来提供线程内部的局部变量,不同的线程之间不会相互干扰。

2、这种变量在多线程环境下访问(通过get和set方法访问)时能保证各个线程的变量相对独立于其他线程内的变量。

 

 

 演示:

 //1、初始化一个ThreadLocal
    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
    public static void main(String[] args) {
        Thread thread1 = new Thread(()->{
            //2、统计人数-一班
            int count = 35;
            threadLocal.set(count);
            print();
        });
        Thread thread2 = new Thread(()->{
            //统计人数-二班
            int count = 40;
            threadLocal.set(count);
            print();
        });
        thread1.start();
        thread2.start();
    }
    //3、定制校服
    private static void print() {
        Integer value = threadLocal.get();
        System.out.println(Thread.currentThread().getName()+"需要定制"+value+"套校服");
    }

 


继续加油~

 

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

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

相关文章

第十三届蓝桥杯国赛JavaB组题解

A. 重合次数 思路&#xff1a; 枚举不同的时刻&#xff0c;判断哪些时刻秒针和分针表示的数字是相同的。这道题坑就坑在:xx:59:59 xx:00:00分针和时。也就是说一个小时会重叠两次。 题目要求是分钟和秒钟的重叠次数,故时钟,分钟,秒钟同时重叠的次数不算(这题还是有点咬文嚼字了…

MySQL---事务

1. 事务操作 开启事务&#xff1a;Start Transaction 任何一条DML语句(insert、update、delete)执行&#xff0c; 标志事务的开启命令&#xff1a;BEGIN 或 START TRANSACTION 提交事务&#xff1a;Commit Transaction 成功的结束&#xff0c;将所有的DML语句操作历史记录…

G2O学习使用

g2o全称是General Graph Optimization&#xff0c;也就是图优化&#xff0c;我们在做SLAM后端或者更加常见的任何优化问题&#xff08;曲线拟合&#xff09;都可以使用G2O进行处理。 就经验而言&#xff0c;solvers给人的感觉是大同小异&#xff0c;而 types 的选取&#xff0…

C语言小游戏——扫雷

前言 结合前边我们所学的C语言知识&#xff0c;本期我们将使用C语言实现一个简单的小游戏——扫雷 目录 前言 总体框架设计 多文件分装程序 各功能模块化实现 初始化棋盘 棋盘打印 埋雷 判赢与排雷 游戏逻辑安排 总结 总体框架设计 和三子棋相同&#xff0c;游戏开始时…

32岁测试工程师,陷入中年危机,最终我裸辞了....

前言 今年32岁&#xff0c;我从公司离职了&#xff0c;是裸辞。 前段时间&#xff0c;我有一件事情一直憋在心里很难受&#xff0c;想了很久也没找到合适的人倾诉&#xff0c;就借着今天写出来。 我一个十几年IT经验&#xff0c;七年测试经验的职场老人&#xff0c;我慢慢涨…

02 Android开机启动之BootLoader及kernel的启动

Android开机启动之BootLoader及kernel的启动 1、booloader的启动流程 第一阶段:硬件初始化,SVC模式,关闭中断,关闭看门狗,初始化栈,进入C代码 第二阶段:cpu/board/中断初始化;初始化内存以及flash,将kernel从flash中拷贝到内存中,执行bootm,启动内核 2、kernel的启…

学习如何将Jenkins与UI测试报告完美整合,事半功倍,轻松获取高薪职位!

目录 引言 &#xff08;一&#xff09;在本地整合出报告 1.在cmd分别安装pytest和allure-pytest 2.进入需要执行的代码所在的路径 3.运行测试报告&#xff0c;代码如下 4.解析此json文件&#xff0c;代码如下&#xff08;新打开cmd进入路径&#xff09; 5.打开此HTML文件…

包管理工具

包 package&#xff0c;代表了一组特定功能的源码集合。 包管理工具 管理包的应用软件&#xff0c;可以对包进行下载安装、更新、删除、上传等操作。 借助包管理工具&#xff0c;可以快速开发项目&#xff0c;提升开发效率。 常用包管理工具 npm&#xff08;nodejs官方内…

百度API实现自动写诗

作者介绍 张琪&#xff0c;男&#xff0c;西安工程大学电子信息学院&#xff0c;2022级研究生 研究方向&#xff1a;机器视觉与人工智能 电子邮件&#xff1a;3126743452qq.com 王泽宇&#xff0c;男&#xff0c;西安工程大学电子信息学院&#xff0c;2022级研究生&#xff0…

Spring——Spring_IOC

1.Spring_IOC概念引入 控制反转 2.Spring_IOC代码测试 IOC代码演示 控制反转&#xff1a;就是创建对象的权力交给了容器 1.创建一个接口&#xff0c;定义一个抽象方法 package org.example;public interface Empdao {int addemp(); } 2.创建一个实现类&#xff0c;实现这…

两台电脑之间怎么互相传文件?

​随着技术的发展&#xff0c;我们似乎可以从家中或工作电脑远程访问另一台电脑。同时&#xff0c;一些用户也在想&#xff0c;“我能不能把文件从一台电脑远程传输到另一台电脑&#xff0c;这样我就可以在本地电脑上随心所欲地查看和编辑文件了”。 这个问题的答案是…

Android自定义一个省份简称键盘

hello啊各位老铁&#xff0c;这篇文章我们重新回到Android当中的自定义View&#xff0c;其实最近一直在搞Flutter&#xff0c;初步想法是&#xff0c;把Flutter当中的基础组件先封装一遍&#xff0c;然后接着各个工具类&#xff0c;列表&#xff0c;网络&#xff0c;统统由浅入…

PostgreSQL实战之物理复制和逻辑复制(八)

目录 PostgreSQL实战之物理复制和逻辑复制&#xff08;八&#xff09; 8 级联复制 8.1 级联复制物理架构 8.2 级联复制部署 PostgreSQL实战之物理复制和逻辑复制&#xff08;八&#xff09; 8 级联复制 实际上PostgreSQL支持备库既可接收主库发送的将WAL&#xff0c;也支持…

Java泛型基本知识附面试题

一次平平无奇的面试 为什么要写这篇文档&#xff0c;主要就是在字节二面的时候&#xff0c;面试官提了这么一个问题 面试官&#xff1a;Java中的List<Integer>里有可能存String类型元素吗&#xff1f; 当时的我&#xff1a;应该…不可以吧&#xff0c;好像编译器会报错…

2023口腔护理行业分析:市场需求多元化,细分市场持续多变

随着人们生活水平的提高以及口腔护理意识的提升&#xff0c;消费者对于口腔护理的诉求愈发多样化&#xff0c;对于与此相关的产品&#xff0c;包括牙膏、牙刷、牙齿美白产品、漱口水、牙线等产品的需求也日益提高&#xff0c;在这种情况下&#xff0c;口腔护理相关细分产品的销…

软件研发管理高效的关键:11项自动化功能

1、自动锁定需求缺陷 为了提高用户需求分析质量&#xff0c;尽早发现需求缺陷&#xff0c;CoCode开发云特开发了需求分析工具&#xff0c;使用AI&#xff0c;通过需求测试和一致性检测&#xff0c;能够在几分钟内快速分析用户需求缺陷&#xff0c;如歧义、重复、遗漏、不一致和…

深度学习神经网络学习笔记-多模态方向-09-VQA: Visual Question Answering

摘要 -我们提出了自由形式和开放式视觉问答(VQA)的任务。给定一张图像和一个关于图像的自然语言问题&#xff0c;任务是提供一个准确的自然语言答案。镜像现实场景&#xff0c;比如帮助视障人士&#xff0c;问题和答案都是开放式的。视觉问题有选择地针对图像的不同区域&#…

nodejs+vue飞机机票在线预订票网站

本机票预订系统以vue作为框架&#xff0c;b/s模式以及MySql作为后台运行的数据库&#xff0c;同时使用Tomcat用为系统的服务器。本系统主要包括首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;机票类型管理&#xff0c;机票信息管理&#xff0c;订票信息管理&#x…

STM32F4_DMA直接存储器详解

目录 1. 什么是DMA 2. DMA的主要特性 3. DMA功能 3.1 DMA功能框图 3.2 DMA事务 3.3 通道选择 3.4 仲裁器 3.5 DMA数据流 3.6 源、目标和传输模式 3.6.1 外设到存储器模式 3.6.2 存储器到外设模式 3.6.3 存储器到存储器模式 3.7 指针递增 3.8 DMA内存占用 3.9 存…

<SQL>《SQL命令(含例句)精心整理版(1)》

《SQL命令精心整理》 1 SQL基础2 关键字 select & distinct3 排序检索 - order by & desc & asc4 where 语句5 操作符 -and & or & not & in6 通配符6.1 LIKE % 谓词 下划线 方括号 7 计算、拼接、别名 1 SQL基础 名词概念数据库&#xff08;database…