问题
今天面试官问我一个问题:有一个单例对象,对象里有2个方法a、b,方法名上都加了synchronized关键字,启动2个线程T1、T2,T1线程调用方法a,T2调用方法b,同时调用能成功吗?
我的回答:不能,面试官让我验证一下。What???难道不是吗?给普通方法加锁,锁住的是实例对象,2个线程肯定是一个个访问的呀。
这里有个知识点,synchronized的作用范围:
- 作用于非静态方法,锁住的是对象实例,即this对象
- 作用于静态方法,锁住的是Class
- 作用于代码块,锁住的是代码块中的配置的对象
下面来一一验证。
验证
作用于非静态方法,锁住的是对象实例,即this对象
单实例方法加锁
先创建一个单例
public class Singleton {
private static final Singleton singleton = new Singleton();
public static Singleton getSingleton(){
return singleton;
}
private Singleton(){
}
public synchronized void methodA(){
for(int i = 0; i < 20; i++){
System.out.println("method A" + i);
}
}
public synchronized void methodB(){
for(int i = 0; i < 20; i++){
System.out.println("method B" + i);
}
}
}
再创建两个线程
class ThreadA extends Thread{
Singleton singleton = Singleton.getSingleton();
@Override
public void run() {
System.out.println(singleton);
singleton.methodA();
}
}
class ThreadB extends Thread{
Singleton singleton = Singleton.getSingleton();
@Override
public void run() {
System.out.println(singleton);
singleton.methodB();
}
}
最后编写main方法测试
public class LockTest {
public static void main(String[] args) {
ThreadA a = new ThreadA();
ThreadB b = new ThreadB();
a.start();
b.start();
}
}
结果:
com.test.lock.Singleton@3badb99e
com.test.lock.Singleton@3badb99e
method B0
method B1
method B2
method B3
method B4
method B5
method B6
method B7
method B8
method B9
method B10
method B11
method B12
method B13
method B14
method B15
method B16
method B17
method B18
method B19
method A0
method A1
method A2
method A3
method A4
method A5
method A6
method A7
method A8
method A9
method A10
method A11
method A12
method A13
method A14
method A15
method A16
method A17
method A18
method A19
分析:单例,这个没问题,两个线程的执行顺序是一个先执行完,另一个才开始执行,两个线程有先后顺序,先后顺序不一定,有时候T1先执行,有时候T2先执行。这证明我一开始的猜想没错。
多实例方法加锁
public class Singleton {
public Singleton(){
}
public synchronized void methodA(){
for(int i = 0; i < 20; i++){
System.out.println("method A" + i);
}
}
public synchronized void methodB(){
for(int i = 0; i < 20; i++){
System.out.println("method B" + i);
}
}
}
结果:
com.test.lock.Singleton@3482f9ca
com.test.lock.Singleton@2d20bf50
method B0
method A0
method B1
method A1
method A2
method A3
method A4
method A5
method A6
method A7
method A8
method A9
method A10
method A11
method A12
method A13
method A14
method A15
method A16
method A17
method A18
method A19
method B2
method B3
method B4
method B5
method B6
method B7
method B8
method B9
method B10
method B11
method B12
method B13
method B14
method B15
method B16
method B17
method B18
method B19
多例,生成了2个实例对象,2个线程就是交叉执行。这一点也更加证明了给普通方法加锁,锁住的是实例对象。如果只有一个实例对象,线程就只能顺序执行,如果是多个对象,2个线程交叉执行,相当于同时执行。
同步块给this加锁(多实例)
public class Singleton {
public Singleton(){
}
public void methodA(){
synchronized (this) {
for (int i = 0; i < 20; i++) {
System.out.println("method A" + i);
}
}
}
public void methodB(){
synchronized (this) {
for (int i = 0; i < 20; i++) {
System.out.println("method B" + i);
}
}
}
}
结果:
com.springboot.webflux.test.lock.Singleton@cfc3eb5
com.springboot.webflux.test.lock.Singleton@14c9e724
method A0
method A1
method B0
method A2
method B1
method A3
method B2
method B3
method B4
method B5
method A4
method B6
method A5
method B7
method A6
method B8
method A7
method B9
method B10
method B11
method B12
method A8
method B13
method B14
method A9
method B15
method B16
method B17
method A10
method A11
method A12
method A13
method A14
method A15
method A16
method A17
method A18
method A19
method B18
method B19
代码块锁住this,锁的是实例对象。单例不用说肯定是顺序执行,多例是交替执行。
作用于静态方法,锁住的是Class
静态方法加锁(多实例)
在A、B方法上加上static关键字
public class Singleton {
public Singleton(){
}
public static synchronized void methodA(){
for(int i = 0; i < 20; i++){
System.out.println("method A" + i);
}
}
public static synchronized void methodB(){
for(int i = 0; i < 20; i++){
System.out.println("method B" + i);
}
}
}
结果:
method B0
method B1
method B2
method B3
method B4
method B5
method B6
method B7
method B8
method B9
method B10
method B11
method B12
method B13
method B14
method B15
method B16
method B17
method B18
method B19
method A0
method A1
method A2
method A3
method A4
method A5
method A6
method A7
method A8
method A9
method A10
method A11
method A12
method A13
method A14
method A15
method A16
method A17
method A18
method A19
多例,锁的是Class对象,顺序执行。
同步块给Class加锁(多实例)
public class Singleton {
public Singleton(){
}
public void methodA(){
synchronized (Singleton.class) {
for (int i = 0; i < 20; i++) {
System.out.println("method A" + i);
}
}
}
public void methodB(){
synchronized (Singleton.class) {
for (int i = 0; i < 20; i++) {
System.out.println("method B" + i);
}
}
}
}
结果:
com.springboot.webflux.test.lock.Singleton@cfc3eb5
com.springboot.webflux.test.lock.Singleton@2d20bf50
method A0
method A1
method A2
method A3
method A4
method A5
method A6
method A7
method A8
method A9
method A10
method A11
method A12
method A13
method A14
method A15
method A16
method A17
method A18
method A19
method B0
method B1
method B2
method B3
method B4
method B5
method B6
method B7
method B8
method B9
method B10
method B11
method B12
method B13
method B14
method B15
method B16
method B17
method B18
method B19
锁住的是Class对象,顺序执行。
作用于代码块,锁住的是代码块中的配置的对象
同步代码块给变量加锁(多实例)
public class Singleton {
public Singleton(){
}
private final String lock = "lock";
public void methodA(){
synchronized (lock) {
for (int i = 0; i < 20; i++) {
System.out.println("method A" + i);
}
}
}
public void methodB(){
synchronized (lock) {
for (int i = 0; i < 20; i++) {
System.out.println("method B" + i);
}
}
}
}
结果:
com.springboot.webflux.test.lock.Singleton@2d20bf50
com.springboot.webflux.test.lock.Singleton@3482f9ca
method A0
method A1
method A2
method A3
method A4
method A5
method A6
method A7
method A8
method A9
method A10
method A11
method A12
method A13
method A14
method A15
method A16
method A17
method A18
method A19
method B0
method B1
method B2
method B3
method B4
method B5
method B6
method B7
method B8
method B9
method B10
method B11
method B12
method B13
method B14
method B15
method B16
method B17
method B18
method B19
多例,顺序执行,锁的是lock对象,线程1和2使用的是同一个lock对象,线程1使用过程锁住了lock,所以线程2只能等待获取锁。