1、对象锁
包括方法锁(默认锁对象为this,当前实例对象)和同步代码块锁(自己指定锁对象)
2、类锁
指synchronize修饰静态的方法或指定锁对象为Class对象
3、加锁和释放锁的原理:现象、时机(内置锁this)、深入JVM看字节码(反编译看monitor指令)
Monitorenter和Monitorexit指令,会让对象在执行,使其锁计数器加1或者减1。
monitor计数器为0,意味着目前还没有被获得,那这个线程就会立刻获得然后把锁计数器+1,一旦+1,别的线程再想获取,就需要等待
如果这个monitor已经拿到了这个锁的所有权,又重入了这把锁,那锁计数器就会累加,变成2,并且随着重入的次数,会一直累加
这把锁已经被别的线程获取了,等待锁释放
monitorexit指令:释放对于monitor的所有权,释放过程很简单,就是讲monitor的计数器减1,如果减完以后,计数器不是0,则代表刚才是重入进来的,当前线程还继续持有这把锁的所有权,如果计数器变成0,则代表当前线程不再拥有该monitor的所有权,即释放锁。
4、保证可见性的原理:内存模型和happens-before规则
4、Synchronied同步锁,一共有四种状态:无锁、偏向锁、轻量级锁、重量级锁,它会随着竞争情况逐渐升级。锁可以升级但是不可以降级,目的是为了提供获取锁和释放锁的效率。
无锁状态(无竞争状态):
当一个线程第一次尝试进入同步块时,它会尝试获取锁。如果此时没有其他线程竞争这个锁,那么该线程将获得锁,并且锁的状态会被设置为偏向该线程。
偏向锁:
当一个线程第一次访问一个同步块时,偏向锁会记录下这个线程的ID,并将对象头中的锁标志位设置为偏向锁。
在接下来的访问中,如果没有其他线程尝试获取锁,那么该线程会无竞争地获得锁,并且不需要进行同步操作,提高性能。
如果其他线程尝试获取锁,偏向锁就会失效,升级为轻量级锁,然后继续按照轻量级锁的规则执行。
偏向锁适用于大部分情况下是单线程访问同步块的场景,例如在对象创建后,初始化阶段由单个线程执行。但是,如果存在多线程并发访问的情况,偏向锁的性能优势可能会受到影响,因为偏向锁失效后需要转为轻量级锁,引入了额外的开销。
轻量级锁升级为重量级锁: 如果轻量级锁的竞争仍然存在,最终锁会升级为重量级锁(synchronized锁)。这时候,锁的状态会转变为重量级锁,使用互斥量等机制来确保线程安全,但这也带来了一定的性能开销。
5、使用Synchronized有哪些要注意的?
锁对象不能为空,因为锁的信息都保存在对象头里作用域不宜过大,影响程序执行的速度,控制范围过大,编写代码也容易出错避免死锁在能选择的情况下,既不要用Lock也不要用synchronized关键字,用java.util.concurrent包中的各种各样的类,如果不用该包下的类,在满足业务的情况下,可以使用synchronized关键,因为代码量少,避免出错
6、synchronized是公平锁吗?
synchronized实际上是非公平的,新来的线程有可能立即获得监视器,而在等待区中等候已久的线程可能再次等待,这样有利于提高性能,但是也可能会导致饥饿现象。