Synchronized本质上都是依赖对象来锁,根据不同的对象类型,可以分为三种锁粒度

  • this:当前实例锁
  • class:类对象锁
  • Object:对象实例锁

synchronized 关键字的使用方式主要有下面 3 种:

  • 修饰实例成员方法:使用this锁,线程想要执行被Synchronized关键字修饰的普通方法,必须先获取当前实例对象的锁资源;
  • 修饰静态成员方法:使用class锁,线程想要执行被Synchronized关键字修饰的静态方法,必须先获取当前类对象的锁资源;
  • 修饰代码块:使用Object锁,使用给定的对象实现锁功能,线程想要执行被Synchronized关键字修饰的代码块,必须先获取当前给定对象的锁资源。

1、修饰实例方法 (锁当前对象实例)

给当前对象实例加锁,进入同步代码前要获得 当前对象实例的锁

1
2
3
synchronized void method() {
//业务代码
}

当一个线程正在访问一个被synchronized修饰的实例方法时,其他线程则不能访问该对象的其他被synchronized修饰的对象实例方法,毕竟一个对象只有一把锁,当一个线程获取了该对象的锁之后,其他线程无法获取该对象的锁,所以无法访问该对象的其他被synchronized修饰的对象实例方法。但是如果有其他方法未被synchronized修饰,又或者其他被synchronized修饰的是静态方法,这类方法其他线程还是可以访问的

2、修饰静态方法 (锁当前类)

synchronized用于修饰静态方法时,其锁就是当前类的class对象,当使用class锁时,当前Java程序中,一个类只会生成一个class对象,不会因为new出多个实例造成多把锁、线程分别获取不同锁资源的情况发生。

这是因为静态成员不属于任何一个实例对象,归整个类所有,不依赖于类的特定实例,被类的所有实例共享。

1
2
3
synchronized static void method() {
//业务代码
}

静态 synchronized 方法和非静态 synchronized 方法之间的调用互斥么?

不互斥!如果一个线程 A 调用一个实例对象的非静态 synchronized 方法,而线程 B 需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。

3、修饰代码块 (锁指定对象/类)

对括号里指定的对象/类加锁:

  • synchronized(object) 表示进入同步代码库前要获得 给定对象的锁,也就是对对象加锁
  • synchronized(类.class) 表示进入同步代码前要获得 给定 类 的锁,也就是对整个类加锁

这里的 lockObject 是一个对象引用,它可以是任何对象,通常会选择一个与临界资源相关的对象作为锁的对象。当多个线程试图访问同一段被 synchronized 修饰的代码块时,它们必须获取同一个锁对象,否则无法进入临界区

1
2
3
synchronized(lockObject) {
//业务代码
}

总结:

  • synchronized 关键字加到 static 静态方法和 synchronized(class) 代码块上都是是给 Class 类上锁;
  • synchronized 关键字加到实例方法上是给对象实例上锁;
  • 尽量不要使用 synchronized(String a) 因为 JVM 中,字符串常量池具有缓存功能