其他分享
首页 > 其他分享> > 多线程

多线程

作者:互联网

并发与并行

锁机制

在JDK6以前,synchronized一直被称为重量级锁,monitor依赖于底层操作系统的lock实现,java的线程是映射到操作系统的原生线程上的

轻量级锁:

在即将开始执行同步代码块中的内容时候,会首先检查对象的mark word,查看锁对象是否被其它线程占用,如果没有任何线程占用,那么会在当前的线程中所处的栈帧中建立一个名为锁记录(lock record)的空间,用于复制并存储对象目前的mark word信息。接着虚拟机使用CAS操作将对象的mark word更新为轻量级锁状态(数据结构变为指向lock record的指针,指向当前的栈帧),CAS操作基于cmpxchg指令,如果CAS操作失败的话,那么说明有线程已经进入了这个同步代码块中,这时候虚拟机会再检查对象的mark word 是否指向当前线程的栈帧,如果是说明不是其他线程,而是当前线程已经有了这个对象的锁,如果不是,说明已经被其它线程占用,只能将锁膨胀为重量级锁,按照重量级锁操作执行。

重量级锁:

在java虚拟机中,monitor是由Objectmonitor实现的,每个等待锁的线程都会被封装成ObjectWaiter对象 ,

自旋锁

入自旋锁之后,不会将处于等待状态的线程挂起,而是通过无线循环的方式,不断检测是否能够获取锁,由于单个线程占用锁的时间非常短,所以说循环的次数不会太多。如果等待时间太长,也只会浪费处理器资源,因此自旋锁等待时间是有限制的,如果失败就会采用重量级锁机制。JDK1.6之后,自旋锁得到了优化,次数限制不在是固定的,而是自适用变化的,如果某个锁经常都自自旋失败,有可能不在采用自旋策略,直接使用重量级锁。
解锁过程同样采用CAS算法,如果对象的mark word仍然指向线程的锁记录,那么就用CAS操作把对象的mark word 和复制到栈帧中的displaced mark word 进行交换,如果替换失败,说明其它线程尝试获取该锁,在释放锁的同时,需要唤醒被挂起的线程。

偏向锁

偏向锁实际上专门为单个线程而生的,当某个线程第一次获得锁的时候,如果接下来都没有其它线程来获取此锁,那么持有锁的线程不在需要同步操作,偏向锁也会通过CAS操作记录线程id,值得注意的是如果对象通过调用hashcode方法计算过对象的一致性hash值,那么它不支持偏向锁,会直接进入到轻量级锁状态,因为hash需要被保存,而偏向锁的mark word 无法保存hash值,如果对象已经是偏向锁状态再调用hashcode方法,直接回将锁升级为重量级锁。

JMM内存模型

在CPU中,一般会有高速缓存,为了解决内存的速度和处理器的速度,在CPU内部添加一级或者多级高速缓存来提高处理器的数据获取效率,但是现在都是基于多核处理器,每个处理器都有自己的高速缓存,如何保证每个处理器的高速缓存内容一致? 为了解决缓存一致性的问题,需要各个处理器访问缓存时遵循一些协议,在读写时候根据协议来进行操作,java也采用了类似的模型来实现支持多线程的内存模型。 JMM内存模型规定如下:

重排序

在编译或者执行时,为了优化程序的执行效率,编译器或者处理器统称会对指令进行重排序,有以下情况:

volatile 关键字

当写一个volatile变量时候,JMM会把该线程本地内存中的变量强制刷新到主内存中去,并且这个写操作会导致其他线程中的volatile变量缓存无效,这样另一个线程改变了这个值时,当前线程会立即得知,并将工作内存中变量更新为最新的版本。
volatile会禁止指令重拍,也就是说,如果我们操作的是一个volatile变量,将不会出现重排序,在编译时,会在指令序列中插入内存屏障禁止特定类型的处理器重排序。如果在指令间插入一条memory barrier 则会告诉编译器和CPU,不管社什么指令都不能和memory barrier指令重排。所以volatile能够保证之前的指令一定全部执行,之后的指令一定都没有执行,并且前面的语句的结果对后面的语句可见
内存屏障:又称内存栅栏,是一个CPU指令,有两个作用:保证特定的操作顺序;保证某些变量的内存可见性。

happens-before 原则

JMM 提出先行发生原则,禁止编译优化的场景,来向各位程序员做一些保证,

标签:变量,对象,mark,线程,内存,多线程,monitor
来源: https://www.cnblogs.com/henryzhu19/p/16079479.html