zoukankan      html  css  js  c++  java
  • 《Jave并发编程的艺术》学习笔记(1-2章)

    Jave并发的艺术


    并发编程的挑战

    上下文切换

    CPU通过时间片分配算法来循环执行任务,当前时间片执行完之后会切换到下一个任务。但是,切换会保存上一个任务的状态,一遍下次切换回这个任务时,可以再次加载这个状态。所以任务从保存到再加载的过程就是一次上下文切换。

    如何减少上下文切换

    减少上下文切换的方法有无锁并发编程、CAS算法、使用最少线程和使用协程

    • 无锁并发编程:多线程竞争锁时,会引起上下文切换,所以多线程处理数据时,可以用一些办法避免使用锁,如将数据ID按照Hash算法取模分段,不同的线程处理不同段的数据
    • CAS算法:Java的Atomic包使用CAS算法来更新数据,而不需要加锁
    • 使用最少线程:避免创建不需要的线程,比如任务很少,但是创建了很多线程来处理,这样会造成大量线程都处于等待状态
    • 协程:在单线程实现多任务的调度,并在单线程里维持多个任务间的切换

    死锁

    死锁常见的原因是,线程之间相互等待。避免死锁的几个常见方法:

    • 避免一个线程获取多个锁
    • 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源
    • 尝试使用定时锁,使用lock.tryLock(timeout)来代替使用内部锁机制
    • 对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况

    底层实现原理

    volatile

    volatile的定义与实现原理

    Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获取这个变量。所以java提供了volatile,在某些情况下面比锁更加的方便。如果一个字段被声明为volatile,Java线程内存模型确保所有线程看到这个变量的值是一致的。

    volatile如何保证可见性:通过Lock前缀指令,对volatile进行写时,JVM会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存

    • Lock前缀指令会引起处理器缓存行写回到内存
    • 一个处理器的缓存回写会导致其他处理器的缓存无效

    synchronized

    synchronized的介绍

    并发编程里面元老级别的存在,重量级的锁。Java里面每一个对象都可以作为锁

    • 对于普通的方法,锁是当前实例的对象
    • 对于静态同步方法,锁是当前类的Class对象
    • 对于同步方法块,锁是Synchronized括号里配置的对象

    Java对象头

    synchronized用的锁是存在Java对象头里面的。对象头的内容

    • Mark Word:存储对象的HashCode或锁信息
    • Class Metadata Address:储存到对象类型数据的指针
    • Array length:数组的长度

    Mark Word里默认存储对象的HashCode、分代年龄和锁标记,Mark Word里储存的数据会随着锁标志位的变化而变化。以下是四种状态的变化

    • 轻量级锁:指向栈中锁记录的指针,锁标志位 00
    • 重量级锁:指向互斥量(重量级锁)的指针 ,锁标志位 10
    • GC标记:空,锁标志位 11
    • 偏向锁:线程ID,Epoch,对象分代年龄,是否是偏向锁,锁标志位 01

    锁的升级与对比

    锁一共有四种状态,级别从低到高依次是:无锁状态、偏向锁状态,轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。注意:锁可以升级但是不能够降级。一些的设计目的只有一个。为了提高获得锁和释放锁的效率

    • 偏向锁:大多数情况下,锁不仅不存在多线程竞争,而且总是由他同一个线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。如:当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程再次进入和退出同步块时不需要进行CAS操作来加锁和解锁,仅仅判断一下对象头里面的Mark Word里面是否存储着指向当前线程的偏向锁。如果有,表示当前线程已经获得了锁;如果没有,则使用CAS竞争锁,再尝试使用CAS将对象头的偏向锁指向当前线程
    • 轻量级锁:线程在执行同步块之前,JVM会先在当前线程的栈帧中创建用于储存记录的空间,并将对象头中的Mark Word复制到锁记录中,官方称为Displaced Mark Word。然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程就会获得锁,如果失败,表示其他线程竞争锁,当前线程便可以尝试通过自旋来获取锁,自旋失败之后,锁就会膨胀成重量级锁
    • 重量级锁:因为自旋会消耗CPU,为了避免无用的自旋(如:获得锁的线程被阻塞住了),一旦锁升级为重量级锁就不会恢复到轻量级锁状态。当锁出于这个状态下,其他线程试图获取锁时,都会被阻塞住,当持有锁的线程释放锁之后会唤醒这些线程,被唤醒的线程就会进入新一轮的夺锁之争

    原子操作的实现原理

    原子(atomic),不能被进一步分割的最小粒子,原子操作:不能被中断的一个或一系列的操作。

    处理器如何实现原子操作

    • 第一个机制,通过总线锁来保证原子性
    • 第二个机制,通过缓存锁定来保证原子性

    Java如何实现原子操作

    可以通过锁,和循环CAS的方式实现原子操作

    CAS

    CAS(Compare And Swap):比较并且交换。一个旧值,一个新值,操作之前比较旧值有没有发生变化,如果没有发生变化,才换成新值,发生了变化则不交换

    CAS存在的三大问题:

    • ABA问题:CAS操作需要在操作的值的时候,检查值有没有发生变化。但是如果一个值原来是A,变成了B,又变成了A,使用CAS进行检查时会发现他的值没有发生变化,但是实际上他发生了变化。解决方案:给变量加上版本号
    • 循环时间长开销大:自旋CAS如果不成功,会给CPU带来非常大的开销
    • 只能保证一个共享变量的原子操作:JDK提供了AtomicReference类来保证引用对象之间的原子性,就可以把多个变量放在一个对象里来进行CAS操作
  • 相关阅读:
    团队项目-第一阶段冲刺7
    团队项目-第一阶段冲刺6
    Spring Boot 揭秘与实战(七) 实用技术篇
    Spring Boot 揭秘与实战(七) 实用技术篇
    Spring Boot 揭秘与实战(六) 消息队列篇
    Spring Boot 揭秘与实战(五) 服务器篇
    Spring Boot 揭秘与实战(五) 服务器篇
    Spring Boot 揭秘与实战(五) 服务器篇
    Spring Boot 揭秘与实战(五) 服务器篇
    Spring Boot 揭秘与实战(四) 配置文件篇
  • 原文地址:https://www.cnblogs.com/horken/p/10706112.html
Copyright © 2011-2022 走看看