上篇文章介绍了饿汉模式,由于懒汉模式内容多所以新写一篇介绍懒汉模式,想要看饿汉模式移步——
懒汉模式:调用时采取创建实例
public class Singleton {
private static Singleton instance;
private Singleton() {
System.out.println("I am singleton instance");
}
public static Singleton getInstance() {
if(instance==null)
instance = new Singleton();
return instance;
}
}
注意如果我们不主动去同步,那么懒汉模式是线程不安全的
if(instance==null)
instance = new Singleton();
我们可以看到,懒汉式创建对象有一个判断的过程,也就是两个步骤,那么就会出现问题:
如果两个线程同时到达了判断语句,然后当前实例为空,所以他们分别进行了实例化,也就是
Thread1:if(instance==null)//判断为真
Thread2:if(instance==null)//同样为真
Thread1:instance = new Singleton();
Thread2:instance = new Singleton();
下面写出具体的例子:
测试线程安全类
public class Client implements Runnable{
Singleton instance;
public static void main(String[] args) {
Client c1 = new Client();
Client c2 = new Client();
new Thread(c1).start();
new Thread(c2).start();
try {
Thread.sleep(200);
//保证两个线程跑完
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(c1.instance==c2.instance);
}
@Override
public void run() {
instance = Singleton.getInstance();
}
}
运行结果如下
会发现进行了两次构造,并且两个实例不是同一个对象
那么就需要我们为其加上同步。
懒汉式+同步方法
懒汉式的同步方法有很多,不同方法的粒度也不一样,这里先介绍几个简单的
- 类锁(静态方法锁)
public synchronized static Singleton getInstance() {
if(instance==null)
instance = new Singleton();
return instance;
}
很简单,就是在获取实例的地方加锁,这样在一个时间点只有一个线程可以访问这段代码,就保证了线程安全,下面是使用两个线程获取实例(也就是上面的测试线程安全类)的运行结果
可以看到实例是同一个
2. 二次校验同步(类锁)
这种方法是比较有名的,十分高效
public static Singleton getInstance() {
if(instance == null) { ①
synchronized(Singleton.class) {
if(instance ==null) ②
instance = new Singleton();
}
}
return instance;
}
具体流程如下,Thread1和Thread2都通过①进入了条件语句,由于Thread1拿到了锁,所以Thread2需要等待锁,此时Thread1实例化了instance,然后释放锁。接着Thread2拿到锁进入②,判断此时是有实例的,所以什么也不做获取实例
其他方法都不常用,就不再这里赘述