单例模式就是一个类只有一个实例存在,即唯一实例:
当创建一个对象需要消耗很多资源的时候,就可以使用单例模式来减少资源的消耗
单例模式可以分为两种:饿汉式,懒汉式
先了解饿汉式再了解懒汉式这样的学习顺序比较好,因为饿汉式直接返回对象比较简单
懒汉式就是不愿意创建实例对象,只有等getInstance()方法被调用的时候才创建对象,并返回
饿汉式就是老早就创建好了实例对象,调用getInstance()方法的时候,不用再创建,直接返回
总之:直接返回就是饿汉式,懒汉式则需要先创建,再返回
注意:下面举的例子构造方法都是private,这样就不能通过A a=new A()的方式进行实例化,而是必须通过A a=A.getInstance()的方式进行实例化
懒汉式存在的场景:有些对象创建的开销是比较大的,你如果一开始就创建出来,后面又没有使用,那很浪费
懒汉式的一种经典实现:双重检查锁
class A
{
//属性是这个类的对象
//而且这个属性必须是私有的
//这里必须使用volatile关键字,保证原子性
private volatile static A a;
//构造方法必须是私有的
private A()
{
}
//创建一个方法来获取这个对象
//加锁来防止被多次实例化
public synchronized static A getInstance()
{
//第一次使用的时候进行实例化,即第一次调用getInstance方法的时候进行实例化
if(a==null)
{
synchronized(A.class)
{
if(a==null)//第二次判断a对象是否为空
{
a=new A();
}
}
}
return a;
}
}
为什么要双重校验:
假设不双重校验:
public synchronized static A getInstance()
{
//第一次使用的时候进行实例化,即第一次调用getInstance方法的时候进行实例化
if(a==null)
{
synchronized(A.class)
{
a=new A();
}
}
return a;
}
假设两个线程一起调用了getInstance()方法,并且都发现singleton这个Singleton对象没有被创建出来,然后第一个线程获取了锁,创建了一个Singleton对象singleton,创建完这个对象之后就释放了锁,第二个线程拿到锁之后,会再创建一个Singleton对象,这样就创建了两个对象
所以,需要在拿到锁之后,再判断一下,这个A对象是否已经被创建出来了,如果被创建出来了,就不用再创建
单例模式的问题:全局共享一个变量,相当于全局变量,程序到处都可以更改
常见面试题:手写单例模式
由于在常用的设计模式中,单例模式是唯一能用短短几十行代码就能完整实现的模式,因此手写一个单例模式是一道很常见的面试题(多线程下的单例模式)
饿汉式:
class SinglePattern
{
static SinglePattern singlePattern=new SinglePattern();
private SinglePattern()
{
}
public static SinglePattern getInstanceOf()
{
return singlePattern;
}
}
public class test
{
public static void main(String[] args){
SinglePattern singlePattern=SinglePattern.getInstanceOf();
System.out.println(singlePattern);
}
}
懒汉式:
class SinglePattern
{
static SinglePattern singlePattern;
private SinglePattern()
{
}
public static SinglePattern getInstanceOf()
{
if(singlePattern==null)
{
singlePattern=new SinglePattern();
return singlePattern;
}
}
}
public class test
{
public static void main(String[] args){
SinglePattern singlePattern=SinglePattern.getInstanceOf();
System.out.println(singlePattern);
}
}
懒汉式不是线程安全的,因为,有可能两个线程都执行完if(singlePattern==null),发现没有创建对象,于是都创建对象,这样就没有达到单例的效果
如果在getInstanceOf方法上加synchronized,变成:
public static synchronized SinglePattern getInstanceOf()
{
if(singlePattern==null)
{
singlePattern=new SinglePattern();
return singlePattern;
}
}
确实可以保证只有一个线程进入getInstanceOf方法,但是又会出现新的问题:我们只是想创建对象的时候保证单线程进入,获取对象的时候其实是允许多线程同时获得这个对象的,所以这种做法是不可取的
加锁双重校验:
public static synchronized SinglePattern getInstanceOf()
{
if(singlePattern==null)
{
synchronized(SinglePattern.class)
{
if(singleton==null)
{
singlePattern=new SinglePattern();
}
}
}
return singlePattern;
}
两次判断对象是否被创建出来,拿到锁之后再判断一次对象是否被创建出来了,没有被创建才需要去创建