多线程强化(下)

简介: 多线程强化(下)

七、CountDownLatch



CountDownLatch:count down — 倒计时,latch — ( n. 门闩 )


我们可以将 CountDownLatch 机制想象成一个倒计时的仪器,在运动会的跑步比赛中,如果选手跑完所有路程,最终一定会 " 撞终点线 ",每个选手撞一次线,就有人会记录,或许是红外装置能够检测到。不管怎样,只要一个选手撞线了,说明一个人就跑完了赛道,那么就减一。而 CountDownLatch 也是一样的,只要一个任务完成了,利用 countDown() 方法计数一次,底层就减一。


程序清单7:


import java.util.concurrent.CountDownLatch;
public class Test {
    public static void main(String[] args) throws InterruptedException {
        //构造 CountDownLatch 实例, 括号中的 6 表示有 6 个任务需要完成
        CountDownLatch latch = new CountDownLatch(6);
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("起跑!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //相当于计数器自减
                //每个任务完成之后,就减一次
                latch.countDown();
                System.out.println("撞线!");
            }
        };
        for (int i = 0; i < 6; i++) {
            Thread t = new Thread(runnable);
            t.start();
        }
        //任务在执行中的时候,await 方法阻塞等待,直到所有任务都执行完成
        latch.await();
        System.out.println("比赛结束!");
    }
}


输出结果:


9df43a4adff041d5a11902b24873c9f7.png


JUC 包中的常见类



JUC:java.util.concurrent ,concurrent ( adj. 并发的 )

当前介绍的 JUC 包中的这些东西,主要是在工作中用的比较多。但在面试中出现的不是特别多( 也是会出现,但相比于前面的内容,出现概率会少一些 )


  • ReentrantLock 类:可重入互斥锁,通常使用 lock 和 unlock 加锁解锁
  • 原子类:底层使用 CAS 机制实现,原子类具有原子性,可以避免加锁
  • 线程池:ThreadPoolExecutor 和 Executors,前者复杂版本,后者简单版本
  • 信号量:Semaphore
  • CountDownLatch类:倒计时计数器


八、Java 标准库中的线程安全类



这里的集合类,大部分是线程不安全的。( 不能在多线程环境下去并发地修改同一个对象 )


例如:


ArrayList

LinkedList

HashMap

TreeMap

HashSet

TreeSet

StringBuilder


但还有些是线程安全的。


例如:

Vector (不推荐使用)

Hashtable (不推荐使用)

ConcurrentHashMap

StringBuffer


其中 Vector 和 Hashrable 为很多方法都加上了 synchronized,而大多数情况下,在我们使用单线程的时候,synchronized 就会带来负面影响。

还有的虽然没有加锁,但仍然是线程安全的,例如:String,因为 String 类描述的是一个不可变对象,所以不涉及 “修改”,那么加锁和无锁并没有区别。


1. 多线程环境使用哈希表


在上面的时候,我们提到 HashMap 是线程不安全的类,而 Hashtable 是线程安全的类,但不推荐使用,那我们怎么才能折中一下呢,使用既是线程安全,又是可以用的类呢?


我们先来看看 Hashtable 类:


这是我从源代码中,查看的 Hashtable 类,它在底层中,只是简单地把关键的方法加上了 synchronized 关键字,我只截取了部分加锁的方法,而还有的一些方法加了 synchronized ,有些方法没有加。


f138fa90456f4bddbe16d8a4950e767c.png


那么这就相当于直接针对 Hashtable 对象本身加锁,也就是说,整个哈希表只有一把锁,如果多线程访问同一个 Hashtable 就会直接造成锁冲突,size 属性也是通过 synchronized 来控制同步,所以也比较慢。一旦触发扩容, 就由该线程完成整个扩容过程,这个过程会涉及到大量的元素拷贝,效率会非常低。


那么问题就来了,一个 Hashtable 实际上就只有一把锁,多个线程访问 Hashtable 中的数据时,都会发生锁竞争,那么多个线程竞争同一把锁,一定会造成一个线程成功获取到锁,而其他线程阻塞等待,所以说,这个 Hashtable 带来的锁冲突还是比较激烈的,同时,发生锁冲突的概率也是比较大。


2. ConcurrentHashMap 类的优化


而 ConcurrentHashMap 实现的哈希桶是直接给每个哈希桶 ( 每个链表 ),分配了一个锁 ( 以每个链表的头结点对象作为锁对象 )


ConcurrentHashMap 相比于 Hashtable 做出了一系列的改进和优化,以 Java1.8 为例:


① 读操作没有加锁 ( 但使用了 volatile 保证从内存读取结果 ),只对写操作进行加锁,加锁的方式仍然是用 synchronized,但不是将整个对象加锁,而是 " 锁桶 " (用每个链表的头结点作为锁对象),大大降低了锁冲突的概率。


② 充分利用 CAS 机制,比如 size 属性通过 CAS 来更新,避免出现重量级锁的情况。


③ 优化了扩容方式,化整为零。发现需要扩容的线程,只需要创建一个新的数组,同时只搬几个元素过去,扩容期间,新老数组同时存在。后续每个来操作 ConcurrentHashMap 的线程,都会参与搬运的过程,每个操作负责搬运一小部分元素,搬完最后一个元素后,再把老数组删掉。这个期间,插入只往新数组加,同时,查找需要同时查新数组和老数组。


90fb0cedd5dc46df873f7db5dffcbba3.png


3. 相关面试题


(1) ConcurrentHashMap 的读是否要加锁,为什么?


读操作没有加锁,目的是为了进一步降低锁冲突的概率,为了保证读到刚修改的数据,搭配了volatile 关键字。


(2) 介绍下 ConcurrentHashMap的锁分段技术?


这个是 Java1.7 中采取的技术,Java1.8 中已经不再使用了,简单地说,就是把若干个哈希桶分成一个 " 段 " (Segment),再针对每个段分别加锁,目的也是为了降低锁竞争的概率,当两个线程访问的数据恰好在同一个段上的时候,才触发锁竞争。显然,这并没有 Java1.8 所使用的 " 锁桶 " 的并发程度高。


(3) ConcurrentHashMap 在 JDK1.8 中做了哪些优化?


取消了分段锁,直接给每个哈希桶 ( 每个链表 ) 分配了一把锁,( 以每个链表的头结点对象作为锁对象 )


(4) HashMap、Hashtable、ConcurrentHashMap 之间的区别和联系


HashMap:线程不安全,key 允许为 null


Hashtable:线程安全,使用 synchronized 锁 Hashtable 对象,效率较低,key 不允许为 null


ConcurrentHashMap:线程安全,使用 synchronized 来锁每个链表头结点,锁冲突概率低,充分利用 CAS 机制,优化了扩容方式,key 不允许为 null


九、死锁



说明


下面的一些代码只是为了演示死锁的情况,可能表达方式上会有些不恰当的地方,但我旨在说明逻辑和对应的原理。


1. 第一种情况


一个线程一把锁


6b7a65e9ee224d18ac4355d4c24f8f56.png


synchronized 内部已经记录了当前的锁是由哪个线程持有的。因此当再次尝试加锁的时候,就会进行判定,看看当前尝试加锁的线程是否就是持有锁本身的线程。如果是,就不会阻塞,而是把引用计数给自增。所以说,由于 synchronized 内部的这个机制,就决定了它是一个可重入锁,因此,上面的代码实际上会被优化。


2. 第二种情况


两个线程两把锁


7b0d49d1ab774282a3726e80945f6867.png


3. 第三种情况


多个线程多把锁




4. 是什么造成了死锁


我们必须明确,死锁产生的四个必要条件:


① 互斥使用:即当资源被一个线程使用( 占有 )时,别的线程不能使用。

② 不可抢占:资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。

③ 请求和保持:即当资源请求者在请求其他资源的同时,也保持对原有资源的占有。

④ 循环等待:起初,线程1 占有锁1 的资源,线程2 占有锁2 的资源。而接着线程1 尝试获取锁2,线程2 尝试获取锁1,这就陷入了循环等待,或者说休眠状态。


当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,便可让死锁消失。其中最容易破坏的就是第④个条件,( 循环等待 ),而前三个条件,在大部分情况下,不好干预。


5. 如何避免死锁


① 尽量避免复杂的设计,避免在某个锁的代码中,再尝试获取其他锁,即尽量不要嵌套锁。


② 如果有些应用场景下,必须使用锁的嵌套,那么,一方面我们要保证持有锁的时间足够短,代码足够简单;另一方面要保证按照统一的顺序来进行加锁。而这样的固定顺序,其实就是破坏了死锁的 " 循环等待 " 条件。


4530133ad6c04a118ee07baee939dedb.png


比方说,在刚刚的 " 哲学家就餐的问题上 ",我们为筷子编号,A 先使用 12 筷子,C 先使用 34 筷子,E 先使用 56 筷子,等他们吃好了,再让其他人用这些筷子…这就破坏了循环等待,最起码,这保持了一些线程在同步执行!

目录
相关文章
|
5月前
|
监控 Java 测试技术
Java并发编程最佳实践:设计高性能的多线程系统
Java并发编程最佳实践:设计高性能的多线程系统
82 1
|
2月前
|
监控 安全 定位技术
《C++新特性:为多线程数据竞争检测与预防保驾护航》
多线程编程是提升软件性能的关键,但数据竞争问题却是一大挑战。C++新特性如增强的原子类型和完善的内存模型,为检测和预防数据竞争提供了有力支持。这些改进不仅提高了程序的可靠性,还提升了开发效率,使多线程编程更加安全高效。
73 19
|
6月前
|
安全 数据安全/隐私保护 数据中心
Python并发编程大挑战:线程安全VS进程隔离,你的选择影响深远!
【7月更文挑战第9天】Python并发:线程共享内存,高效但需处理线程安全(GIL限制并发),适合IO密集型;进程独立内存,安全但通信复杂,适合CPU密集型。使用`threading.Lock`保证线程安全,`multiprocessing.Queue`实现进程间通信。选择取决于任务性质和性能需求。
104 1
|
6月前
|
安全 Java 数据处理
Java并发编程:线程同步与协作的深度解析
在探索Java并发编程的海洋中,线程同步与协作的灯塔指引着航向。本文将深入挖掘线程同步机制的核心原理,揭示锁、条件变量等工具如何确保数据的一致性和线程间有序的通信。通过案例分析,我们将解码高效并发模式背后的设计哲学,并探讨现代Java并发库如何简化复杂的同步任务。跟随文章的步伐,您将获得提升多线程应用性能与可靠性的关键技能。 【7月更文挑战第24天】
55 5
|
7月前
|
安全 Java 调度
Java并发编程:优化多线程应用的性能与安全性
在当今软件开发中,多线程编程已成为不可或缺的一部分,尤其在Java应用程序中更是如此。本文探讨了Java中多线程编程的关键挑战和解决方案,重点介绍了如何通过合理的并发控制和优化策略来提升应用程序的性能和安全性,以及避免常见的并发问题。
75 1
|
7月前
|
缓存 并行计算 安全
【并发编程系列一】并发编年史:线程的双刃剑——从优势到风险的全面解析
【并发编程系列一】并发编年史:线程的双刃剑——从优势到风险的全面解析
|
6月前
|
Java 测试技术 容器
多线程编程基础与并发问题解决方案
多线程编程基础与并发问题解决方案
|
7月前
|
安全 程序员
多线程的6个综合练习
多线程的6个综合练习
50 0
|
8月前
|
安全 物联网 Java
未来交织:新兴技术的融合与革新深入理解Java并发编程:线程安全与性能优化
【5月更文挑战第27天】 在数字化的浪潮中,创新技术如同星辰般璀璨,引领着时代的前行。本文聚焦于区块链、物联网(IoT)、虚拟现实(VR)等前沿科技,剖析它们的发展脉络,并探讨这些技术的交互融合与实际应用。通过深入分析,我们预见这些技术将如何重塑经济结构、改善人类生活,并引发社会层面的深刻变革。
|
缓存 监控 安全
【并发技术系列】「并发编程」技术体系和并发模型的基础探究(夯实基础)
【并发技术系列】「并发编程」技术体系和并发模型的基础探究(夯实基础)
172 0
【并发技术系列】「并发编程」技术体系和并发模型的基础探究(夯实基础)