多线程访问同一数据的不一致性问题
多线程访问同一个数据时产生数据的不一致性问题
解决:对线程做一个同步,保证它操作的原子性,也就是说,把一个线程的操作当成一个整体,等一个线程结束后,另一个线程才可以操作。
synchronized(互斥锁)
只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法。
对象锁:锁任意对象都可以。
Synchronized取得的锁都是对象锁,而不是把一段代码或方法当作锁,哪个线程执行带synchronized关键字的方法,哪个线程就持有该方法所属对象的锁,那么其他线程只能等待,前提是多个线程访问的是同一个对象。
对象在内存中的布局
mark word(8字节)class pointer(4字节)实例数据没有成员变量则为0 不够8整除的补齐
加锁信息记录在mark word中。
mark word 三大作用:记录所信息,记录GC垃圾回收信息(分代年龄和被垃圾回收的次数),hashcode
JAVA线程模型
JAVA new一个线程是JAVA线程,JVM开启一个线程,操作系统也必定开启一个线程,一比一模型,JVM将线程的调度,锁的同步直接交给操作系统去执行。
锁升级过程
偏向锁:
JDK15,偏向锁默认关闭。JDK8,偏向锁默认打开。
偏向锁一般用于第一个上锁的线程
举例:Vector,StringBuffer,hashtable 线程安全,自带同步。虽然自带同步(synchronized),但是在70%-80%情况下,不存在锁竞争。
打开偏向锁是否效率一定会提升?为什么?
明确知道在有很多线程来竞争,锁撤销过程也消耗CPU资源,效率会降低。
匿名偏向:
处于偏向锁状态,但没有上锁。但是调用wait()方法则会进入等待队列,直接升级重量级锁。
偏向锁升级轻量级锁:
批量重偏向和批量锁撤销(了解)。
轻量级锁(自旋锁、CAS、乐观锁):
自旋锁while(true)循环,在执行的过程中上锁,在写回的过程中与原先的数据进行比较。如果数据没有发生变化,则保证了数据的原子性。中间如果有其他线程对数据进行修改,则继续进行++操作,直到原线程的数据保持一致,当然,也存在一些问题,ABA问题,假如在操作过程中,另一个线程将数据改回原来的值,只需要加个version版本号即可。如果在比较过程中,这个操作被其他线程打断,所以也需要保证比较的原子性,通过硬件汇编lock保证。
轻量级锁:JUC包下ReentrantLock,AtomicInteger
重量级锁:
JVM将所有对锁的操作交给操作系统来做,操作系统启动线程,对线程的同步都消耗很多资源。重量级锁将线程放入等待队列,在等待队列中不消耗CPU资源。
与轻量级锁的区别:轻量级锁在JVM线程中操作,while(true)循环,没必要交给操作系统,线程是一直运行的,消耗CPU资源。
什么时候采用重量级锁?什么时候采用轻量级锁?
线程等待数量多时,线程竞争度过强,临界区的时间过长,一般不用自旋锁。CPU将时间都消耗在了线程的切换上,资源效率利用不高。如果线程数量不多时,则可以采用轻量级锁。
线程等待数量多时,线程竞争度过强,临界区的时间过长,将线程放入等待队列,采用重量级锁能节省CPU的资源。
锁升级:
偏向锁升级过程:
偏向锁不参与锁的竞争,偏向于第一个抢占到的线程,当只有一个线程时,使用偏向锁,减少锁的竞争过程,提升效率。
没有偏向锁,增加所的竞争,效率会下降。
第一次锁升级:偏向锁---->自旋锁(存在锁竞争的时候)
第二次锁升级:轻量级锁----->重量级锁(线程等待数量多,线程竞争度过强,临界区的时间过长)
锁降级过程
锁只有垃圾回收线程访问,才会降级,无意义,可以简单认为不存在。
AtomicInteger实现原理 CAS原子性
AtomicInteger 底层使用了自旋锁(CAS)
AtomicInteger atomicInteger = new AtomicInteger(0);
atomicInteger.incrementAndGet();//实现原子性的自增
CAS在最底层汇编指令使用cmpxchg指令完成,多核处理器前面+lock(锁总线),保证原子性,单核处理器 只需要cmpxchg指令完成。
结语:笔记是根据马士兵老师的多线程学习总结的,很感谢马士兵老师的免费公开课程,学到了很多知识点。