面试的时候说道了单例,又扯到了加锁等。后来面试官问了问静态方法加锁和非静态方法加锁的区别。结果尴尬了,还是自己没有太动脑筋,其实挺容易能够想到的。
static方法调用方式是通过class.fun
,而非static方法调用是先new出这个对象,再调用
。
static synchronized
是类锁,synchronized
是对象锁。
对象锁(又称实例锁,
synchronized
):该锁针对的是该实例对象(当前对象)。
synchronized
是对类的当前实例(当前对象)进行加锁,防止其他线程同时访问该类的该实例的所有synchronized块,注意这里是“类的当前实例”, 类的两个不同实例就没有这种约束了。
每个对象都有一个锁,且是唯一的。类锁(又称全局锁,
static synchronized
):该锁针对的是类,无论实例出多少个对象,那么线程依然共享该锁。
static synchronized
是限制多线程中该类的所有实例同时访问该类所对应的代码块。(实例.fun
实际上相当于class.fun
)
pulbic class Something(){
// 非静态方法加锁
public synchronized void isSyncA(){}
public synchronized void isSyncB(){}
// 静态方法加锁
public static synchronized void cSyncA(){}
public static synchronized void cSyncB(){}
}
假如有Something类的两个实例 Something x = new Something()
与
Something y = new Something()
,如果用多线程进行下述访问
-
x.isSyncA()
在多个线程同时访问该方法,显然同实例对象加锁,因此会线程同步,不会被同时访问,有约束。xThread1: 1 xThread1: 2 xThread1: 3 xThread2: 1 xThread2: 2 xThread2: 3
x.isSyncA()与x.isSyncB()
这里就要说道,每个对象都有且只有一个锁。
假设分配的一个对象空间(new了一个对象),里面有多个方法,相当于空间里面有多个小房间,如果我们把所有或部分的小房间都加锁(synchronized
),因为这个对象只有一把钥匙,因此同一时间只能有一个人打开一个小房间,然后用完了还回去,再由JVM 去分配下一个获得钥匙的人。
所以此处,都是对同一个实例的synchronized域访问,因此不能被同时访问,有约束。x.isSyncA()与y.isSyncA()
不同的实例对象会生成不同的对象锁(对象锁对于不同的对象实例没有锁的约束),因此两者互不影响,可以同时访问(运行时成交替输出),无约束。x.cSyncA()与y.cSyncB()
x.cSyncA()与y.cSyncB()
相当于是Something.cSyncA()与Something.cSyncB()
。
对于类锁,相当于将所有实例对象共享了这一唯一锁,因此即便是不同实例对象之间,仍然会被限制。所以不能被同时访问,有约束。x.isSyncA()与x.cSyncA()(相当于Something.cSyncA())
这是一种较为特殊的情况,加锁的实例对象方法与加锁的类方法由于锁定(lock)不同这一原因,各自管自己的,因此并无约束,可以同时被访问到,无约束。
类锁和对象锁是两个不一样的锁,控制着不同的区域,两者互不干扰。
在线程获得对象锁的同时,也可以获得该实例的类锁,即同时获得两个锁,这是允许的。
总结:
- 一个锁的是类对象,一个锁的是实例对象。
- 若类对象被lock,则类对象的所有同步方法(static synchronized func)全被lock。
- 若实例对象被lock,则该实例对象的所有同步方法(synchronized func)全被lock。
synchronized方法与synchronized代码块的区别
synchronized methods() {}
与 synchronized (this) {}
之间并没有什么区别。只是前者便于阅读理解,而后者可以更精确的控制冲突限制访问区域,有时候表现得更加高效。
synchronized
代码块获得的是一个对象锁,锁住的同样是整个对象。
public void test4() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + " : " + i);
}
System.out.println();
synchronized (this) {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + " s: " + i);
}
}
}
比如在上述代码中,假设有两个线程访问该方法。
- 当A线程访问对象的synchronized(this)代码块的时候,B线程依然可以访问对象方法中其余非synchronized块的部分。
- 当A线程进入对象的synchronized(this)代码块的时候,B线程如果要访问这段synchronized块,那么访问将会被阻塞。
// 运行结果
thread2 : 0
thread1 : 0
thread2 : 1
thread1 : 1
thread2 : 2
thread1 : 2
thread1 s: 0
thread1 s: 1
thread1 s: 2
thread2 s: 0
thread2 s: 1
thread2 s: 2
由上可知,从执行效率的角度考虑,有时候并不需要将整个方法都加上synchronized,而是可以采取synchronized代码块的方式,对会引起线程安全问题的那部分代码进行synchronized就可以了。
注:由上述知道,synchronized方法和synchronized代码块没有太大区别,所以假设线程A访问了对象的X方法中的synchronized代码块部分,线程B访问同一对象的Y方法中的synchronized方法/代码块,都会被堵塞。
synchronized(非this对象)
上述都是使用synchronized(this)的格式来同步代码块,但JAVA还支持对"任意对象"作为对象监视器来实现同步的功能。这个"任意对象"大多数是实例变量及方法的参数,使用格式为synchronized(非this对象)。
其实同理,锁住的不是当前实例对象,而是放入synchronized(非this对象)中的非this对象,即对该对象进行加锁。
优点:
如果在一个类中有很多synchronized方法,这时虽然能实现同步,但会受到阻塞,从而影响效率。
但如果同步代码块锁的是非this对象,则synchronized(非this对象)代码块中的程序与同步方法是异步的,不与其他锁this同步方法争抢this锁,大大提高了运行效率。
注:synchronized(非this对象),这个对象如果是实例变量的话,指的是对象的引用,只要对象的引用不变,即使改变了对象的属性,运行结果依然是同步的。
参考:
Synchronized(对象锁)和Static Synchronized(类锁)的区别
java的静态方法加锁与一般方法加锁
java 多线程9 : synchronized锁机制 之 代码块锁