解决并发问题
保证可见性
相当于lock和unlock原子操作,会在多线程对变量进行操作的时候从主内存重新获取最新的值到工作内存,这样就能保证可见性
保证有序性
cpu和编辑器会对代码重新排序
as-if-serial
即不管如何重新排序,必须保证单线程下结果是正确的
程序仍然会重新排序,但是sync会保证只有一个线程执行,所以重排序会保证了有序性
特性
可重入
概念:一个线程可以多次执行sync,重复获取同一把锁,也就是方法调用方法可以重复使用sync
原理:sync对象有一个计数器(recursions变量)会记录线程获得几次锁,执行完同步代码块计数器会减1,直到计数器数量为0即释放锁
好处:1,可以避免死锁。2,可以更好地封装代码
不可中断
概念:一个线程获得锁后,另一个线程想要获得锁,那么该线程必须处于阻塞或者等待状态,而且是不可中断的(interrupt()不能中断线程),会一直等待
ReentrantLock
方法lock()不可中断
方法trylock()可中断<br>
锁升级过程jdk1.6之后添加
无锁状态
偏向锁
原理:当锁对象第一次被线程获取的时候,虚拟机会把对象头的标识位标记为01,即偏向模式,同时使用CAS操作把获取到的线程锁id存储到Mark word中,持有偏向锁的线程之后每次进入同步块时,虚拟机都不会再进行任何同步操作,偏向锁单线程效率高
好处:适用于单线程进入同步代码块反复获得同一个锁的情况,可以提高带有同步但无竞争的程序效率,当存在竞争的时候就会升级为轻量级锁
乐观锁
轻量级锁
原理:线程在执行同步代码块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中,官方称为Displaced Mark Word。然后线程尝试使用 CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。
好处:在多线程交替执行同步代码块的时候,可以避免重量级锁引起的性能消耗
(自旋锁)
在锁升级的过程中,为了避免线程的阻塞和唤醒对cpu的负载过大,会先尝试自旋锁,减少升级为重量级锁
自适应自旋
乐观锁
锁消除
jvm会在一段认为并不存在线程的共享数据安全问题的代码块中消除锁。如Stringbuffer
锁粗化
例如:在for循环中在外面加锁而不是在循环内加锁,即锁粗化。避免加锁解锁消耗资源
升级过程:
当创建一个锁对象的时候,对象会有一个mark word,存储了是否是偏向锁和标志位,标志位为0非偏向锁,1为偏向锁,初始为无锁状态,第一个线程进来的时候,会被标记为偏向锁,在之后的执行过程中如果没有出现线程竞争,那么这个持有偏向锁的线程永远不需要同步操作。如果有其它线程进入,那么就会释放偏向锁,在释放偏向锁的过程中,需要等到全局安全点,导致性能下降,所以如果一段程序有多个线程竞争的话,需要禁用偏向锁否则加锁和释放很消耗资源。偏向锁释放之后会升级为轻量级锁,轻量级锁会使用cas算法在mark word中竞争锁,如果在此期间一直没有获取到锁,那么就会尝试自旋锁循环尝试获取锁,最终没有获得锁就会锁膨胀升级为重量级锁
面试题
sync代码块遇到异常会释放锁吗
会,可以通过反编译文件看出,monitorexit会在代码执行异常时也会执行一次。
sync和lock区别
sync是关键字,lock是接口
sync会自动释放锁,lock手动释放锁
sync是不可中断的,lock的lock()不可中断trylock()可中断
sync不能知道线程是否有拿到锁,而lock通过返回值可以判断
sync用于方法和代码块,lock只能用于代码块
lock可以使用读锁提高读的效率
sync是非公平锁,lock可以控制是否公平锁
即多个线程处于等待状态的时候,唤醒时会随机抽取一个而不是先来后到,所以是非公平的
sync关键字
sync主要用于多线程访问资源的同步性问题,保证修饰的方法和代码块在任意时刻只有一个线程执行,在旧的jdk版本只有重量级锁,重量级锁效率低下,其底层使用的monitor需要频繁的切换线程用户到内核再到用户,在jdk1.6之后新增了锁升级策略减少重量级锁的开销。修饰代码块是使用monitorenter和monitorexit操作锁,修饰方法是使用ACC_SYNCHRONIZED修饰隐式调用。monitor信息存储在每一个java对象的对象头中,monitor里面有一个计数器标记,当计数器为0代表锁被释放。锁升级就是从偏向锁到轻量锁再到重量级锁的过程,偏向锁适用于单线程,当竞争激烈时就会升级为轻量锁,轻量锁没有多线程的消耗,加锁解锁只是通过CAS策略,轻量级锁会通过自旋锁,自适应自旋锁获取锁,如果一直没有获取成功那么就是通过锁膨胀的方式升级为重量级锁。