JMM内存模型8大原子操作:

read(读取):从主内存中读取数据;

load(载入):将主内存读取到的数据写入工作内存;

use(使用):从工作内存读取数据来计算;

assign(赋值):将计算好的值重新赋值到工作内存当中;

store(存储):将工作内存数据写入主内存;

write(写入):将存入的数据变量值赋值给主内存中的共享变量;

lock(锁定):将主内存变量加锁;

unlock(解锁):将主内存变量解锁。

JMM内存模型8大原子操作图示:

缓存一致协议(MESI): 多个CPU从主内存读取同一个数据到各自的高速缓存,当其中某个CPU修改了缓存里的数据,该数据马上同步会主内存,其他的CPU通过总线嗅探机制可以感知到数据的变化从而将自己缓存的数据失效。

缓存加锁: 缓存锁的核心机制是遵循与缓存一致性协议,一个处理器的缓存回写到内存会导致其他处理器的缓存失效,IA-32和Inter 64处理器使用MESI实现缓存一致性协议,Arm架构下是AMBA协议。

Volatile可见性底层实现原理:

Volatile缓存可见性实现原理: 底层实现主要通过一条汇编指令lock前缀指令,他会锁定这块内存区域的缓存(缓存行锁定)并写回到主内存中;

Inter架构软件开发者手册中对lock指令的解释: 会将当前处理器缓存行的数据立即写回到系统内存,这个写回内存操作会引起其他CPU缓存了该地址的数据无效(MESI),提供内存屏障功能,使lock指令不会进行重排。

指令重排序:

在计算机执行指令的顺序在经过程序编译器编译之后形成的指令序列。一般而言,这个指令序列是会输出确定的结果,以确保每一次的执行都有确定的结果,但是一般情况下,CPU和编译器为了提升程序执行的效率,会按照一定的规则允许进行指令优化,在某些情况下,这种优化会带来一些执行的逻辑问题,主要的原因是代码逻辑之间是存在一定的先后顺序 在并发执行情况下,会发生二义性,即按照不同的执行逻辑,会得到不同的结果信息。

Happens-Before:

程序次序规则:在一个线程内一段代码的执行结果是有序的。就是还会指令重排,但是随便它怎么排,结果是按照我们代码的顺序生成的,不会变;

管程锁定规则:就是无论是在单线程环境还是多线程环境,对于同一个锁来说,一个线程对这个锁解锁之后,另一个线程获取了这个锁都能看到前一个线程的操作结果!(管程是一种通用的同步原语,synchronized就是管程的实现);

volatile变量规则:就是如果一个线程先去写一个volatile变量,然后一个线程去读这个变量,那么这个写操作的结果一定对读的这个线程可见;

线程启动规则:在主线程A执行过程中,启动子线程B,那么线程A在启动子线程B之前对共享变量的修改结果对线程B可见;

线程终止规则:在主线程A执行过程中,子线程B终止,那么线程B在终止之前对共享变量的修改结果在线程A中可见。也称线程join()规则;

线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程代码检测到中断事件的发生,可以通过Thread.interrupted()检测到是否发生中断;

传递性规则:happens-before原则具有传递性,即hb(A, B) , hb(B, C),那么hb(A, C);

对象终结规则:一个对象的初始化的完成,也就是构造函数执行的结束一定 happens-before它的finalize()方法。

内存屏障:

Store:将处理器缓存的数据刷新到内存中;
        Load:将内存存储的数据拷贝到处理器的缓存中。

实际上就是如果CPU在指令优化是给与一个标记位置,碰到此位置不进行优化;

临界区与竞态条件:

临界区: 一个程序运行多个线程本身没有问题,出现问题最大的地方在于多个线程访问共享资源,多个线程读共享资源其实也没有问题,在多个线程对共享资源读写操作时发生指令交错,就会出现问题,一段代码块内如果存在对共享资源的多线程读写操作,称这段代码为临界区;

竞态条件:多个线程在临界区内执行,由于代码执行序列不同而导致结果无法预测,称之为竞态条件。

为了避免临界区的竞态条件发生,JAVA提供多种手段进行规避:

1.阻塞式的解决方案:synchronized,Lock;

2.非阻塞式的解决方案:原子变量。

线程分布的不同锁分类:

偏向锁: 只针对于一个线程,单个线程体系下加锁,本质就只有一把,直接应用markword解决识别问题;

轻量级锁:只针对于两个线程,利用栈区结构来存储线程ID不同;

重量级锁:两个以上线程,利用一个全新的结构来存储不同的ID。

Monitor对象与synchronized:

执行同步代码块内容,然后唤醒entryList中其他线程时,此处采取竞争策略,先到不一定先得,所以synchronize锁是非公平。非公平锁: 在锁可用的时候,一个新到来的线程要占有锁,可以不需要排队,直接获得。 公平锁: 在锁可用的时候,一个新到来的线程要占有锁,需要排队,等待执行 。

什么是CAS?

CAS是英文单词Compare And Swap的缩写,翻译过来就是比较并替换。

CAS机制当中使用了3个基本操作数:

内存地址V

旧的预期值A

要修改的新值B

更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。

线程的上下文切换:

本质:CPU切换前把当前任务的状态保存下来,以便下次切换回这个任务时可以再次加载这个任务的状态,然后加载下一任务的状态并执行。任务的状态保存及再加载, 这段过程就叫做上下文切换。

每个线程都有一个程序计数器(记录要执行的下一条指令),一组寄存器(保存当前线程的工作变量),堆栈(记录执行历史,其中每一帧保存了一个已经调用但未返回的过程)。

寄存器是CPU内部的数量较少但是速度很快的内存(与之对应的是 CPU 外部相对较慢的 RAM 主内存)。寄存器通过对常用值(通常是运算的中间值)的快速访问来提高计算机程序运行的速度。

程序计数器是一个专用的寄存器,用于表明指令序列中CPU正在执行的位置,存的值为正在执行的指令的位置或者下一个将要被执行的指令的位置。

上下文切换会导致额外的开销,常常表现为高并发执行时速度会慢串行,因此减少上下文切换次数便可以提高多线程程序的运行效率。

直接消耗:指的是CPU寄存器需要保存和加载,系统调度器的代码需要执行,TLB实例需要重新加载CPU的pipeline需要刷掉;

间接消耗:指的是多核的cache之间得共享数据,间接消耗对于程序的影响要看线程工作区操作数据的大小。

原子引用与ABA问题:

在多线程对于原子变量操作时,会发生将数据变更回去的现象,CAS在判断时会造成概念上的认知错误,但是实际上对业务结果是不变的。实际业务运用过程中可能会需要知道整个运行过程值是否改变,通过AtomicStampedReference追溯版本号,通过AtomicMarkableReference 得到是否更改结果。

享元设计模式:

本质:运用内存共享的原理,去有效支撑大量的细颗粒度的对象;

享元工厂:一个享元工厂,用来创建并管理对象,他主要是用来确保合理地共享对象,当用户请求一个对象时,由工厂提供一个已创建的对象实例或者创建一个;

享元对象:一个重复的对象;

使用场景:如果一个程序对于某个对象进行大量应用,且使用生命周期短,可以考虑采取享元模式进行复用。

创建多少线程合适?

CPU密集型运算:通常采用CPU核数 + 1 能够实现最优的CPU利用率,+1是保证当线程由于页缺失故障(操作系统)或其他原因导致暂停时,额外的这个线程就能顶上去,保证CPU始终周期不被浪费;

I/O密集型运算:CPU不总是处于繁忙状态,例如,当你执行业务计算时,这时候会使用CPU资源,但当你执行IO操作、或者远程的RPC调用时,包括进行数据库操作等,这个时候CPU会闲下来,你可以利用多线程提高他的利用率;

经验公式如下:线程数 = 核数 * 期望CPU利用率 * 总时间(CPU计算时间+等待时间) / CPU计算时间,例如:4核CPU,计算时间是50%,其他等待时间是50%,期望CPU被100%利用,套用公式4 * 100% * 100% / 50% = 8。

什么是AQS?

AQS全程AbstractQueuedSynchronizer,位于java.util.concurrent.locks包下,是JDK1.5提供的一套用于实现阻塞锁和一系列依赖FIFO等待队列的同步器(First Input First Output先进先出)的框架实现。是除了java自带的synchronized关键字之外的锁机制。

可以将AQS作为一个队列来理解。 我们常用的ReentrantLock、Semaphore、CountDownLatch、CyclicBarrier等并发类均是基于AQS来实现的。具体用法是通过继承AQS,并实现其模板方法,来达到同步状态的管理。

AQS的功能在使用中可以分为两种,独占锁和共享锁:

独占锁:每次只能有一个线程持有锁。eg: ReentrantLock就是独占锁;

共享锁:允许多个线程同时获得锁,并发访问共享资源。eg: ReentrantReadWriteLock中的读。

AQS核心思想:如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。

AQS使用方式:

AQS设计是基于模板方法模式的,一般的使用方式是:

1.使用者继承AbstractQueuedSynchronizer并重写指定的方法。(这些重写方法很简单,无非是对于共享资源state的获取和释放);

2.将AQS组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法。

AQS定义的这些可重写的方法:

1.protected boolean tryAcquire(int arg): 独占式获取同步状态,试着获取,成功返回true,反之为false;

2.protected boolean tryRelease(int arg) :独占式释放同步状态,等待中的其他线程此时将有机会获取到同步状态;

3.protected int tryAcquireShared(int arg) :共享式获取同步状态,返回值大于等于0,代表获取成功,反之获取失败;

4.protected boolean tryReleaseShared(int arg) :共享式释放同步状态,成功为true,失败为false;

5.protected boolean isHeldExclusively(): 是否在独占模式下被线程占用。

AQS的模板方法:

独占锁:

1.void acquire(int arg);// 独占式获取同步状态,如果获取失败则插入同步队列进行等待;

2.void acquireInterruptibly(int arg);// 与acquire方法相同,但在同步队列中进行等待的时候可以检测中断;

3.boolean tryAcquireNanos(int arg, long nanosTimeout);// 在acquireInterruptibly基础上增加了超时等待功能,在超时时间内没有获得同步状态返回false;

4.boolean release(int arg);// 释放同步状态,该方法会唤醒在同步队列中的下一个节点。

共享锁:

1.void acquireShared(int arg);// 共享式获取同步状态,与独占式的区别在于同一时刻有多个线程获取同步状态;

2.void acquireSharedInterruptibly(int arg);// 在acquireShared方法基础上增加了能响应中断的功能;

3.boolean tryAcquireSharedNanos(int arg, long nanosTimeout);// 在acquireSharedInterruptibly基础上增加了超时等待的功能;

4.boolean releaseShared(int arg);// 共享式释放同步状态。

自定义,使用总结:

首先,我们需要去继承AbstractQueuedSynchronizer这个类,然后我们根据我们的需求去重写相应的方法,比如要实现一个独占锁,那就去重写tryAcquire,tryRelease方法,要实现共享锁,就去重写tryAcquireShared,tryReleaseShared;

然后,在我们的组件中调用AQS中的模板方法就可以了,而这些模板方法是会调用到我们之前重写的那些方法的。也就是说,我们只需要很小的工作量就可以实现自己的同步组件,重写的那些方法,仅仅是一些简单的对于共享资源state的获取和释放操作,至于像是获取资源失败,线程需要阻塞之类的操作,自然是AQS帮我们完成了。

AQS源码分析:

AQS的基本实现:

AQS维护一个共享资源state,通过内置的FIFO来完成获取资源线程的排队工作。(这个内置的同步队列称为"CLH"队列)。该队列由一个一个的Node结点组成,每个Node结点维护一个prev引用和next引用,分别指向自己的前驱和后继结点。AQS维护两个指针,分别指向队列头部head和尾部tail。

当线程获取资源失败(比如tryAcquire时试图设置state状态失败),会被构造成一个结点加入CLH队列中,同时当前线程会被阻塞在队列中(通过LockSupport.park实现,其实是等待态)。当持有同步状态的线程释放同步状态时,会唤醒后继结点,然后此结点线程继续加入到对同步状态的争夺中。

ReentrantLock原理:

ReentrantLock加锁流程:

条件变量实现原理: 每个条件变量其实就对应着一个等待队列,其实现类是ConditionObject, await流程: 开始Thread-0持有锁,调用 await,进入ConditionObject的addConditionWaiter流程 创建新的Node状态为 -2(Node.CONDITION),关联 Thread-0,加入等待队列尾部。

同步方案对比:

wait/notify:依托于synchronized,基于VM底层对于阻塞的实现,使用waitSet作为等待机制,set结构的问题,要么是随机一个(set的提取算法),要么是全部提出来唤醒;

await/signal:依赖于ReentrantLock条件变量,已经用条件变量与AQS体系作为唤醒机制,本质上底层是park/unpark实现阻塞;

park/unpark:以thread为操作对象,操作更精准,可以准确地唤醒某一个线程(notify随机唤醒一个线程,notifyAll唤醒所有等待的线程),增加了灵活性;

其实park/unpark的设计原理核心是“许可”:park是等待一个许可,unpark是为某线程提供一个许可,但是这个“许可”是不能叠加的,“许可”是一次性的。 比如线程B连续调用了三次unpark函数,当线程A调用park函数就使用掉这个“许可”,如果线程A再次调用park,则进入等待状态。

ReentrantReadWriteLock读写锁:

读写锁指一个资源能够被多个读线程访问,或者被一个写线程访问,但是不能同时存在读写线程;

ReentrantReadWriteLock中有两个静态内部类:ReadLock读锁和WriteLock写锁,这两个锁实现了Lock接口ReentrantReadWriteLock支持可重入,同步功能依赖自定义同步器(AbstractQueuedSynchronizer)实现,读写状态就是其同步器的同步状态;

写锁的获取和释放:写锁WriteLock是支持重进入的排他锁。如果当前线程已经获取了写锁,则增加写状态。如果当前线程在获取读锁时,读锁已经被获取或者该线程不是已获取写锁的线程,则当前线程进入等待状态。读写锁确保写锁的操作对读锁可见。写锁释放每次减少写状态,当前写状态为0时表示写锁已背释放。

读锁的获取与释放:读锁ReadLock是支持重进入的共享锁(共享锁为shared节点,对于shared节点会进行一连串的唤醒,知道遇到一个读节点),它能够被多个线程同时获取,在没有其他写线程访问(写状态为0)时,读锁总是能够被成功地获取,而所做的也只是增加读状态(线程安全)。如果当前线程已经获取了读锁,则增加读状态。如果当前线程在获取读锁时,写锁已经被获取,则进入等待状态。

CyclicBarrier和CountDownLatch:

CountDownLatch:一个或者多个线程,等待其他多个线程完成某件事情之后才能执行;(具体业务场景:我们在玩LOL英雄联盟时会出现十个人不同加载状态,但是最后一个人由于各种原因始终加载不了100%,于是游戏系统自动等待所有玩家的状态都准备好,才展现游戏画面);         CyclicBarrier:多个线程互相等待直到到达同一个同步点,再继续一起执行,而且可以重用;         CountDownLatch是计数器,线程完成一个记录一个,只不过计数不是递增而是递减,而CyclicBarrier更像是一个阀门,需要所有线程都到达,阀门才能打开,然后继续执行。

Android JUC相关推荐

  1. android平板 useragent,移动端适配 user-Agent

    用navigator结合正则表达式来判断 打开网址的来源function getOrin() { if((navigator..match(/(phone|pad|pod|iPhone|iPod|io ...

  2. 收集几个移动平台浏览器的User-Agent

    转自:http://luckerme.com/archives/1011.html 之前介绍的 更简洁的方式修改Chrome的User Agent,轻松体验移动版网络这种简洁的方法好像只适用于Chro ...

  3. javascript判断浏览器和终端类型,js如何区分手机、电脑终端和浏览器

    判断浏览器类型 复制代码代码如下: if ( window.sidebar && "object" == typeof( window.sidebar ) & ...

  4. 手机浏览器类型ua php,通过userAgent判断手机浏览器类型(示例代码)

    我们可以通过userAgent来判断,比如检测某些关键字,例如:AppleWebKit*****Mobile或AppleWebKit,需要注意的是有些浏览器的userAgent中并不包含AppleWe ...

  5. 浏览器 user agent

    Android N1 Mozilla/5.0 (Linux; U; Android 2.3.7; en-us; Nexus One Build/FRF91) AppleWebKit/533.1 (KH ...

  6. php通过agent判断app,通过userAgent判断手机浏览器类型 – 好饱的博客 – 程序员博客...

    我们可以通过userAgent来判断,比如检测某些关键字,例如:AppleWebKit*****Mobile或AppleWebKit,需要注意的是有些浏览器的userAgent中并不包含AppleWe ...

  7. python爬虫之手机模拟

    一般情况下,网站是通过对http请求的header 进行识别来判断是访问的是pc还是手机,哪个版本的浏览器 所以,可以通过修改header的方法来模拟手机. 例如下面就是模仿了安卓4.3b版本的手机, ...

  8. Android工程师进阶第四课 jmm内存模型和juc多线程基础

    第07讲:Java 内存模型与线程 本课时我们将进入 Java 内存模型的学习. Java 内存模型一词翻译自 Java Memory Model,简称 JMM,它所描述的是多线程并发.CPU 缓存等 ...

  9. android都图片mat_普通Android码农,该如何逆袭月薪5W的移动端架构师?

    作为一名普通安卓码农,我相信大家都有一个成为移动端架构师的梦. 毕竟,安卓行业越来越内卷,这都是一个老生常谈的话题了.如今会写xml和Activity的程序员一抓一大把,如果你只是一名普通的安卓码农, ...

最新文章

  1. BSP 二叉树再次讨论
  2. 删除目录下的特定命名的图片,获取特定名称图片的路径
  3. logstash接收多台服务器日志_Logstash实践: 分布式系统的日志监控
  4. Facebook的bigpipe
  5. 【.NET Core项目实战-统一认证平台】第一章 功能及架构分析
  6. 自定义MongoDB的Spring Social Connect框架
  7. 用scanline取BMP上某点的颜色,代码如下,为什么可以编译,运行时却出错呢?...
  8. 基础知识—数据类型-数据的输出与输入
  9. freecplus框架-PostgreSQL数据库操作
  10. 计算机安全流量填充,计算机安全.doc
  11. (004)每日SQL学习:物化视图之二
  12. 对区块链技术的一些思考
  13. java公众号图片上传_调用微信公众号接口上传图片素材
  14. UE4 创建开始游戏界面UI
  15. 资源-Windows10-2020原版镜像下载地址(20H2)以及1809、1803、1709
  16. (一)TileMap使用
  17. 物理每日一题(hyq的1、2)
  18. 【P-00】anaconda 安装总结
  19. 【解决阿里云服务器提示挖矿程序风险2022】
  20. 安卓实现循环定时响铃

热门文章

  1. 情侣旅游时遭偷拍40分钟,被4万人围观!如何防范?
  2. java协议手机qq批量登录软件_手把手分享:日常大量账号批量登录,批量点击软件...
  3. Kronos银行木马的前世今生
  4. 如何快速被腾讯录取的?看这篇
  5. c语言codeblock简单日历,vue实现简单日历
  6. 工业用微型计算机02241答案,2001年10月工业用微型计算机真题及答案
  7. 《通信技术导论(原书第5版)》——1.5 通过多路复用增加网络容量
  8. Apache Geronimo读音及含义
  9. Excel中利用“填充”快速生成任意起止、间隔的长序列序号
  10. 如何学好OBJECTIVEC