单例设计模式是Java中最常见也是最简单的一种设计模式,这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。
单例模式主要是为了避免因为创建了多个实例造成资源的浪费,且多个实例由于多次调用容易导致结果出现错误,而使用单例模式能够保证整个应用中有且只有一个实例。
它可以解决的问题是:可以保证一个类在内存中的对象的唯一性,在一些常用的工具类、线程池、缓存,数据库,账户登录系统、配置文件等程序中可能只允许我们创建一个对象,一方面如果创建多个对象可能引起程序的错误,另一方面创建多个对象也造成资源的浪费。
单例模式的关键就是保证对象的唯一性:
(1)不允许其他程序用new对象
因为new就是开辟新的空间,在这里更改数据只是更改的所创建的对象的数据,如果可以new的话,每一次new都产生一个对象,这样肯定保证不了对象的唯一性。
(2)在该类中创建对象
因为不允许其他程序new对象,所以这里的对象需要在本类中new出来
(3)对外提供一个可以让其他程序获取该对象的方法
因为对象是在本类中创建的,所以需要提供一个方法让其它的类获取这个对象。
将以上三步转化为程序语言就是:
(1)私有化该类的构造函数
(2)通过new在本类中创建一个本类对象
(3)定义一个公有的方法,将在该类中所创建的对象返回
单例模式的几种实现方式:1.饿汉式 2.懒汉式 3.双重校验锁 4.静态内部类 5.枚举
1.饿汉式【可用】
public class SingleDemo{
private static SingleDemo instance=new SingleDemo();
private SingleDemo(){};
public static SingleDemo getInstance(){
return instance;
}
}
访问方式:
SingleDemo instance=SingleDemo.getInstance();
优缺点:没有加锁,执行效率较高;类加载的时候就完成了实例化,避免了线程同步问题,但是可能会用不到这个实例,造成内存的浪费。
2.懒汉式【线程不安全,不可用】
public class SingleDemo{
private static SingleDemo instance=null;
private SingleDemo(){};
public static SingleDemo getInstance(){
if(instance==null){
instance=new SingleDemo();
}
return instance;
}
}
为什么会存在线程安全问题呢?在运行过程中可能会存在这样一种情况:有多个线程去调用getInstance方法来获取SingleDemo的实例,那么就有可能发生这样一种情况当第一个线程在执行if(instance==null)这个语句时,此时instance是为null的进入语句。在还没有执行instance=new SingleDemo()时(此时instance是为null的)第二个线程也进入if(instance==null)这个语句,因为之前进入这个语句的线程中还没有执行instance=new SingleDemo(),所以它会执行instance=new SingleDemo()来实例化SingleDemo对象,因为第二个线程也进入了if语句所以它也会实例化SingleDemo对象。这样就导致了实例化了两个SingleDemo对象。所以单例模式的懒汉式是存在线程安全问题的。
懒汉式改进1:【线程安全,效率低,不推荐使用】
public class SingleDemo{
private static SingleDemo instance=null;
private SingleDemo(){};
public static synchronized SingleDemo getInstance(){
if(instance==null){
instance=new SingleDemo();
}
return instance;
}
}
每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。
懒汉式改进2:【线程不安全,不可用】
public class SingleDemo{
private static SingleDemo instance=null;
private SingleDemo(){};
public static SingleDemo getInstance(){
if(instance==null){
synchronized(SingleDemo.class){
instance=new SingleDemo();
}
}
return instance;
}
}
当一个线程还没有实例化SingleDemo时另一个线程执行到if(instance==null)这个判断语句时就会进入if语句,虽然加了锁,但是等到第一个线程执行完instance=new SingleDemo()跳出这个锁时,另一个进入if语句的线程同样会实例化另外一个SingleDemo对象,线程不安全的原理跟懒汉式类似!
3.单例模式懒汉式双重校验锁【推荐用】
public class SingleDemo {
/**
* 懒汉式变种,属于懒汉式中最好的写法,保证了:延迟加载和线程安全
*/
private static SingleDemo instance=null;
private SingleDemo() {};
public static SingleDemo getInstance(){
if (instance == null) { //双重判断,减少锁判断次数
synchronized (SingleDemo.class) {
if (instance == null) {
instance = new SingleDemo();
}
}
}
return instance;
}
}
访问方式
SingleDemo instance=SingleDemo.getInstance();
优缺点:线程安全,延迟加载,效率较高
4.静态内部类【推荐】
public class SingleDemo {
private SingleDemo() {};
private static class Inner{
private static SingleDemo instance=new SingleDemo();
}
public static SingleDemo getInstance() {
return Inner.instance;
}
}
访问方式:
SingleDemo instance=SingleDemo.getInstance();
优缺点:线程安全,延迟加载,效率高。
饿汉式只要是SingleDemo类被装载就是实例化,没有lazy-loading,静态内部类在被装载时并不会立即实例化,调用getInstance方法,才会装载Inner类,从而完成SingleDemo的实例化。
类的静态属性只会在第一次加载类的时候初始化,jvm保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
5.枚举【极推荐】
public enum SingleDemo {
instance;
private SingleDemo() {};
public void method() {
}
}
访问方式
SingleDemo.instance.method();
优缺点:避免了多线程同步问题;同时防止反序列化重新创建新的对象。
如有错误,欢迎指正。