并发编程知识体系

基础知识

并行和并发

  • 并发

    • 强调一个任务的交替执行,指的是多个任务在同一个时间片上交替去执行

      • 就像一个家庭主妇,又要带孩子,又要做饭,又要洗衣服,同一时间只能做某一件事情,然后交替去做这几件事情

  • 并行

    • 强调多个任务同时执行,指的是多个任务在同一时间片上被多个cpu同时执行

      • 还是家庭主妇,但是请了两个保姆,家庭主妇在带孩子的同时,保姆一可以去做饭,而保姆二可以去洗衣服,他们可以在同一时间去做这三件事

异步

  • 任务可以不必按照顺序执行,程序可以执行其他的任务,而不是等待异步任务完成后再去执行

    • 文件读取这种耗时的操作就可以采用异步的方式

线程和进程

  • 进程

    • 操作系统在运行一个程序时,会为其创建一个进程

  • 线程

    • 操作系统调度的最小单位,也叫做轻量级进程,一个进程中含有多个线程

多线程的好处

  • 更多的处理器核心

    • cpu核心数的增加,能够同时处理更多的任务,达到更好的并发能力

  • 更快的相应速度

    • 多个线程并行处理任务,缩短处理时间,得到更快的速度

使用多线程带来了什么问题

  • 上下文切换

    • 因为cpu核心是切换时间片去循环执行任务的,在进行上下文切换的过程时,需要保存当前线程的执行环境到内存中,然后将下一个线程执行环境重新加载到内存中,这个过程会带来线程开销

  • 死锁

    • 两个线程循环等待对方的所释放,造成一直无法执行,就会造成死锁

  • 资源限制

    • 进行并发编程时,程序的执行速度受限于计算机的硬件和软件资源

      • 硬件资源

        • 带宽的上传和下载速度

        • 硬盘的读写和cpu的处理速度

      • 软件资源

        • 数据库的连接数

        • socket的连接数

    • 资源限制出现的问题

      • 并发编程之所以能够提升速度,就是将串行执行的程序转化为并行执行,但是如果受限于资源,程序仍然是串行执行的,反而因为增加了上下文切换,速度更慢

        • 硬件资源受限解决方案

          • 既然单机资源受限,我们考虑做集群,用多台机器去抗衡资源受限

        • 软件资源受限解决方案

          • 考虑使用资源池将资源复用-使用连接池将数据库连接和socket连接复用

多线程的基本使用

  • 多线程执行流程

    • 当一个线程对象被创建之后,它会被JVM进行管理。当调用线程对象的start方法时,JVM会为该线程对象映射一个操作系统级的线程,并通知操作系统去创建一个对应的线程执行该线程对象需要执行的代码

  • 线程的生命周期

    • 正常流程

      • 一个线程被创建(初始态)->调用start方法启动这个线程(运行态)->线程代码执行完成,线程被销毁(终止态)

    • 异常情况

      • 线程执行过程调用了wait/join方法,进入了等待状态(waiting),直到被其他线程的notify/notifyAll唤醒之后重新回到运行状态

      • 线程执行过程中调用了wait(time)/sleep(time)/join(time)方法时,从运行态转变为超时等待状态,直到等待时间过了或者是被其他线程的notify/notifyAll唤醒恢复运行态

      • 线程执行过程中没有获取到锁资源,会从运行态转变为blocked阻塞态,直到获取到锁资源之后才恢复运行态

  • 线程的停止方式

    • 主动停止

      • 线程在代码块执行完成进入终止态之后,此时就主动停止了,即一个线程的任务执行结束主动停止

    • 被动停止

      • 强制终止

        • 使用stop方法强行中断一个线程

          • 只是将线程停止但是线程中的任务不一定完成,强行去中断线程类似于Linux的kill -9命令,会导致数据出现问题,是一种不安全的操作,在JDK11中已经废除,不建议使用

      • 线程自己选择性终止

        • 场景一:线程中存在无限循环的情况,无法自动中断(whlie(true)循环)需要外部干预

        • 场景二:线程被阻塞(wait/join/sleep)

          • 我们在平常使用线程的sleep/wait/join等方法,都会抛出一个interruptedException异常,因为它在阻塞期间,必须要能够响应被其他线程发起中断请求之后的一个响应

        • interrupt

          • 其他线程通过调用当前线程的interrupt方法,表示它向当前线程打个招呼,告诉它可以中断线程的执行,至于什么时候执行,取决与当前线程自己,线程通过检查自身是否被中断进行响应,可以通过isInterrputed()判断是否被中断(中断状态标志位)

线程的通信

  • volatile

    • 可以用来修饰成员变量,作用是告知程序任何对该变量访问均需要从共享内存中获取,而对他的改变必须同步刷新回共享内存

      • 保障所有线程对共享变量的可见性

  • synchronized

    • 可以用来修饰方法或者同步代码块,确保在多线程环境下,只能有一个线程处在方法或者同步代码块中

      • 保障线程对变量的可见性和排他性

  • wait/notify

    • 等待通知机制,线程A调用wait方法进入等待状态,释放锁阻塞线程,等待其他线程唤醒,线程B抢占到锁之后调用notify/notifyAll方法去唤醒处于等待状态的线程,让其可以有抢占锁的资格

    • 流程

      • 和Lock锁流程差不多,唯一多的是一个阻塞队列,并且wait/notify是基于synchronized做线程通信的,与Condition在这个方面唯一的不同

        • 线程A抢占到锁,然后调用wait方法阻塞掉线程A,进入阻塞队列等待其他线程唤醒,将锁资源释放出去,然后线程B抢占到锁之后调用notify/notifyALL方法去唤醒处于阻塞队列中的一个或全部线程,转移到等待队列中,等到锁资源释放之后可以进行抢占锁

  • join

    • 控制线程的执行顺序,按照预期的顺序进行

      • 当线程A调用lmain.join方法时,会等待main线程执行完成之后在执行线程A

  • Condition

    • J.U.C包下的等待通信机制,使用的是Lock锁进行线程通信,通过wait/signal方法使得线程等待或者唤醒

    • 流程

      • 线程A调用lock.await方法之后,进入阻塞状态,释放锁资源,等待其他线程调用signal/signalAll方法进行唤醒,而其他线程获取到锁资源之后,调用lock.signal方法去唤醒处于阻塞队列中的线程,转移到竞争的队列中,获得抢占锁的资源

核心问题之并发安全

原子性问题

  • 现象

    • 就是一个共享变量,然后开很多线程去对这个共享变量进行累加操作,比如三个线程每个都循环10000次,按道理来说预期结果应该是10000*3,而最终结果却小于30000次

  • 本质

    • 本质就是线程在对共享变量进行累加操作的时候,这个过程并不是原子性的,看起来i++只有一个步骤,实际上有两步:读取i,对i进行累加

      • 所以说只有一个指令的操作,其实内部包含了多条指令去执行,当多个线程对共享变量进行修改时,会引发安全问题,造成修改丢失

  • 解决方案

    • cas操作(乐观锁)

      • compare and swap :比较在交换,会先去比较预期的值,如果和预期的值不一样,就重新产生尝试,直到与预期的结果一致再进行修改操作

    • synchronized(悲观锁)

      • 基本应用

        • ①修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁。②静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁。③修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

      • 原理

        • 线程在抢占锁,使用synchronized进行同步的时候,锁的信息会被存储到对象头中,当线程进入锁方法或者是锁同步代码块的时候,会去尝试获取锁,获取到锁之后,会更改锁信息对象的对象头中的标记位,标记为已经获取到了锁,其他对象在尝试获取锁的时候,在看到锁对象的对象头中标记位锁已经被抢占之后,就无法再去获得锁对象,总而言之,就两点,弄懂锁对象在内存中的存储布局以及锁对象在被抢占之后标记位的改变,便是同步锁synchronized的互斥原理所在

      • 锁升级

        • 四种状态

          • 锁主要存在四中状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。

        • 设计目的

          • 为了减少重量级锁带来的性能开销,尽量在无锁状态下解决线程并发问题 其中偏向锁和轻量级锁底层是基于自旋锁,相对于重量级锁来说,算是一种无锁的实现

        • 锁升级流程

          • 1.用户线程进来之后,偏向锁默认是开启状态(JDK15会被废除,后面讲) 2.只有一个线程来抢占锁,这个线程就会去抢占偏向锁,抢占到之后有一个把对象头的线程ID改为当前线程ID的过程,偏向锁指向获取锁的线程 3.如果再来一个线程抢占锁,此时就会有锁竞争的情况,就会撤销偏向锁,升级为轻量级锁(CAS保证线程安全性) 4.如果资源竞争加剧,比如自旋次数超过一定的次数,或者超过一半CPU核心数的线程都在自旋,就会升级为重量级锁,没有抢到锁的线程会被阻塞(由用户态转化为内核态之后发送指令去阻塞这个线程)但是这个自旋次数也不是固定不变的,在JDK1.6之后,引入了自适应自旋的的操作,根据上次竞争的情况自动控制自旋的时间,更好的提升效率,不会一味的自旋指定次数在阻塞

        • 阻塞线程的性能损耗

          • 锁升级的过程这么麻烦,还得升级锁消耗性能,为什么非得升级不直接阻塞呢? 阻塞线程是由用户态转化为内核态发送指令将没有抢到锁的线程阻塞,这个过程其实是一个比较耗费性能的动作,所以在升级为重量级锁的过程,让用户线程循环的去获取锁,重试几次所带来的性能开销,如果小于切换内核态唤醒线程状态带来的开销,那没有理由不去选择锁升级这个过程

        • 为什么偏向锁被废除

          • 锁的升级关乎于线程竞争的激烈程度,但是当我们没有锁竞争的情况,默认的锁并不是想象中的偏向锁,而是轻量级锁 我们知晓,偏向锁在JDK15之后就被废除了,为什么被废除,不就是因为不好用或者作用范围不大嘛,事实上也是如此,JVM中有一些默认启动的线程,这些线程中有很多加了synchronized的代码,启动时就会存在竞争的情况,如果直接使用偏向锁,锁的升级与撤销势必会影响效率,所以有一个延时四秒再开启它的优化,不是否定偏向锁,而是在一些场景下没有竞争的情况,偏向锁还是有一定的作用范围,但是,如果一开始就有竞争,造成锁升级和撤销偏向锁的性能损耗,也是不提倡的,这也是在多线程情况下,在JDK15将偏向锁废除的一大理由

从硬件层面分析JVM的有序性、可见性

  • 一图了解发展过程,从高速缓存探讨一致性问题,因一致性问题造成阻塞引出异步操作,规避异步操作带来的指令重排序引出volatile

  • ①高速缓存

    • 由于cpu、内存与磁盘性能方面的差异性太大,要知道cpu的速度要远远快于内存与磁盘,而cpu对数据进行处理必须要从内存中读取到数据进行运算,可能cpu运算的时间仅仅只需要1毫秒,而内存中读取到这个数据需要100毫秒,所以为了避免cpu的利用率过低,在cpu中增加高速缓存,直接从高速缓存中去拿取数据,提高cpu的利用率

      • 探讨对象在内存中的布局:对象头、对其填充、实例数据

        • cpu在内存中是以块加载的模式,也就是一次性去加载多个块,避免频繁的与内存交互影响性能,对齐填充就是一种以空间换时间的思想,去保证每一块只有一个数据,通过对齐填充的方式,在变量之间添加一些额外的填充数据,不同的变量位于不同的缓存行,当多个CPU核心同时需要访问这些变量的时候,因为对齐填充,每一个变量在一个缓存中,而不是三个变量在两个缓存行中,这样当不同和核心需要访问一些变量的时候,他们是不是同时访问造成的冲突更小呢,即使多个核心同时访问这些变量,它们实际上是在不同的缓存行上进行操作,从而避免了不必要的缓存行同步和失效,提高了多核并发访问时的性能,降低多核CPU读取同一缓存行的竞争

  • ②缓存一致性

    • 为了优化cpu的性能采用了高速缓存,缓存必定会出现缓存一致性问题,如何保障从cpu的高速缓存中拿到的是和内存中一致的数据,是值得我们进行探讨的

      • 总线锁

        • 在多核cpu中,其中一个cpu对共享内存中的数据进行操作,为了保障操作的安全性,锁住其他cpu核心对共享内存操作,这种方式虽然安全,但是效率大打折扣

      • 缓存锁

        • 相比较总线锁而言,缓存锁降低了锁的粒度,他不在像总线锁那般只允许一个cpu核心对共享内存中的变量进行操作,而是允许多个cpu核心在同时操作共享内存的时候,需要遵顼一个协议,来保证读写操作时缓存与内存的一致性,这个协议就是MESI协议,缓存锁就是基于这个协议进行实现

          • MESI协议

            • 定义

              • MESI分别代表缓存行的四种状态,我们需要知晓的是缓存锁的作用范围是某个缓存行

                • M(独占,不一致)

                  • 共享数据之缓存在当前cpu缓存中,且与共享内存中的数据不一致

                • E(独占,一致)

                  • 共享数据之缓存在当前cpu缓存中,且与共享内存中的数据一致

                • S(共享,一致)

                  • 共享数据缓存在多个cpu缓存中,且与共享内存中的数据一致

                • I(失效)

                  • 表示当前缓存行已失效

            • 核心点

              • MESI协议就是保障在修改缓存的时候,得先让处于共享状态下的缓存失效,失效之后才能做同步,避免出现缓存一致性的问题,就是遵循在读取和修改的时候达到MESI协议中的S,内存中的数据被多个CPU缓存,各个缓存中的数据与内存中的数据保持一致

            • 失效场景

              • cpu不支持缓存一致性协议

              • MESI协议是针对单独的一个缓存行进行加锁,如果数据超出一个缓存行的大小,则失效

  • ③异步优化带来的可见性问题

    • 背景

      • 由于规避缓存一致性问题,遵顼MESI协议时必然会对性能带来影响,因为在对共享变量进行修改的时候,遵循的MESI协议必然会先使处于不一致的cpu缓存失效,然后在更新完刷回主内存在刷回其他cpu核心的时候,会去阻塞到其他核心对于缓存行的读写,大大影响效率,所以异步操作同样也是对缓存锁的性能损耗的一种优化,他们允许处理器核心在必要时延迟写回到内存、延迟更新缓存行的状态

    • 本质

      • 某个线程在写入共享变量后,其写入操作被乱序执行到了之后,其他线程可能无法立即看到这个写入操作的结果,因为其他线程看到的顺序是不一致的。(指令优化)

      • 当一个处理器核心修改了某个共享变量的值后,这个变化可能不会立即被其他处理器核心的缓存所感知(遵循一致性协议的优化)

  • ④内存屏障避免可见性

    • 读屏障

      • 读屏障告诉处理器在执行任何加载操作之前,必须先执行所有已经在失效队列中的失效操作的指令,就是说我读这个缓存中的数据,必须保障那种失效指令已经执行完毕的状态

    • 写屏障

      • 在执行写屏障之后的指令之前,必须先执行所有已经在存储缓冲区中保存的写操作,就是说我这个修改的缓存数据,必须先刷回主存然后保障所有线程对这个变量的读是最新的

Java内存模型

  • 由于不同操作系统有不同的内存屏障,所以定义了java线程访问内存的一个规范,屏蔽不同操作系统以及平台之间的一个差异化,保障Java程序在各平台下去访问内存都能达到一致的机制和规范

    • JMM定义

      • 每个线程都有自己的工作区域,叫做工作内存,是主内存中共享变量的副本 线程对共享变量进行写操作时,首先将数据写入自己的工作内存,然后再同步到主内存中,使得其他线程可以看到这个更新后的值。 当线程需要读取共享变量时,首先将数据从主内存中读取到自己的工作内存中,然后再进行操作。

    • 重排序

      • 编译器优化的重排序

      • 内存系统的重排序(读/写缓冲区)

      • JMM可以通过内存屏障来禁止这种重排序,保障指令的有序性

    • happens-before模型

      • 背景

        • 在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作必须要存在 happens-before关系。这两个操作可以是同一个线程,也可以是不同的线程。这个概念保证了在多线程环境下不需要通过增加volatile关键字,也能保证在多线程环境下的可见性和有序性

      • 规则

        • 1.程序顺序规则:不管指令如何重排序,单线程程序执行结果一定不会发生变化 2.传递性规则: int a=2; //A int b=2; //B int c=a*b; //C A happens-before B。 B happens-before C。 A happens-before C。 3.volatile变量规则:volatile修饰的共享变量需要满足一些条件,比如说volatile变量写happens-before 对于volatile变量读操作 4.监视器锁规则:锁代码修改的变量在释放锁被其他线程抢到之后,其他变量一定能看到这个修改之后的结果 5.start规则:在start之前的指令一定会Happens-Before之后的指令,之前对共享变量的修改一定保持着一个可见性 6.join规则:线程ajoin线程b,线程a对共享变量的修改一定对线程b可见 Happens-before规则保证多线程操作下对共享变量的正确访问

工具层

基础工具

  • ThreadLocal

    • 核心作用

      • 核心作用也是解决线程安全问题的,本质上就是多个线程对共享变量的读和写,引起的问题 ----->ThreadLocal通过线程隔离方式去解决这些问题 线程隔离:希望获得到共享变量的值是初始值,不受其他线程对共享变量修改的影响 使用ThreadLocal对共享变量修改之后发现,每一个线程对共享变量修的值全部不受其他线程的影响

    • 基本使用

      • ①全局用户信息的存储

        • 可以将用户信息(如用户 ID、用户名、权限等)保存到 ThreadLocal 中,这样不同线程可以根据自己的线程上下文获取对应的用户信息,而不必传递参数或依赖全局变量。这种方式适用于各种需要在线程间共享用户信息或上下文的情况,例如 Web 应用中处理用户请求时可以将用户信息存储在 ThreadLocal 中,方便后续业务逻辑获取和使用。避免与不同用户的信息造成混淆。

      • ②在 Spring 中,使用 ThreadLocal 来管理数据库连接

        • 在使用数据库连接池的场景下。通过将数据库连接存储在 ThreadLocal 中,可以确保每个线程都可以独立获取和管理自己的数据库连接,避免多个线程之间共享数据库连接引发的并发安全问题。

    • 底层原理

      • API

        • ①set() 在当前线程范围内,设置一个值存储到ThreadLocal中,这个值仅对当前线程可见->相当于在当前线程范围内建立了副本。 ②get() 从当前线程范围内取出set方法设置的值. ③remove() 移除当前线程中存储的值 ④withInitial java8中的初始化方法 ,快速去初始一个线程副本的值

    - 原理一览
​
        - 每一个线程使用一个独立的私有结构进行存储key-value,即每一个线程都有自己独立的ThreadLocalMap与其他线程的数据进行隔离
​
- 内存泄漏
​
    - 引发原因
​
        - ThreadLocal 没有手动移除:如果在使用 ThreadLocal 的过程中没有手动调用 remove 方法清除对应的值,当线程结束时 ThreadLocalMap 中关联的 Entry 对象(包含 ThreadLocal 实例和值)可能无法被正确清理,从而导致内存泄漏。
​
        - ThreadLocal 引用外部对象:如果 ThreadLocal 实例被设置为静态变量,或者与其他长生命周期的对象发生了强引用关系,而这些对象又引用了外部对象,那么即使 ThreadLocalMap 中的 Entry 对象可以被回收,但外部对象仍可能无法被释放,造成内存泄漏。
​
        - 线程池中使用 ThreadLocal:在使用线程池时,如果 ThreadLocal 没有及时清理,会导致 ThreadLocalMap 中的 Entry 随着线程的复用而累积增多,最终导致内存泄漏。
​
    - 解决方案
​
        -   • 及时调用 remove 方法清除不再需要的 ThreadLocal 变量。
• 避免将 ThreadLocal 设置为静态变量,尽量控制其作用范围。
• 确保线程结束时进行资源释放操作,包括清理 ThreadLocal 变量。

在使用线程池时,对于需要使用 ThreadLocal 的任务,务必在任务执行完毕后将 ThreadLocal 变量手动清理。

J.U.C

  • CHM

    • 使用原因

      • 考虑到使用hashmap的时候是非线程安全的,使用hashtable时锁的控制力度太大,针对于每个方法级别进行加锁,效率太差 所以结合安全性与效率综合考虑----->CHM是不二之选

    • 具体使用

      • computeIfAbsent

      • computeIfPresent

      • compute(computeIfAbsent和computeIfPresent两者的结合)

        • 两者结合,可以同时兼顾key存在与不存在进行操作

      • merge(合并数据)

    • 安全性保障

      • 存储结构

        • JDK1.7--->采用分段锁 segment去实现,锁的粒度比较大 JDK1.8--->引入红黑树,基于数组+链表/红黑树实现存储 链表为了解决哈希冲突,红黑树解决链表过长时间复杂度增加,由O(n)--->O(logn) 链表转红黑树条件:node长度大于64,链表长度大于8

      • put流程

        • ①数组初始化过程,延迟初始化,在进行put操作的时候再初始化

        • ②计算数组下标位置之后,进行判断,如果该下标位置为空,则直接存储进去,存储过程需要通过CAS来保证原子性

        • ③当多个线程并发执行hash计算到同一个下标,且该下标位置已经存入元素,此时就会链式寻址法来解决hash冲突,在数组位置下链式存储元素,并在数组的头节点添加synchronized,去避免线程安全问题

        • ④锁住其他线程,有一个线程进入该下标下的链表之后,就会有两个逻辑:一是遍 历链表,看有没有相同的key,有的话就直接覆盖(更新元素);二是没有相同key ,就采用尾插法将这个元素put到链表的尾部

        • ⑤第三种情况,就是计算hash值得到的下标位置既不为空也不为链表,为红黑树的结构,在该数组下标有元素之后我们应该知晓已经加了锁来保证线程安全,然后进行判断数组结构为链表或者是红黑树,此时为红黑树:同样也会调用红黑树的插入逻辑,红黑树的首节点同样添加锁来保证一个线程安全

        • ⑥前面插入链表的时候有对链表长度进行记录的过程,此时会去判断这个链表的长度,如果大于8,则会去调用treeifyBin方法,判断到底是链表长度转化为红黑树还是扩容

    • 底层原理

      • 扩容具体流程

      • 数据迁移

  • Lock

    • 实现类

      • ReentrantLock

        • 重入的概念:一个线程在获取到重入锁之后,如果后续在释放锁之前,再去尝试获取这把锁,不需要去获得锁,而是记录一个重入的次数 基础使用:Lock.lock--->开启锁 Lock.unlock(一定在finally代码块中去释放锁)

    • 实现原理

      • 简述

        • 满足线程的互斥特性,同一个时刻只允许一个线程进入到加锁的代码中,保证多线程环境下,线程的顺序执行(排号系统)

      • 具体

        • ReentrantLock的实现原理可以从AQS出发进行分析。AQS是ReentrantLock的同步器,用于实现锁的底层机制。 state:state是ReentrantLock中用于记录锁状态的变量,在多线程并发情况下会被修改。线程抢占到锁之后,state会被修改为1,并且支持重入,即允许同一个线程多次加锁,此时state会增加重入次数。state的修改操作是通过CAS来保证原子性。 AQS队列:AQS使用双向链表的数据结构来存储按照先后顺序阻塞的线程。当一个线程无法获取锁时,会被放入AQS队列进行排队。被阻塞的线程通过自旋操作去尝试抢占锁,如果自旋不成功,线程就会被阻塞。 公平和非公平:在ReentrantLock中,可以选择使用公平锁和非公平锁。公平锁会按照线程请求锁的顺序进行获取,而非公平锁则允许已经持有锁的线程插队。这一特性并不体现在AQS队列中排队的线程,而是体现在从AQS队列中唤醒线程时的行为,即是否允许后来的线程插队。

    • 与synchronized的区别

      • 获取方式

        • synchronized是Java语言的关键字,通过在方法上加上synchronized关键字或在代码块中使用synchronized(this)来获取锁。 Lock接口的实现类,如ReentrantLock,是Java中的一个类,通过lock()方法获取锁,通过unlock()方法释放锁。需要手动释放锁,因此更加灵活。

    - 灵活性
​
        -   Lock接口相比synchronized提供了更多的灵活性和功能,如可重入锁、公平锁和非公平锁机制等,可以根据需求选择不同的实现方式。

synchronized是一种简单而且隐式的锁机制,功能相对受限,无法满足某些复杂的并发场景。

    - 性能
​
        -   在早期版本的Java中,synchronized性能较低,因为每次获取锁都会导致上下文切换。

从Java 6开始,JVM对synchronized进行了优化,引入了偏向锁、轻量级锁和重量级锁等概念,提高了synchronized的性能,但在某些高并发场景下,Lock可能比synchronized更高效。

    - 异常处理
​
        -   在使用synchronized时,如果线程在同步块中发生异常而未被捕获,锁会自动释放。

使用Lock时,需要手动编写finally块来确保锁的释放,以避免死锁等情况的发生。

    - 可中断性
​
        -   Lock接口提供了可中断的获取锁机制,即可以在等待锁时响应中断。

synchronized不支持中断操作,无法响应中断信号,可能会导致线程长时间阻塞。

  • 线程池

  • 其他工具

    • countdownLatch

    • Semaphore

    • CyclicBarrier

  • 24
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值