• Java并发II


    Java并发

    J.U.C图

    一.线程的安全性

    当多个线程访问某个类的时候,不管运行环境采用何种方式调度或者这些线程如何交替执行,并且在主调代码中不需要任何额外的同步或者协同,这个类都可以表现出正确的行为,那么这个类就是线程安全的

    无状态和竞态条件

    无状态:不包含任何域,也不包含任何对其他对象中域的引用

    竞态条件(Race Condition):并发编程中,不恰当的执行时序而出现不正确的结果

    保证线程安全

    • 复合操作原子性, "先检查,后执行"的延迟初始化;"读取-修改-写入"操作必须是原子的

    • 加锁机制

    内置锁,重入

    内置锁:synchronized同步代码块,每个java对象都可以用作一个作为同步的锁

    内置锁 (Intrinsic Lock) 也叫监视器锁(Monitor Lock)

    重入: 内置锁可重入,当某个线程试图获得一个已经由它自己持有的锁,请求会成功

    重入多用于在子类继承线程同步的情况,子类改写父类的synchronized方法,在子类中的super方法调用父类的方法。如果锁不能重入的话,子类已经拥有锁,无法访问父类方法,并陷入死锁状态

    死锁

    死锁现象的描述:

    线程A拥有L锁,并想获得R锁的同时,线程B拥有R锁,并想获得L锁,这样的情况称为死锁

    死锁的条件:

    • 1,互斥条件,任务使用的资源中至少有一个是不能共享的
    • 2.至少有一个任务必须持有一个资源,且等待的资源是另一个任务所持有的资源
    • 3.资源不能被任务抢占,任务把资源当做普通事件
    • 4.必须有循环等待

    JMM

    jmm的原子操作

    内存间的交互操作

    read: 将一个变量从主内存传输到工作内存
    load: 将read的值放入工作内存的变量副本
    use: 将工作内存的一个变量值传递给执行引擎

    assign:把一个执行引擎接受到的值赋值工作内存的变量
    store: 把工作内存变量值传递到主内存
    read: 把store的得到的值放到主内存变量中

    lock: 作用于主内存变量
    unlock:

    二.对象的共享

    内存模型三大特性

    可见性

    原子性

    有序性

    可见性

    可见性描述:
    一个线程修改了共享变量的值,其他线程能立即得知这个修改;
    从java内存模型来说,变量修改后将新值同步会主内存,并在变量读取之前从主内存刷新变量值

    实现可见性

    1.Volatile变量

    主要作用:
    确保将变量更新操作通知到其他线程

    java默认提供的弱同步机制,但是没有提供锁机制

    写入volatile变量相当于退出同步代码块
    读取volatile变量相当于进入同步代码块

    加锁机制保证(可见性,原子性)
    volatile (可见性)

    语义:

    • java存储模型,不会对volatile执行进行重排序,保证volatile变量操作按照指令的顺序出现
    • volatile 并不保证线程安全,volatile字段不是原子性,想保证线程安全只有加锁
    • volatile 确保可见性 increment的自增操作

    原子操作

    锁机制问题

    多线程竞争条件下,加锁、释放锁都会导致较多的上下文切换,调度延时,性能问题

    一个线程持有锁,会导致其他线程挂起

    独占锁

    独占锁使一种悲观锁,synchronized 就是独占锁

    乐观锁

    乐观锁, 每次不加锁,假设没有冲突而去完成某项操作,因为冲突失败就重试,直到成功

    乐观锁机制基于 CAS(compare and swap) 来实现

    整个J.U.C 都是建立在CAS之上

    不变性

    不可变对象Immutable一定是线程安全的

    • final
    • String,枚举类型
    • 集合,可以使用Collections.unmodifiableMap() 函数进行实现

    显示锁

    为什么创建一个和内置锁机制非常相似的新的加锁机制?

    大多数情况下,内置锁可以很好的完成任务

    内置锁在功能上存在一些局限

    • 无法中断一个正在等待获取锁的线程
    • 无法在请求获取一个锁时无限的等待下去
    • 无法实现非阻塞结构的加锁机制

    AQS

    AbstractQueuedSynchronizer

    抽象队列同步器,基本思想是一个同步器

    获取锁:判断当前状态是否允许获取锁,是- 获取锁;否- 对于独占锁(阻塞),失败(共享锁),阻塞队列(阻塞线程)

    释放锁:修改状态位,如果有线程因为状态位阻塞的话,就唤醒队列中一个或者更多的线程

    ReentrantLock

    获取锁

    • 该锁没有被其他线程保持,获取锁并立即返回,将锁的保持计数设置为1
    • 当前线程已经保持该锁,保持计数+1,该方法立即返回
    • 该锁被另一个线程保持,禁用当前线程,获得锁之前,一直处于休眠状态

    ReentrantLock是可重入锁

    公平锁: 获取一个锁是按照请求顺序得到的

    Condition

    条件变量用来解决 Object wait()/notify()/notifyAll()的难以使用

    释放锁

    await(),挂起线程,一旦条件满足被唤醒,再次获取锁

    Latch 闭锁

    CountDownLatch

    闭锁的一种实现,这个闭锁的状态是一次性的

    CountDown() 实现自减

    CyclicBarrier

    循环屏障,计数器可以使用reset()来重置

    await() 来完成自减

    Seamphore

    信号量是一个计数信号量

    计数器不为0的时候,对线程放行
    计数器位0的时候,请求资源的新线程都会被阻塞,包括增加到请求许可的线程,seamphore是不可重入的

    请求一个许可,计数器减1
    释放一个请求,计数器加1

    是实现线程池,请求池的完美结构

    ReentrantReadWriteLock

    ReentrantLock实现标准的互斥,一次只有一个线程持有锁(独占锁)

    ReentrantReadWriteLock

    • 读取锁上,操作使用共享的获取、释放方法
    • 写入锁上,操作使用独占的获取、释放方法

    ReadWriteLock 是一个interface接口

    多线程开发的良好实践

    • 1.线程名称的命名有意义和区分度
    • 2.缩小同步范围,减少锁争用(能用同步块就不用同步方法)
    • 3.多用同步工具(CountDownLatch,CycliBarrier,Semaphore,Exchanger);少用wait(),notify(),很难实现复杂控制流
    • 4.使用BlockingQueue,用来实现生产者、消费者问题
    • 5.多用并发集合(ConcurrentHashMap),少用同步集合(HashTable)
    • 6.使用本地变量和不可变量来实现线程安全
    • 7.使用线程池而不是直接创建线程,线程的创建代价很高

    Java并发必看

  • 相关阅读:
    每日英语:Surviving a Conference Call
    每日英语:Boost Your Balance; Avoid Falls
    每日英语:Proactive Advice for Dealing With Grief: Seek Out New Experiences
    每日英语:Tencent Fights for China's Online Shoppers
    每日英语:5 Things to Know About Missing Malaysia Airlines Flight and Air Safety
    每日英语:Does China Face a Reading Crisis?
    每日英语:Six Ways to Modernize Your Car
    每日英语:Welcome to the Global Middle-Class Surge
    阿里巴巴 Sentinel + InfluxDB + Chronograf 实现监控大屏
    SpringBoot 2.0 + InfluxDB+ Sentinel 实时监控数据存储
  • 原文地址:https://www.cnblogs.com/GeekDanny/p/11851240.html
Copyright © 2020-2023  润新知