synchronize相关
- 可见性,就是一个线程的操作了主内存的数据,另一个线程也可以实时同步主内存的数据,比如就是一个线程在for循环他一直用的是自己工作区的副本,主内存中的变量如果更新了,这个线程如果还在用副本的值,就是没有可见性
- 原子性,就是要么一块代码都执行完,要么都不执行
- 排序性,按照顺序执行代码,不让jvm指令重排
synchronize如何做到这3个特性
因为有锁的话每次 加锁 和 释放锁 都会同步主内存信息
Java内存模型
计算机内存结构是
CPU
缓存
内存
Java内存空间
方法区
jvm栈
本地方法栈
堆
程序计数器
Java内存模型分
主内存
线程工作空间
Java内存模型是一套规范,抽象出来的,不是真有这个物理结构,其主内存和工作空间都有可能存在内存和缓存中
主内存存放的是静态变量和成员变量,工作空间想操作主内存的数据,需要先拷贝到工作区副本,操作完在同步回去
具体步骤
- 先read到主内存的变量
- 在load加载到工作内存中
- 用到的时候就use操作
- 用完了assign分配回来改副本
- 然后在store存储到主内存
- 在write进变量
- 如果有锁的话,在上锁前即使方法区有该副本也先删除,同步过去最新的
- 解锁前,先同步回来在释放锁
syncornized两大特性
synchornize是可重入锁,意思就是synchornize嵌套,锁对象会有一个计数器 ,记录锁获取几次,获取+1,返回-1,计数器为0时释放锁
好处是,避免死锁,方便封装
synchornize是不可中断的 ,如果一个线程没获得锁会一直等着,处于block状态
lock也是,不过是wait状态
trylock可以设置时间,超过时间获得不到就干其他的去
synchornize原理
- 在执行synchornize的时候会执行monitorenter指令,判断锁对象有没有monitor,如果没有则创建,然后判断monitor的owner是不是自己,
- 如果空,设置monitor对象的owner为该线程,然后recursions加1,recursion是嵌套的意思,就是里面嵌套几个,
- 如果是自己,recursions加1
- 如果不是自己,线程阻塞执行到花括号结束的时候会执行monitorexit,recursions就会-1,当减到0时,线程释放锁
锁升级原理
重量级锁,因为他需要调用很多内核函数,涉及到用户态和内核态的切换,效率低
• 偏向锁,一个线程反复操作的话,会直接在对象头里保存线程ID,每次进同步代码块只需要看看ID是不是相同的,如果线程存在竞争,会先撤销锁
• 轻量级锁,在有线程竞争的时候,会把锁对象拷贝到栈帧里面的空间,线程拿不到锁不进人阻塞状态,而是自旋,自旋拿不到锁才会膨胀为重量级锁
• 自旋锁,因为线程拿不到锁转为阻塞状态比较耗时间,可能还没有转为阻塞状态,锁对象就释放了,所以自旋就是拿不到锁就多拿几次试试,具体次数是jvm根据以前自旋成功率来定
• 重量级锁,竞争不到线程会阻塞,不会自旋
jvm的锁优化
• 锁消除,在有些没有必要加锁的地方,jvm会通过逃逸分析判断,然后取消锁
• 锁粗化,就同一个对象的锁多次反复执行,会把锁放在这行代码外面,在锁里反复执行,不会去频繁创建锁
hashtable和currenthashtable
• Hashtable读和写都有锁,而且锁对象相同,写的时候不能读,而且操作一个桶的数据时,别的桶不能操作
• currenthashmap读的时候没有锁,写的时候锁对象为该桶第一个节点,可以同时读写,同时操作不同的桶
synchornize和lock的区别
• lock是接口
• lock有读锁,可以多线程读
• lock有reenlock可以控制是否是公平锁,synchornize是非公平锁
• lock只能修代码块
• lock可以知道是否拿到了锁
• lock遇到异常不会自动释放锁
• lock可以用trylock来避免阻塞
CAS
因为synchornize的效率低,volite又只能保证可见性和有序性,这时候atomic相关类就来了,他可以保证可见性,原子性,有序性
atomic里面用volite修饰变量,保证了可见性和有序性,原子性采用了CAS操作
CAS原理
• 使用了乐观锁,使用到了3个变量,内存地址值,旧预估值,要修改的值
• 先从地址值里查出值赋给旧预估值
• 真正操作前先判断地址值里的值和旧预估值里的值是否一样,如果一样就改值,如果不一样证明其他线程操作过,就重新执行一遍
• 如果竞争太过激烈,线程大概率会被切走,一直重复执行会降低效率,甚至比synchornize还低
• 竞争不太激烈的话,效率比synchornize高
CAS的aba问题
• 线程1在执行代码时从内存中拿出值a赋值给预估值,线程2抢到线程,a变为b,又改回了a,但改了其他值,线程1在切换回来会赋值成功
• 判断版本号