【重难点】【JUC 01】线程安全都体现在哪些方面 、如何维护线程安全、多线程的同步方法、多线程通信方式、AQS

文章目录

  • 【重难点】【JUC 01】线程安全都体现在哪些方面 、如何维护线程安全、多线程的同步方法、多线程通信方式、AQS
  • 一、线程安全都体现在哪些方面
    • 1.原子性
    • 2.可见性
    • 3.有序性
  • 二、如何维护线程安全
  • 三、多线程的同步方法
    • 1.synchronized 修饰方法
    • 2.同步代码块
    • 3.重入锁
    • 4.volatile
    • 5.使用 ThreadLocal 管理变量
    • 6.使用原子变量
    • 7.使用阻塞队列
  • 四、多线程通信方式
  • 五、AQS
    • 1.概述
    • 2.同步器
    • 3.抽象
    • 4.队列
    • 5.总结

一、线程安全都体现在哪些方面

主要体现在原子性、可见性和有序性三个方面

1.原子性

原子性是一组操作要么完全发生,要么都没有发生,其余线程不会看到中间过程。即使在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程所干扰

例:银行转账过程中,A 账户减少 100 元,B 账户多了 100 元,这两个动作是原子操作,我们不会看到 A 减少了 100 元,B 余额保持不变的中间过程

实现方式:

  1. 利用锁的排他性,保证同一时刻只有一个线程在操作一个共享变量
  2. 利用 CAS 保证
  3. Java 语言规范中,保证了除 long 和 double 以外的任何变量的写操作都是原子操作

注意:

  1. 原子性针对的是多个线程的共享变量,所以对于局部变量来说,不存在共享问题,也就无所谓是否是原子操作
  2. 单线程环境下讨论是否是原子操作没有意义
  3. volatile 仅仅能保证变量写操作的原子性,不保证符合操作

2.可见性

可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其它线程能够立即看到修改的值

实现方式:

  1. synchronized
  2. volatile
  3. final

3.有序性

有序性表现在以下两种场景:

  • 线程内
    从某个线程的角度来看方法的执行,指令会按照一种叫 “串行(as-if-serial)” 的方式执行,此种方式已经应用于顺序编程语言
  • 线程间
    这个线程 “观察” 到其它线程并发地执行非同步的代码时,由于指令重排序优化,任何代码都有可能交叉执行。唯一其作用的约束是:对于同步方法、同步块以及 volatile 修饰的字段的操作仍维持相对有序

二、如何维护线程安全

  1. synchronized
  2. ReentrantLock
  3. ReentrantReadWriteLock

三、多线程的同步方法

1.synchronized 修饰方法

public synchronized void method(){}

由于 Java 的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前需要先获得内置锁,否则就处于阻塞状态

synchronized 如果修饰静态方法,该方法被调用时将会锁住整个类

2.同步代码块

 synchronized(this){}

被 synchnornized 修饰的代码块将会自动被加上内置锁,从而实现同步

同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用同步代码块同步关键代码即可

3.重入锁

ReentrantLock 类是可重入、互斥、实现了 Lock 接口的锁,它与使用 synchronized 修饰的方法和代码块具有相同的基本行为和语义,并且扩展了其能力

ReentrantLock 类的常用方法:

  • lock() 获得锁
  • unlock() 释放锁

4.volatile

volatile 关键字为变量和的访问提供了一种免锁机制,使用 volatile 修饰相当于告诉 JVM 该变量可能会被其它线程更新,因此每次使用改变量时就要重新计算,而不是使用寄存器中的值

注意

  1. volatile 不能修饰 final 类型的变量
  2. volatile 可以保证可见性和有序性,但不能保证原子性

5.使用 ThreadLocal 管理变量

使用 ThreadLocal 管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意
修改自己的变量副本,而不会对其它线程产生影响

ThreadLocal 类的常用方法:

  • get() 返回此线程局部变量的当前线程副本中的值
  • initialValue() 返回此线程局部变量的当前线程的 “初始值”
  • set(value) 将此线程局部变量的当前线程副本中的值设置为 value

ThreadLocal 的使用场景

ThreadLocal 通常用在防止全局变量的共享,或者单例实例的共享。举个例子,连接数据库的时候,首先要创建一个 connection 对象,但是这个 connection 对象不一定是线程安全的,如果所有线程方法都使用这个对象进行连接,就有可能出现问题。如果使用加锁进行同步,那么性能上会有问题,这个时候就可以使用 ThreadLocal,让每个线程都持有一份 connection 对象

6.使用原子变量

需要线程同步的根本原因在于对普通变量的操作不是原子的,在 java.util.concurrent.atomic 包中提供了创建原子类型变量的工具类,使用该类可以简化线程同步

7.使用阻塞队列

阻塞队列与我们平常接触的普通队列(LinkedList 或 ArrayList 等)的最大不同点,在于阻塞队列的阻塞添加和阻塞删除方法

  • 阻塞添加:当阻塞队列元素已满时,队列会阻塞加入元素的线程,直至队列不满时才重新唤醒线程执行元素加入操作
  • 阻塞删除:在队列元素为空时,删除队列元素的线程将被阻塞,直至队列不为空再执行删除操作

四、多线程通信方式

线程间通信的模型有两种:共享内存和消息传递,以下方式都是基于这两种模型来实现的

  • 使用 Object 类提供的方法:wait()、notify()、notifyAll(),需要配合 synchronized 使用,wait 方法释放锁,notify 方法不释放锁
  • 使用 Reentrant Lock 结合 Condition,跟 Object 的 wait() 和 notify() 很像
  • 使用 volatile,基于 volatile 关键字来实现线程间相互通信是基于共享内存的思想,即多个线程同时监听一个变量,当这个变量发生变化的时候,线程能够感知并执行相应的业务
  • 使用 CountDownLatch,CountDownLatch 是基于 AQS 框架,相当于维护了一个线程间共享的变量 state(0 表示未锁定,1 表示锁定)
  • 使用 LockSupport,LockSupport 是一种非常灵活的实现线程间阻塞和唤醒的工具

五、AQS

1.概述

AQS(AbstractQueueSynchronizer),抽象的队列式的同步器,它提供了一个基于 FIFO 队列,可以用于构建锁或者其他相关同步器的基础框架,许多同步类的实现都依赖于它,比如 ReentrantLock、Semaphore、CountDownLatch

我们来看一下 java.util.concurrent.locks 的结构

上图中,Lock 的实现类其实都是构建在 AQS 上的,但为何没用 UML 线表示它们之间的关系呢?这是因为每个 Lock 实现类都持有自己内部类 Sync 的实例,而这个 Sync 才是继承自 AQS 的。那为何要实现不同的 Sync 呢?这是因为每种 Lock 的用途不同

但是我们还是不知道什么是 AQS,什么叫抽象的队列式的同步器?那我们就依次讲讲什么是同步器、为什么称之为抽象的、为什么称之为队列式的

2.同步器

多线程环境下,线程之间可以通过某种状态来同步,比如只有当状态满足某种条件,才能触发线程执行某种操作,能实现这个特性的可以称之为同步器

AQS 里有一个最关键的属性 private volatile int state,名称为 state,翻译为状态,类型是 int,可以理解为资源数量

它提供了 getState 和 setState 方法,还有一个线程安全的 compareAndSetState 方法。重点是 compareAndSetState 方法,利用了 Unsafe 进行 CAS 操作,如果当前状态值等于预期值,则以原子方式将同步状态设置为给定的更新值

正是因为可以做到在并发场景下对 state 的修改是原子性的,并且可以获取修改结果,所以基于这个特性可以构建同步器

3.抽象

抽象说明 AQS 可以被子类继承并且重写其中的一些方法,官方对此做了一些说明:子类必须定义那些会更改 state 的 protected 方法,以及定义何种状态对于此对象意味着被获取或被释放

为次,这个类提供了以下方法:

  • tryAcquire(int):试图在独占模式下获取对象的状态
  • tryRelaease(int):试图设置状态来反映独占模式下的释放
  • tryAcquireShared(int):试图在共享模式下获取对象状态
  • tryReleaseShared(int):试图设置状态来反映共享模式下的释放
  • isHeldExclusively():如果对于当前线程,同步是以独占方式进行的,则返回 true

如果要实现 AQS,就要重写这些方法,定义那种状态对于此对象意味着被获取或被释放

比如,如果实现一个锁,则 state 可以定义为两个值:0 表示未锁定状态,1 表示锁定状态。如果 state 用作信号量,state 可以表示剩余量,每获取一个资源剩余量就减 1

4.队列

AQS 将阻塞线程封装到一个内部类 Node 里,并维护一个 CHL Node FIFO 队列。CHL 队列是一个非阻塞的 FIFO 队列,也就是说往里面插入或移除一个结点的时候,在并发条件下不会阻塞,而是通过自旋锁和 CAS 保证节点插入和移除的原子性,实现无锁且快速地插入

5.总结

AQS 实现了 3 点基本功能:

  1. 同步器基本范式、结构
  2. 线程的阻塞、唤醒机制
  3. 线程阻塞队列的维护

【重难点】【JUC 01】线程安全都体现在哪些方面 、如何维护线程安全、多线程的同步方法、多线程通信方式、AQS相关推荐

  1. 【重难点】【JUC 05】线程池核心设计与实现、线程池使用了什么设计模式、要你设计的话,如何实现一个线程池

    [重难点][JUC 05]线程池核心设计与实现.线程池使用了什么设计模式.要你设计的话,如何实现一个线程池 文章目录 [重难点][JUC 05]线程池核心设计与实现.线程池使用了什么设计模式.要你设计 ...

  2. 【重难点】【JUC 03】怎么实现一个线程安全的队列、手写模拟实现一个阻塞队列

    [重难点][JUC 03]怎么实现一个线程安全的队列.手写模拟实现一个阻塞队列 文章目录 [重难点][JUC 03]怎么实现一个线程安全的队列.手写模拟实现一个阻塞队列 一.怎么实现一个线程安全的队列 ...

  3. 【重难点】【Redis 01】为什么使用 Redis、Redis 的线程模型、Redis 的数据类型及其底层数据结构

    [重难点][Redis 01]为什么使用 Redis.Redis 的线程模型.Redis 的数据类型及其底层数据结构 文章目录 [重难点][Redis 01]为什么使用 Redis.Redis 的线程 ...

  4. 【重难点】【分布式 01】RESTful、RPC 对比、Dubbo、Spring Cloud 对比、Eureka、Zookeeper、Consul、Nacos 对比、分布式锁

    [重难点][分布式 01]RESTful.RPC 对比.Dubbo.Spring Cloud 对比.Eureka.Zookeeper.Consul.Nacos 对比.分布式锁 文章目录 [重难点][分 ...

  5. 【重难点】【RabbitMQ 01】消息队列的作用、主流的消息队列、RabbitMQ 基于什么传输消息、RabbitMQ 模型架构、死信队列和延迟队列

    [重难点][RabbitMQ 01]消息队列的作用.主流的消息队列.RabbitMQ 基于什么传输消息.RabbitMQ 模型架构.死信队列和延迟队列 文章目录 [重难点][RabbitMQ 01]消 ...

  6. 【重难点】【JUC 04】synchronized 原理、ReentrantLock 原理、synchronized 和 Lock 的对比、CAS 无锁原理

    [重难点][JUC 04]synchronized 原理.ReentrantLock 原理.synchronized 和 Lock 的对比.CAS 无锁原理 文章目录 [重难点][JUC 04]syn ...

  7. 【重难点】【JUC 02】volitale 常用模式 、JUC 下有哪些内容 、并发工具类

    [重难点][JUC 02]volitale 常用模式 .JUC 下有哪些内容 .并发工具类 文章目录 [重难点][JUC 02]volitale 常用模式 .JUC 下有哪些内容 .并发工具类 一.v ...

  8. 【重难点】【JVM 01】OOM 出现的原因、方法区、类加载机制、JVM 中的对象

    [重难点][JVM 01]OOM 出现的原因.方法区.类加载机制.JVM 中的对象 文章目录 [重难点][JVM 01]OOM 出现的原因.方法区.类加载机制.JVM 中的对象 一.OOM 出现的原因 ...

  9. 【重难点】【Java集合 01】HashMap 和 ConcurrentHashMap

    [重难点][Java集合 01]HashMap 文章目录 [重难点][Java集合 01]HashMap 一.HashMap 1.概述 2.JDK 1.8 中的变化 3.链表转换为红黑树 4.扩容问题 ...

最新文章

  1. 干货 | 成为一名推荐系统工程师永远都不晚
  2. Winform开发框架之通用人员信息管理实现代码介绍
  3. 2020年国家电网计算机类考纲,终于发布!详解2020届国家电网考试大纲,带你读懂考纲变化!...
  4. 在android添加数据采集,一种基于Android系统的地理信息数据采集方法与流程
  5. stl max函数_std :: max_element()函数以及C ++ STL中的示例
  6. react - next.js 引用本地图片和css文件
  7. 冒泡排序及其稳定性介绍
  8. shopnc B2B2C商城 Nginx下开启伪静态
  9. 面试评估表和评估指标雷达图(附模板下载)
  10. Android7.0的xposed框架,Android 7.x 安装Xposed框架
  11. linux下安装交叉编译器
  12. 二维码图片处理换logo,加文字
  13. 手机端解决2倍图3倍图自适应
  14. 计算机网络实验(三个部分--验证性、Wireshark、CPT)
  15. Java语言中抽取word、pdf的四种方法
  16. 浅析简历——中华英才网
  17. 【Office】excel当前日期,下月日期
  18. 告别极寒,科学家突破将量子计算机运行最低温提了15倍
  19. php 画low poly,五分钟教你明白高大上LOW-POLY风格图片生成术
  20. 康熙一朝无贪官,雍正一朝无清官 什么意思

热门文章

  1. java输入年月输出日历_12月营销热点日历 | 2020年创意冲刺倒计时!
  2. android 4.4 keyfactory.getinstance 报错_Android实际开发bug大总结
  3. 创建目录_聊聊Word创建目录那些事儿
  4. python两个基本的库管理工具_Python多版本共存管理工具之pyenv
  5. Java复习-对象的回收与垃圾的回收
  6. Windows文本文件编码
  7. 使用LogParser分析IIS网站日志
  8. org.hibernate.HibernateException: 'hibernate.dialect' must be set when no Connection avalable
  9. java 蓝桥杯算法训练 快速排序
  10. java代码隐藏面消除算法,java常面的几种排序算法