Synchronized(一) 从例子看Synchronized的特点

71 阅读3分钟

首先,我们从一个延迟初始化对象入手,来解析深度学习Synchronized。


public class UnsafeLazyInitizalization{

 public static User user
 public static User doubleCheckLock(){
    if(user == null){
       return new User();
    }
    return user;
    }
}

首先我们思考一下这段代码,如果在并发的情况下,会出现怎么样的一个问题?

这段代码的目的是创建有且仅有一个的User对象,但在并发中会有这样的问题:

  1. A线程和B线程齐头并进,首先A线程进入了if判断语句中。
  2. A准备执行return new User()语句,B线程此时也执行了if判断语句。
  3. 这个时候A线程返回了新的User对象,而B线程也在随后也能够执行new User()语句。
  4. 此时A、B线程所获取的User对象是不同的两个对象

这时候,大家也应该跟我一样,给他加锁,解决并发问题!

好,这个时候我们将锁加上:

public class SafeLazyInitizalization{

 public static User user
 public synchronized static User doubleCheckLock(){
    if(user == null){
       return new User();
    }
    return user;
    }
}

这样,看起来好像就万事大吉了,那我们能试着优化一下这段代码吗?

首先,这个锁加的,能够实现我们的线程可以有序的去执行对应的代码块,那是因为synchronized保证了数据的可见性,这是我们今天讲到synchronized的第一个知识点:可见性,也就是每个线程能够获取的数据都是最新的。

其次,我们来优化一下这段代码:

public class DoubleCheckLock{

 public static User user
 public static User doubleCheckLock(){
    if(user == null){
        synchronized(DoubleCheckLock.class){
           return new User();
       }
    }
    return user;
    }
}

这段代码实现的功能跟上面的一致,但减少了性能的开销:将锁设置在了方法内,而不是在方法上;这样的实现也是早期的双重检查锁,在提高性能的同时又做到了线程安全。

但,事情真的这么简单吗?这样真的就万无一失了嘛?

我们先来了解一下一个对象的创建流程:

ins = new Instance()

memory = allocate();   // 1:分配对象的内存空间
ctorInstance(memory);	//2:初始化对象
ins = memory;           //3:设置ins指向分配的内存地址

这是创建一个对象所对应的指令,而synchronized无法保证这些指令不会重排序(这是源代码到指令序列所导致的一个问题) https://www.yuque.com/u22628569/uk26g9/pyikwdplrmeaxi0a?singleDoc#

也就可能出现下面的这种情况

时间线程A线程B
t1A1:分配对象的内存空间
t2A3:设置ins指向内存空间
t3B1:判断ins是否为空
t4B2:由于ins不为null,线程B将访问ins引用的对象
t5A2:初始化对象
t6A4:访问ins引用的对象

也就是 2和3步骤被重排序了,这样就导致了B线程拿到的一个没被初始化的对象。这也就衍生出了Synchronized无法禁止指令重排序,那我们可以使用一个可以禁止指令重排序的修饰符volatile来避免这样的问题。

public class DoubleCheckLock{

 public static volatile User user
 public static User doubleCheckLock(){
    if(user == null){
        synchronized(DoubleCheckLock.class){
           return new User();
       }
    }
    return user;
    }
}

这样,就是双重检查锁最终形态和为什么一个Synchronized解决不了并发情况下创建一个对象的原因。同时,我们也了解到Synchronized的两个特点:可见性、无法禁止重排序、(还有一个原子性)

当然,还有另外一种类初始化的解决方案,利用了类初始化后就会放到方法区的特点和类变量来实现的,有兴趣的可以点一下上面的链接去查看我的笔记

下一篇我们讲Synchronized的实现原理,以及锁升级