synchronized
synchronized保证使用同一个对象锁的代码块在同一时间只能被一个线程执行。方法
、对象
、静态方法
、class类锁
都属于对象锁(Class对象是唯一的、全局的)
方法
:this
对象
对象
:创建的对象
静态方法
:方法所在类的Class对象
class类锁
:Class对象
用法
对象锁
-
代码块
如果只有一个obj对象,当线程1执行到synchronized代码块时,线程2想执行synchronized代码块的话要等obj的锁被释放,也就是线程1把synchronized代码块执行完成。因为这里使用的是同一个对象Object obj=new Object(); synchronized(obj){ ... }
另一种写法是使用
this
,因为this指的是当前对象,所以和上面的意思一样synchronized(this){ ... }
-
方法
直接加在普通方法上和synchronized(this)
意思一样,直接使用当前对象加锁public synchronized void doSomething(){ ... }
类锁
-
代码块
因为所有class都含有一个Class对]象,并且在程序运行时是唯一的,所以就相对于使用的是同一个对象进行加锁synchronized(Main.class){ ... }
-
方法(
static
)
synchronized
修饰普通方法加锁的是方法所在对象,而在静态方法里面使用加锁的是方法所在的类的class对象,所以效果等同于类锁的代码块形式public synchronized static void doSomethong(){ ... }
性质
- 不可中断
只能等待另一个线程释放锁之后才能获取锁 - 可重入性质
同一线程的外层获取锁之后,内层也可以再次获得锁,而不需要等待释放锁。避免了外层获取而内层不能获取,然后外层又因为内层阻塞释放不了锁而形成的死锁
锁的粒度是线程
原理
-
可见性原理:解锁时把工作内存的共享变量值刷新到主内存,加锁时清空工作内存的共享变量值
-
加锁和释放锁使用
monitor
来实现 -
重入锁靠的加减
monitor
- 当获得锁之后,
monitor
+1,当内部代码再次获取锁时,monitor
再+1 - 内部代码执行完离开时,
monitor
-1,外层执行完成再次-1 - 当
monitor
等于0时说明锁已经被释放了,不等于0则其它尝试获取锁的线程会阻塞
- 当获得锁之后,
-
加锁使用
monitorenter
,释放使用monitorexit
可以通过反编译代码看到- 先使用
javac
编译下面的代码
public void test(){ synchronized(this){ ... } }
- 然后使用
javap -verbose 类名
获得汇编代码(这里截取部分反编译后的代码)
可以看到3:
处加锁使用的monitorenter
以及5:
处使用的monitorexit
0: aload_0 1: dup 2: astore_1 3: monitorenter 4: aload_1 5: monitorexit 6: goto 14 9: astore_2 10: aload_1 11: monitorexit 12: aload_2 13: athrow 14: return
- 先使用
缺点
- 效率低
- 锁的释放情况少
- 不可中断
- 获取锁不能设置超时
- 不够灵活
- 加锁和释放时机单一
- 每个锁只有单一的条件
- 无法知道锁的获取状态
注意点
- 锁对象不能为空,否则会报异常,因为加锁靠的是对象的
monitor
- 作用域不要太大,否则可能效率低
- 避免死锁
- 使用推荐:并发包 > synchronized > Lock