Java多线程(一)--synchronized

synchronized

  synchronized保证使用同一个对象锁的代码块在同一时间只能被一个线程执行。方法对象静态方法class类锁都属于对象锁(Class对象是唯一的、全局的)
方法this对象
对象:创建的对象
静态方法:方法所在类的Class对象
class类锁Class对象

用法

对象锁

  • 代码块

      如果只有一个obj对象,当线程1执行到synchronized代码块时,线程2想执行synchronized代码块的话要等obj的锁被释放,也就是线程1把synchronized代码块执行完成。因为这里使用的是同一个对象

1
2
3
4
5
Object obj=new Object();

synchronized(obj){
...
}

另一种写法是使用this,因为this指的是当前对象,所以和上面的意思一样

1
2
3
synchronized(this){
...
}
  • 方法
    直接加在普通方法上和synchronized(this)意思一样,直接使用当前对象加锁
1
2
3
public synchronized void doSomething(){
...
}

类锁

  • 代码块
      因为所有class都含有一个Class对]象,并且在程序运行时是唯一的,所以就相对于使用的是同一个对象进行加锁

    1
    2
    3
    synchronized(Main.class){
    ...
    }
  • 方法(static)
      synchronized修饰普通方法加锁的是方法所在对象,而在静态方法里面使用加锁的是方法所在的类的class对象,所以效果等同于类锁的代码块形式

    1
    2
    3
    public synchronized static void doSomethong(){
    ...
    }

性质

  • 不可中断
    只能等待另一个线程释放锁之后才能获取锁
  • 可重入性质
    同一线程的外层获取锁之后,内层也可以再次获得锁,而不需要等待释放锁。避免了外层获取而内层不能获取,然后外层又因为内层阻塞释放不了锁而形成的死锁
    锁的粒度是线程

原理

  • 可见性原理:解锁时把工作内存的共享变量值刷新到主内存,加锁时清空工作内存的共享变量值
  • 加锁和释放锁使用monitor来实现
  • 重入锁靠的加减monitor
    • 当获得锁之后,monitor+1,当内部代码再次获取锁时,monitor再+1
    • 内部代码执行完离开时,monitor-1,外层执行完成再次-1
    • monitor等于0时说明锁已经被释放了,不等于0则其它尝试获取锁的线程会阻塞
  • 加锁使用monitorenter,释放使用monitorexit
    可以通过反编译代码看到

    • 先使用javac编译下面的代码
1
2
3
4
5
public void test(){
synchronized(this){
...
}
}
  • 然后使用javap -verbose 类名获得汇编代码(这里截取部分反编译后的代码)
    可以看到3:处加锁使用的monitorenter以及5:处使用的monitorexit

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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