前言
最近在看Glide 4.11的源码时,无意间看到以下一段代码。我就纳闷,为什么不直接用this就好?非要用Object对象,多此一举呢?,后面经一波探究,发现了不同点。
public static class SynchronizedPool<T> extends SimplePool<T> {
private final Object mLock = new Object();
public SynchronizedPool(int maxPoolSize) {
super(maxPoolSize);
}
@Override
public T acquire() {
synchronized (mLock) {
return super.acquire();
}
}
@Override
public boolean release(@NonNull T element) {
synchronized (mLock) {
return super.release(element);
}
}
}
一,同步作用相同。
都可以实现,多线程对类的同一个实例的方法的同步,都属于对象锁的范畴。
二,锁对象不同。
synchronized(this),锁的对象是this,是类的实例。
synchronized(object),锁的对象是object。
class Test{
private final Object object = new Object();
public void print(){
synchronized (object){
System.out.println("xxxx");
}
}
}
class Test{
public void print(){
synchronized (this){
System.out.println("xxxx");
}
}
}
三,那为什么要用 synchronized (object) 这种方式,去多创一个object对象呢?让我们先看一下以下代码。
class STest{
public void print(){
synchronized (this){
System.out.println("xxxx");
}
}
}
public class SynchronizeMain {
public static void main(String[] args) throws InterruptedException {
STest sTest = new STest();
// Thread 1
Thread t1 = new Thread(() -> {
sTest.print();
});
// Thread 2
Thread t2 = new Thread(() -> {
try {
synchronized (sTest){
while (true);
}
} catch (Exception e) {
System.out.println("Exception="+e.getMessage());
}
});
t2.start();
t1.start();
}
}
输出结果是什么都没有! 原因在于,synchronized(this)的锁对象是this,如果使用这种方式,一旦锁对象(实例)被别人获取,别人只要开个线程像Thread 2 一样,那你的Thread 1,这个正常的工作线程,就永远得不到执行,造成死锁。让我们再看看另一个例子:
class STest{
private final Object object = new Object();
public void print(){
synchronized (object){
System.out.println("xxxx");
}
}
}
public class SynchronizeMain {
public static void main(String[] args) throws InterruptedException {
STest sTest = new STest();
// Thread 1
Thread t1 = new Thread(() -> {
sTest.print();
});
// Thread 2
Thread t2 = new Thread(() -> {
try {
synchronized (sTest){
while (true);
}
} catch (Exception e) {
System.out.println("Exception="+e.getMessage());
}
});
t2.start();
Thread.sleep(1000);
t1.start();
}
}
输出结果是 xxxx! 原因在于,synchronized(object),锁的对象是object。所以,你拿到了sTest的实例,也不会影响到Thread1的正常执行。
四,如果我通过反射,获取object变量,传入Thread2里面,会怎么样呢?
如果是这样的话,那一样可以使得Thread 1,这个正常的工作线程,永远得不到执行,造成死锁。Talk is cheap, Show me code:
class STest{
private final Object object = new Object();
public void print(){
synchronized (object){
System.out.println("xxxx");
}
}
}
public class SynchronizeMain {
public static void main(String[] args) throws InterruptedException {
STest sTest = new STest();
// Thread 1
Thread t1 = new Thread(() -> {
sTest.print();
});
// Thread 2
Thread t2 = new Thread(() -> {
try {
synchronized (getPrivateField(sTest,"object")){
while (true);
}
} catch (Exception e) {
System.out.println("Exception="+e.getMessage());
}
});
t2.start();
Thread.sleep(1000);
t1.start();
}
public static Object getPrivateField(Object instance, String filedName) throws NoSuchFieldException, IllegalAccessException {
Field field = instance.getClass().getDeclaredField(filedName);
field.setAccessible(true);
return field.get(instance);
}
}
输出结果是什么都没有! 原因在于,synchronized(object)的锁对象是object,而object被Thread2占用了。
总结
选择锁对象时,要考虑到锁对象的安全性,尽量避免锁对象暴露出去。很多开源库采用的同步方法块的方法是通过设置object来实现。虽然,它仍然可以通过反射来破解。但却增强了一层保障,以及一些可能存在人为的误操作,导致的死锁。