1.线程安全的定义:当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者再调用放进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那么这个对象就是线程安全的。
2.java语言中各种操作共享的数据类型:不可变、绝对线程安全、相对线程安全、线程兼容和线程对立
1)不可变:final关键字修饰的变量
2)绝对的线程安全:【不管运行时环境如何】,调用者都不需要任何额外的同步措施
3)相对线程安全:保证对这个对象单独的操作是线程安全的,我们在【调用的时候】不需要做额外的保障措施。
4)线程兼容:对象并不是线程安全的,但是可以通过在调用端正确使用同步手段来保证对象在【并发环境中】安全地使用。
5)线程对立:不管调用端是否采取了同步措施,都无法在多线程环境中并发使用的代码。
3.线程安全的实现方法
(1)互斥同步【互斥-因/方法,同步-果/目的】
同步:指在多个线程并发访问共享数据时,保证共享数据在同一时刻只被一条线程使用。
互斥:实现同步的一种手段
互斥实现方式:临界区、互斥量和信号量
1)synchronized关键字:经过编译后,会在同步块的前后形成monitorenter和monitorexit两个字节码指令,这两个字节码都需要一个reference类型的参数来指明要锁定和解锁的对象。【具体:在执行monitorenter指令——>尝试获取对象的锁——>若对象】
注:synchronized同步块对同一线程来说是可重入的,不会自己把自己锁死。
同步块在已进入的线程执行完之前,会阻塞后面其他线程的进入。
(2)非阻塞同步:乐观并发策略——先进行操作,如果没有其他线程争用共享数据,那操作就成功了;若有争用,产生了冲突,再进行其他的补偿措施。
(3)无同步方案
线程安全的代码:
1)可重入代码:如果一个方法,它的返回结果是可以预测的,只要输入相同的数据,就能返回相同的结果,那他就是满足可重入性的要求,这样无需同步也能保证数据之间不会出现数据争用的问题
2)线程本地存储:代码中所需要的共享数据的代码
需要被多线程访问的变量——声明为valotile
需要被某个线程独享的变量——通过java.lang.ThreadLocal类来实现本地存储的功能。每一个线程的Thread对象中都有一个ThreadLocalMap对象,这个独享存储了一组以ThreadLocal.threadLocalHashCode为键,以本地线程变量为值的K-V值对,ThreadLocal对象就是当前线程的ThreadLocalMap的访问入口,每一个ThreadLocal对象都包含了一个独一无二的threadLocalHashCode值,使用这个值就可以在线程K-V值对中找到对应的本地线程变量。
4.锁优化
(1)自旋锁和自适应锁
1)自旋锁:由于一般共享数据的锁定状态只会持续很短的一段时间,。当物理机器有一个以上的处理器,能让两个或者以上的线程同时并行执行,可以让后面请求锁的线程“稍等一会”,但不放弃处理器的执行时间,看持有锁的线程是否很快就会释放锁。为了让线程等待,我们须让线程执行一个忙循环(自旋)
自旋次数默认值是10次,参数:-XX:PreBlockSpin
2)自适应的自旋锁
特性:自旋的时间不再固定了,由前一次在同一个锁上的自选时间及锁的拥有者的状态来决定。
(2)锁消除:虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除。
如何判断是否进行锁消除:来源于逃逸分析的数据支持。若一段代码,堆上的所有数据都不会逃逸出去被其他线程访问到,那就可以把他们当做栈上数据对待。
(3)锁粗化
场景:共享数据块的范围过大,一串零碎的操作都对一个对象加锁
做法:把加锁同步的范围扩展到整个操作序列的外部,这样就只需要加锁一次
(4)轻量级锁
场景:在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗
做法:在代码进入同步块的时候,若此同步对象没有被锁定,虚拟机首先在当前线程的栈帧中建立一个名为所记录的空间,用于存储对象目前的MarkWord的拷贝。
加锁——虚拟机将使用CAS操作将对象的MarkWord更新为指向LockRecord的指针。(该线程有了这个对象的锁,对象markword的所标志转变为00)
解锁——用CAS操作把对象当前的Markword和线程中复制的displaced mark word 替换回来,若替换成功,整个同步过程就完成了。
5.java对象(对象头部分)的内存布局
对象头=存储对象自身的运行时数据(hashcode/GC分代年龄) MarkWord + 存储指向方法区对象类型数据的指针
例:在32位的HotSpot迅疾中对象未被锁定的状态下
32bits = 25bits(存储对象hash码)+4bits (存储对象分代年龄)+2bits(存储锁标志位)+ 1bit(固定为0)
1.报错1https://www.cnblogs.com/zjfjava/p/6819459.html