多线程进阶(二)Callable接口,JUC下常见类使用及线程安全集合类

news2025/1/16 8:18:06

目录

前言:

Callable接口

代码实现

JUC下常见类使用

ReentrantLock类

代码实现

信号量

代码实现

CountDownLatch类

代码实现

线程安全的集合类

多线程环境下使用ArrayList

多线程环境下使用队列

多线程环境下使用哈希表

小结:


前言:

    这篇文章主要介绍Callable接口,JUC包下一些常见类的使用,还有我们之前使用集合类在多线程环境下的使用。

Callable接口

    可以使用Callable接口创建带有返回值的线程任务(和Runable类似)。这样的线程具有返回值,由于线程调度的随机性,我们不确定线程什么时候被调度,具体线程任务什么时候执行完毕。基于这样的问题采取FutureTask类对Callable进行包装。FutureTask就可以等待Callable的执行结果。

    FutureTask提供的get方法就可以获取Callable的返回结果。它会阻塞,直到Callable里的任务执行完毕。

代码实现

public class ThreadDemo30 {
    public static void main(String[] args) throws Exception {
        Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int sum = 0;
                for(int i = 0; i <= 100; i++) {
                    sum += i;
                }
                return sum;
            }
        };
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        Thread t = new Thread(futureTask);
        t.start();
        //get方法获取到结果,这里会进行阻塞,直到callable执行完毕,才能获取到结果
        Integer tmp = futureTask.get();
        System.out.println(tmp);
    }
}

JUC下常见类使用

ReentrantLock类

    ReentrantLock和Synchronized类似,都是加锁的。那么为什么存在Synchronized还要有ReentrantLock呢?下面介绍它的优点:

     1)ReentrantLock的加锁和解锁是分开的,可以更加灵活的使用。

     2)提供了公平锁的实现,只需要在构造方法中参数写为true即可。不写或者写false都是非公平锁

     3)Synchronized产生的等待是死等,而它提供了tryLock()方法,返回值为boolean(是否加锁成功)。无参数版本能加锁就加不能则放弃。有参数版本可指定阻塞最大时间,如果时间到了不能获取到锁也就放弃了。 

     4)Synchronized加锁使用notify随机唤醒一个线程。它搭配Condition类指定唤醒某个线程。

    ReentrantLock提供lock()方法加锁,unlock方法解锁。由于两者是分开的,如果加锁成功了,为了能保证unlock方法一定可以执行我们将其写在finally代码块中。

代码实现

public class ThreadDemo31 {
    volatile private static int sum = 0;
    private static ReentrantLock reentrantLock = new ReentrantLock(true);
    public static void func() {
        try {
            reentrantLock.lock();
            for(int i = 0; i <= 100; i++) {
                sum += i;
            }
        }finally {
            reentrantLock.unlock();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                func();
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                func();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(sum);
    }
}

信号量

    信号量为可用资源个数。如果信号量为0,继续申请可用资源个数,就会阻塞(信号量不能为负数)。锁就可以视为计数器为1的信号量(二元信号量)。

    可用资源个数就是具体的一个数字。描述了这个信号量可以提供的最大可用资源的数量,每申请一个可用资源,其计数器就减1。释放一个可用资源计数器就加1。

    Java中提供Semaphore类来实现信号量。acquire()方法申请信号量,release()方法释放信号量,构造方法传递一个参数就是初始化信号量个数。

代码实现

public class ThreadDemo32 {
    public static void main(String[] args) throws InterruptedException {
        //初始化信号量为3
        Semaphore semaphore = new Semaphore(3);
        //申请信号量(个数 -1),可以指定参数一次就申请多个
        semaphore.acquire();
        semaphore.acquire();
        semaphore.acquire();
        semaphore.release();
        semaphore.acquire();
        semaphore.acquire();
        //释放信号量(个数 +1),可以指定参数一次就释放多个
        //semaphore.release();
    }
}

     注意:可以清楚看见代码在阻塞当中。整个进程都没有结束。

CountDownLatch类

    CountDownLatch类可以实现如果有10个线程,可以使另一个线程阻塞到这10个线程全部执行完毕。类似的场景比如跑步比赛,只有当最后一个人到达终点才算比赛结束。

    构造方法提供一个参数,描述了具体任务的数量。countDown()方法来体现一个任务执行完毕,await()方法阻塞到初始任务数量全部执行完毕,那么意味着只有和任务数量一致那一次countDown()方法才起作用。

代码实现

public class ThreadDemo33 {
    public static void main(String[] args) throws InterruptedException {
        //具体有10个任务
        CountDownLatch latch = new CountDownLatch(10);
        for(int i = 0; i < 10; i++) {
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    //任务执行完成,调用countDown方法,表示任务执行完成
                    latch.countDown();
                }
            });
            t.start();
        }

        //阻塞到10个任务全部执行完成,第10次调用countDown方法才起作用
        latch.await();
        System.out.println("Aaaaaa");
        //使用场景跑步比赛
        //等待所有人跑完才算结束
    }
}

线程安全的集合类

    Vector,Stack,HashTable是线程安全的,但是不建议使用。其他线程都是线程不安全的。

多线程环境下使用ArrayList

 1)可以自己手动加锁,来保证线程安全。

 2)使用Collections类下的synchronizedList静态方法,对ArrayList进行包裹。synchronizedList关键操作都带有synchronized。如下图源码可以清楚看见。

 3)CopyOnWriteArrayList

    CopyOnWriteArrayList即写时复制的容器。针对读数据不做任何工作。针对写操作,首先会拷贝一份新的ArrayList,在这份新的当中写(两个不同的对象不会存在线程安全问题)。如果在写的期间需要读,就读旧的ArrayList,当新的写完后在替换到旧的上面去(替换的本质就算引用之间的修改,原子的)。

优点:

    写时拷贝,不需要加锁,就可以实现线程安全。代码效率高。

缺点:

    它只适合数据量比较小的(拷贝需要时间),并且占用内存较多,新写的数据不能第一时间读取到。

多线程环境下使用队列

1)ArrayBlockingQueue

    基于数组实现的阻塞队列。

2)LinkedBlockingQueue

    基于链表实现的阻塞队列。

3)PriorityBlockingQueue

    基于堆实现带有优先级的阻塞队列。

4)TransferQueue

    最多只包含一个元素的阻塞队列。

多线程环境下使用哈希表

    HashMap多线程环境下是不安全的。HashTable多线程环境下线程安全(给主要方法上加了一把大锁)。ConcurrentHashMap更优化的线程安全哈希表。

优化之处:

1)将HashTable的一把大锁改为了小锁

     如果两个元素在同一个链表(树)上,多线程下是不安全的。但如果不在同一条链表(树)上多线程下是安全的(两个元素间没有联系)。HashTable不管在没在同一个链表上,锁是加在方法上的,只要调用了这样的方法就会阻塞,那么两个元素在不同的链表上也是会阻塞。ConcurrentHashMap锁是加在每条链表或者树的头节点上的。元素在不同链表上由于锁对象不同,则不会产生锁竞争。元素在同一条链表上,锁对象相同,则产生锁竞争。

2)针对读不加锁,针对写加锁

    读和读之间没有锁竞争。写和写之间存在锁竞争。读和写之间不存在锁竞争,这样就可能造成脏读问题(读了一条不全的数据),基于这样的问题,这里的写操作设计为:volatile + 原子写操作。那么就只有写完才能读数据,就不会存在脏读问题。

3)充分使用CAS

    充分使用CAS,进一步减少锁的数量。比如维护元素个数。

4)针对扩容采取“化整为零”的方式

    HashMap/HashTable扩容时首先开辟一块更大的数组,将旧数组上数据重新哈希到新数组上,再释放旧数组。如果数据量较大,可能某次put操作就比较耗时。

    ConcurruteHashMap如果需要扩容,首先开辟一块更大的数组,会每次搬运一小部分数据。保留新旧两个数组,put时就往新数组上哈希,并且搬运一部分旧数组数据到新数组上(后续只要操作ConcurruteHashMap的线程都会参与搬运的过程),删除旧数组上被搬运的元素。直到旧数组数据全部搬运完毕,释放旧数组。查找元素时两个数组都查找。删除元素时,两个数组也查找,找到就正常删除即可。

小结:

    与大家分享一句名言:真正的才智是刚毅的志向。 -----  拿破仑

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

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

相关文章

图解24种经典k线图

相信不少国内的老股民心中都藏着一份经典的K线图图解&#xff0c;当中也许有6种、12种或24种能揭示行情方向转变的“K线脸谱”&#xff0c;借由它们的对行情的预知作用&#xff0c;股民度过一次又一次的熊牛更替。其实只要善于变通&#xff0c;它们同样适用于贵金属投资&#x…

再说多线程(一)

世界是并行&#xff01;做过复杂项目的朋友一定遇到过并发的问题&#xff0c;无论是大项目如订票系统&#xff0c;还是小项目中的文件管理都会有并行需求。所以不同于上学时接触的大部分代码&#xff0c;实际的业务往往是为多人提供服务&#xff0c;必然天然的带有并发的需求。…

Mybatis-plus 使用 typeHandler 将 String 拼接字符串转换为 List 列表

一、需求描述 首先说明需求&#xff0c;有三张表&#xff1a; 学生表、角色表、以及一张关联的中间表。 学生可以有多个角色&#xff0c;但是这多个角色我是作为多条记录存储在另外一张表中的&#xff0c;现在想将这多条记录查询出来&#xff0c;注入到Student对象中的一个L…

微服务之JVM调优

一、Xms Xmx Xss等定义及功能 1.Xms 是指设定程序启动时占用内存大小。一般来讲,大点,程序会启动的快一点,但是也可能会导致机器暂时间变慢。 2.Xmx 是指设定程序运行期间最大可占用的内存大小。如果程序运行需要占用更多的内存,超出了这个设置值,就会抛出OutOfMemory异…

易基因|14种全基因组DNA甲基化测序(WGBS)标准分析比对软件的比较| 生信专区

大家好&#xff0c;这里是专注表观组学十余年&#xff0c;领跑多组学科研服务的易基因。全基因组重亚硫酸盐测序&#xff08;WGBS&#xff09;是甲基化研究的重要技术。尽管已经开发了一系列工具来解决由亚硫酸盐处理引起的比对问题&#xff0c;但尚未对最新可用工具的reads比对…

HashMap,Hashtable,ConcurrentHashMap

目录 一、多线程使用HashMap的一些线程安全问题 ①造成数据新增丢失 ②扩容时候&#xff0c;造成链表成环 二、Hashtable和HashMap的区别 ①核心方法加锁 ②其他语法上面的略微差异 三、引入ConcurrentHashMap【重要】 ①ConcurrentHashMap相比于Hashtable的优势 Hashtab…

著名相声艺术家侯耀华,77岁寿宴现场曝光,郭德纲师哥前去祝贺

在中国的相声界&#xff0c;有一条不成文的规定&#xff0c;关于著名相声表演艺术家的判定&#xff0c;从来不是以相声水平高低为标准。只要你有足够长的寿命&#xff0c;只要你能把其他人都熬走熬败&#xff0c;就算你是一个相声小白&#xff0c;也能摇身一变成为艺术家。 不过…

Git介绍与使用

1.集中式版本控制 svn 中央服务器 所有的版本数据都存在服务器上&#xff0c;用户本地只有自己所同步的版本&#xff0c;如果不联网的话&#xff0c;用户就看不到 SVN是集中式版本控制系统&#xff0c;版本库是集中放在中央服务器的 而工作的时候,用的都是自己的电脑,所以首先…

跨境电商物流系统功能框架

随着国内互联网巨头们逐渐将更多注意力投向了跨境电商市场&#xff0c;电商巨头出海也在掀起新的发展高潮。下面是跨境电商物流系统功能框架&#xff0c;供大家参考1、OMS叫做订单管理系统&#xff08;Order Management System&#xff09;&#xff0c;在不同公司&#xff0c;不…

云原生时代的运维体系进化

云原生已经成为数字经济技术的创新基石&#xff0c;并且正在深刻地改变企业上云和用云的方式。云原生的用云方式可以帮助企业最大化获得云价值&#xff0c;也给企业的计算基础设施、应用架构、组织文化和研发流程带来新一轮变革。而业务和技术挑战也催生了新一代云原生运维技术…

设计模式(一)----设计模式概述及UML图解析

1、设计模式概述 1.1 软件设计模式的产生背景 "设计模式"最初并不是出现在软件设计中&#xff0c;而是被用于建筑领域的设计中。 1977年美国著名建筑大师、加利福尼亚大学伯克利分校环境结构中心主任克里斯托夫亚历山大&#xff08;Christopher Alexander&#xf…

Golang开发 02

文章目录一、Golang开发工具二、visual studio code安装(VS code)1、安装window2、安装mac、linux一、Golang开发工具 # 1、Visual studio code &#xff08;常用&#xff09; # 2、Sublime Text(免费) # 3、Vim # 4、Emacs # 5、Eclipes IDE工具&#xff0c;开源免费&#xf…

数据分析-深度学习PytorchDay1

深度学习框架pytorch学习(一)准备环境准备环境一、深度学习框架简介二、Tensorflow与Pytorch的比较三、安装开发环境一、深度学习框架简介1、Google阵营最早的是由加拿大团队开发的theano一个机器学习库&#xff0c;现在已经停止更新。接着Google开发了Tensorflow&#xff0c;并…

【机器学习知识点】3. 目标检测任务中如何在图片上的目标位置绘制边界框

目录前言导入图片定义边界框绘制函数在图片中绘制边界框总结前言 在图像分类任务中&#xff0c;很多时候我们不仅要知道图像中目标的类别&#xff0c;而且还想知道它们在图像中的具体位置。在计算机视觉里&#xff0c;这类任务被称为目标检测&#xff08;object detection&…

uniapp开发技术

目录 1、js 判断iPhone|iPad|iPod|iOS|Android客户端 2、js实现防抖 3、 js实现节流 4、 页面在弹窗时禁止底部页面滚动&#xff08;h5端&#xff09; touchmove.stop.prevent 5、scrollIntoView 1、js 判断iPhone|iPad|iPod|iOS|Android客户端 // fullScreen代表整个页面…

【C++】STL---list的模拟实现

目录前言一、list和vector的区别二、节点的定义三、list类定义四、push_back函数五、push_front函数六、迭代器七、begin和end函数八、迭代器区间初始化九、迭代器的操作符重载操作符重载操作符- -重载操作符&#xff01;重载操作符重载操作符*重载十、insert函数十一、erase函…

如何应用卫星图像插入到Auto CAD

如何应用卫星图像插入到Auto CAD发布时间&#xff1a;2018-01-17 版权&#xff1a;工具准备BIGEMAP GIS Office&#xff1a;http://www.bigemap.com/reader/download/案例&#xff1a;等高线完美套合卫星影像教程本实例使用AutoCAD2008软件进行影像与矢量数据叠加配准。影像获取…

变压器和特斯拉线圈

目录 变压器用途 变压器的原理 变压器特点 特斯拉线圈用途 特斯拉线圈原理 特斯拉线圈特点 参考&#xff1a; 变压器用途 电压变换、电流变换、阻抗变换、隔离、稳压等 1&#xff09;开关电源&#xff0c;充电器&#xff0c;220v转换为指定电压&#xff0c;以给各类电子…

Revit建模幕墙问题:幕墙添加门/窗和生成幕墙

一、Revit中如何在幕墙当中添加门、窗构件 今天跟大家分享一下幕墙当中添加门窗的方法&#xff0c;这种方法大家可以联想到很多应用上&#xff0c;因为这个既是个方法也是个技巧&#xff0c;好了&#xff0c;我们直接进入主题吧。 首先&#xff0c;我们新建幕墙&#xff0c;给它…

范数的意义与计算方法

1. 范数的意义 范数可以简单的理解为“距离”。由于向量是既有大小又有方向的量&#xff0c;所以向量是不能直接比较大小的&#xff0c;但是范数提供了一种方法&#xff0c;可以将所有的向量转化为一个实数&#xff0c;然后就可以比较向量的大小了。&#xff08;注&#xff1a…