首页 > 编程语言 >JUC(java.util.concurrent)的常见类

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

时间:2024-07-04 13:27:27浏览次数:21  
标签:JUC 加锁 java synchronized 链表 util 线程 Callable 操作

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

Callable(这是一个interface接口)

这个也是创建线程的一种方式

Runnable能表示一个任务(run方法)

返回:void

Callable也能表示一个任务(call方法)

返回:一个具体的值,类型可以通过泛型参数来指定(Object)

如果进行多线程操作,只是关心多线程执行的过程,使用Runnable即可.(像,线程池,定时器 )

如果是关心多线程的计算结果,使用Callable更合适这个是使用Runnable来实现从1加到1000,最后返回值的代码.下面我们用Callable的方式写一个一模一样功能的代码

public class Demo {
    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 < 1000; i++) {
                    sum+=i;
                }
                return sum;
            }
        };
        //注意,对于Callable而言,是不能直接放到Thread中的,但是Runnable可以,
        // 原因就是Runnable不在意返回值,但是Callable在意返回值.
        //这里要用到FutureTask.
        FutureTask<Integer> futureTask=new FutureTask<>(callable);
        Thread t1=new Thread(futureTask);
        t1.start();
        //这里需要用到FutureTask的get方法来获取Callable最后的结果.
        Integer result=futureTask.get();
        System.out.println(result);
    }
}

注意看注释!!!

FutureTask.get();这个是用来获取call方法的返回结果的,get类似于join一样,如果call方法没执行完,就会阻塞等待.

ReentrantLock(可重入锁)

这个锁,没有synchronized那么常用,但是也是一个可以选择的加锁的组件,这个锁在使用上更接近于C++.这个里面有lock()方法以及unlock()方法,但是我们这样用的时候就容易出现,unlock调用不到的情况,比如,中间return/抛出异常.

ReentrantLock具有一些特点是synchronized不具备的功能

优势

1.提供了一个tryLock方法进行加锁

对于lock这样的一个操作,如果枷锁不成功,就会阻塞等待(死等).对于tryLock而言,如果加锁失败,直接返回false/也可以设定等待时间.

2.ReentrantLock有两种模式,可以工作在公平锁状态下,也可以工作在非公平锁的状态下,构造方法中通过参数设定的 公平/非公平模式

3.ReentrantLock也有等待通知机制,搭配Condition着花样的类来完成,这里的等待通知要比wait notify功能更强.

劣势

1.synchronized锁对象是任意对象,RenntrantLock锁对象就是自己本身,如果你多个线程针对不同的ReentrantLock调用Lock方法,此时是不会产生锁竞争的.

信号量 Semaphore

这个在操作系统中也经常出现,Semaphore是并发编程中的一个重要的概念/组件.

准确来说,Semaphore是一个计数器(变量),描述了"可用资源(别称:临界资源)的个数",临界资源其实就是多个线程/进程等并发执行的实体可以公共使用到的资源(多个线程修改同一个变量,这个变量就可以认为是临界资源)

例子:假设我们现在要去一个停车场去停车,停车场的入口,上面有一个显示屏,显示了一行字:还有XX个空余车位(这里的XX就是信号量).如果我开车进入停车场,就相当于释放了一个车位(申请了一个可用资源),此时计数器就要-1,称为P操作accquire,如果我开车出了停车场,就相当于,释放了一个车位(释放了一个可用资源),此时计数器就要+1,称为V操作release.当计数器为0的时候,继续进行P操作,就会阻塞等待,一直等到其他线程执行了V操作,释放了一个空闲资源为止.锁,蹦智商是一个特殊的信号量(里面的数值,非0即1,二元信号量)信号量要比锁更广义,不仅仅可以描述一个资源,还可以描述N个资源.

public class Demo {
    public static void main(String[] args) throws InterruptedException {
        //这里可以通过构造方法来指定计数器的初始值
        Semaphore semaphore=new Semaphore(3);
        semaphore.acquire();
        System.out.println("执行了一个P操作");
        semaphore.acquire();
        System.out.println("执行了一个P操作");
        semaphore.acquire();
        System.out.println("执行了一个P操作");
        semaphore.acquire();
        System.out.println("执行了一个P操作");
    }
}

由于我们的初始值只有三个,但是我们这里写了四个P操作,所以在执行第四个P操作的时候会进入阻塞等待.

CountDownLatch

这个是针对特定场景的一个组件,场景:下载某个东西,有的时候下载文件并不是你的网速慢,更多的时候是因为人家服务器的限制,有一些"多线程下载器",就可以把一个大的文件,拆分成多个小的部分,是哟几个多个线程分别下载,每个线程负责下载一部分,每个线程分别是一个网络连接,这样就可以大幅提高下载速度.假设,我们这里分成了10个线程,10个部分来下载,啥时候算是下载完了??10个部分都下载完了,整体才算完成,那我们怎么来判定10个部分都下载完了呢?

CountDownLatch:当需要把一个任务拆分成多个任务,如何衡量现在是把多个任务都搞定了呢?

public class Demo2 {
    public static void main(String[] args) throws InterruptedException {
        //CountDownLatch的构造方法中所写的数字就是我们这里分成了几个任务
        CountDownLatch countDownLatch=new CountDownLatch(10);
        for (int i = 0; i < 10; i++) {
            int id=i;//这个就可以修正下面因为变量捕获所带来的一些不必要的麻烦
            Thread t=new Thread(()->{
                //这里直接写:  +i+  会报错,为什么?
                //1.涉及到lambda表达式的变量捕获,前面说过了
                System.out.println("任务"+id+"开始执行");
                //这里用sleep来代替下载所需要的时间
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("任务"+id+"结束");
                //每个任务执行到这里都调用一下方法,就好比接力赛,每个运动员到这里都撞线了.
                countDownLatch.countDown();
            });
            t.start();
        }
        //主线程如何知道任务结束了呢?
        //总不能用join吧?假设我们有1万个线程,难道你要写1万个join吗?
        //而且有的时候任务结束并不代表线程结束,万一任务需要结束,但是由任务所创建出来的线程不需要结束,
        // 那么join不是也就不行了吗?
        //在这里,主线程中可以使用 CountDownLatch 来负责等待任务结束.
        //如果上方countDown这里的次数<设定的初始值
        //await就会阻塞
        countDownLatch.await();
        System.out.println("多个线程的所有任务都执行结束了");
    }
}

运行结果如下

集合类

在Java集合类中,哪些是线程安全的?也就是多个线程同时操作这个集合类,是否会产生问题.

Vector,Stack,HashTable是线程安全的,其中Vector与HashTable是不建议使用的,其他的集合类是线程不安全的.Vector和HashTable属于是Java上古时期搞出来的集合类.那个时候人们对多线程编程的认识还不够深刻,所以在这里Vector与HashTable都过分的加了锁,有的时候,加了锁,不一定就线程安全,不加锁也不一定就线程不安全=>务必要具体问题具体分析.

所以,Vector/HashTable这样的集合类,虽然加了synchronized也不能保证一定是线程安全的.同时,在单线程的情况下,又可能因为synchronized影响到执行效率,所以,后续设计的集合类,就不再是这种加锁方式了.

多环境使用ArrayList

1.自己使用同步机制(ReentrantLock或者synchronized)(说人话就是自己手动加锁)
2.Collections.synchronizedList(new ArrayList)(很少会真用这个)

ArrayList本身没有使用synchronized,但你又不想自己加锁,就可以使用这个,相当于让ArrayList像Vector一样工作.

CopyOnWriteArrayList(写时复制)

多个线程同时修改同一个变量,会出现线程安全问题,那么如果多个线程修改不同变量,是不是就安全了呢?

如果多线程去读取,本身就不会有任何线程安全问题,一旦有线程修改,就会把自身复制一份,尤其是修改如果比较耗时的话,其他线程还是从旧的数据上读取,一旦修改完成,就使用新的ArrayList替换旧的ArrayList(本质上就是一个引用的重新赋值,速度极快,并且又是原子的)这个过程中,没有引入任何加锁操作,使用了创建副本=>修改副本=>使用副本替换的方式

ConcurrentHashMap

线程安全的hash表

HashTable是在方法上直接加上synchronized,就相当于针对this加锁.

任意的针对ht对象的操作,都会涉及到针对this的加锁.此时,如果有很多线程都想操作ht,就一定会触发激烈的锁竞争,这些线程最后只能一个一个排着队,依次进行(这种情况,并发程度很低)

如果两个修改操作是针对两个不同的链表进行修改,是否会存在线程安全问题呢?? 显然是不会的,所以原来的HashTable给每一个插入以及其他操作都加锁,这种情况是没有必要的,因为加锁这个操作本身的开销就是比较大的,而且也会这样也会造成不必要的阻塞等待.还有一种情况,针对同一个链表修改时,如果两个线程修改的是同一个链表中的同一个位置,那么会造成线程安全问题,针对同一个链表的不同位置修改时,就不会造成线程安全问题.

具体的做法就是给每个链表都去安排一把锁

这样的话,我们的锁冲突的概率就打打降低了,那么怎么给每一个链表都上锁呢?由于Java的synchronized随便拿个对象就可以加锁,所以我们就拿每个链表的头结点来进行加锁

ConcurrentHashMap改进:

1.[核心]减小了锁的粒度,每个链表都有一把锁,大部分情况下都不会涉及到锁冲突.

2.广泛使用了CAS操作,减小了锁冲突的概率

3.写操作进行了加锁(链表级),读操作就不加锁了

4.针对扩容操作进行了优化,渐进式扩容

HashTable一旦触发扩容,就会立即的一口气完成所有元素的搬运,这个过程相当耗时.就会出现大部分请求都很顺畅,突然某个请求就卡了比较久这样的情况,要解决这个问题也非常简单:化整为零,当需要进行扩容的时候,会创建出另一个更大的数组,然后把旧的数组上的数据逐渐的往新的数组上搬运.

1.新增元素,往新数组上插入

2.删除元素,把旧数组的元素给删掉即可

3.查找元素,新数组旧数组都得查找

4.修改元素,统一把这个元素给搞到新数组上

与此同时,每个操作都会触发一定程度搬运,每次搬运一点,就可以保证整体的时间不是很长,积少成多之后,逐渐完成搬运了,也就可以把之前的旧数组彻底销毁了.

那么话说回来,HashMap和ConcurrentHashMap有什么区别吗?

线程安全和线程不安全之间的区别.

关于ConcurrentHashMap的分段技术?

Java 8 之前,ConcurrentHashMap是使用分段锁,从Java 8 开始,就是每个链表自己一把锁了.

就像这样,这样虽然能提高效率,但是不如每个链表一把锁,代码实现起来也更复杂

标签:JUC,加锁,java,synchronized,链表,util,线程,Callable,操作
From: https://blog.csdn.net/X_HJJ/article/details/140176592

相关文章

  • Java 中的字符串替换方法详解:replace, replaceAll 和 replaceFirst
    在Java中,字符串的替换是一种常见的操作,特别是在处理文本和格式化输出时。Java提供了几种不同的方法来实现字符串替换,其中包括replace,replaceAll和replaceFirst。本文将详细讨论这些方法的用法、区别以及示例。1.replace(CharSequencetarget,CharSequencereplaceme......
  • javascript url 传递参数中文乱码问题解决方案
    在JavaScript中,传递URL参数时,如果参数包含中文字符,可能会出现乱码问题。解决这一问题可以使用encodeURIComponent和decodeURIComponent函数。这些函数会对URL参数进行编码和解码,确保特殊字符(包括中文字符)能够被正确传递和解析。以下是一个完整的解决方案示例: 1.......
  • JAVA基础:包的概念和使用
    目录package关键字1,包的概念2,包的定义格式3,包的特点import关键字1,导包的意义2,导包的格式3,注意事项package关键字1,包的概念包其实就是一个一个的文件夹,这些文件夹里放着Java中各种各样的类。在开发中会出现多个开发人员共同开发同一个项目的情况,这时肯定就会出现类......
  • 基于Java+Jsp Struts Mysql实现的图书馆管理系统设计与实现
    一、前言介绍:1.1项目摘要随着信息技术的飞速发展,传统图书馆的管理方式已经难以满足现代读者的需求。传统的图书馆管理方式通常依赖于人工操作,如图书的借阅、归还、分类、编目等,这些过程不仅效率低下,而且容易出错。同时,随着图书馆藏书量的不断增加,如何有效地管理这些图书......
  • 基于Java+Jsp+Springmvc+Mybatis3+Mysql实现的SSM个人博客系统设计与实现
    一、前言介绍:1.1项目摘要随着互联网技术的快速发展和普及,个人博客作为一种表达自我、分享知识和经验的方式,受到了越来越多人的青睐。传统的博客平台虽然提供了丰富的功能和便捷的服务,但往往难以满足个性化、定制化的需求。因此,开发一款基于SSM(Spring+SpringMVC+MyBat......
  • 【社招+校招】华为OD机试 - 运维日志排序(Java & JS & Python & C)
    鱼弦:公众号【红尘灯塔】,CSDN博客专家、内容合伙人、新星导师、全栈领域优质创作者、51CTO(Top红人+专家博主)、github开源爱好者(go-zero源码二次开发、游戏后端架构https://github.com/Peakchen)运维日志排序算法实现(Java、JavaScript、Python、C、C++)算法概述运维日志......
  • 【校招+社招】华为OD机试 - 统计射击比赛成绩(Java & JS & Python)
    鱼弦:公众号【红尘灯塔】,CSDN博客专家、内容合伙人、新星导师、全栈领域优质创作者、51CTO(Top红人+专家博主)、github开源爱好者(go-zero源码二次开发、游戏后端架构https://github.com/Peakchen)统计射击比赛成绩(Java、JavaScript、Python和C++)算法实现问题描述:在一......
  • 【java开发环境】多版本jdk 自由切换window和linux
    win10一、准备各种版本的jdk,按自己的需要下载。我这里是需要jdk17和jdk8。1、jdk17下载:JavaDownloads|Oracle,选择exe后缀文件2、jdk8下载:JavaDownloads|Oracle,选择exe后缀文件二、详细步骤1、安装jdk很简单,双击exe文件后全部默认下一步即可,安装的时候记住安装......
  • JAVA每日作业day7.1-7.3小总结
    ok了家人们前几天学了一些知识,接下来一起看看吧一.APIJava的API(API:Application(应用)Programming(程序) Interface(接口))JavaAPI就是JDK中提供给我们使用的类,这些类将底层的代码实现封装了起来,我们不需要关心这些类是如何......
  • JAVA多线程快速入门
    什么是多线程概述线程线程是操作系统能够进行运算调度的最小单位它被包含在进程之中,是进程中的实际运作单位简单理解应用软件中互相独立,可以同时运行的功能进程进程是程序的基本执行实体/系统分配资源的基本单位作用充分利用cpu提......