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操作
  • 相关阅读:
    自定义view代码
    省份封装代码
    监听锁屏广播,开启1个像素的Activity
    双进程守护
    新增,查询,删除,修改下方即可
    #Android笔记#解决textview使用SpannableString实现图文混排并设置了行高时,图片与文字显示混乱
    显示地图
    局部页面传值Model
    JS的跳转
    helper实现隐藏前台特效
  • 原文地址:https://www.cnblogs.com/horken/p/10706112.html
Copyright © 2011-2022 走看看