
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
单例的实现主要是通过以下三个步骤:
1、将类的构造方法定义为私有方法。这样其他类的代码就无法通过调用该类的构造方法来实例化该类的对象,只能通过该类提供的静态方法来得到该类的唯一实例。
2、定义一个私有的类的静态实例。
3、提供一个公有的获取实例的静态方法。
单例追求的目标
1、线程安全。
2、懒加载。
3、调用效率高。
1、饿汉式单例
// 饿汉式单例(实际常用)
public class Singleton1 {
// 指向自己实例的私有静态引用,主动创建
private static Singleton1 singleton1 = new Singleton1();
// 私有的构造方法
private Singleton1() {
}
// 以自己实例为返回值的静态的公有方法,静态工厂方法
public static Singleton1 getSingleton1() {
return singleton1;
}
}
在上述单例类被加载时,就会实例化一个对象并交给自己的引用,供系统使用;而且,由于这个类在整个生命周期中只会被加载一次,因此只会创建一个实例,即能够充分保证单例。
优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
2、懒汉式单例-(无锁机制)
// 懒汉式单例
public class Singleton2 {
// 指向自己实例的私有静态引用
private static Singleton2 singleton2;
// 私有的构造方法
private Singleton2() {
}
// 以自己实例为返回值的静态的公有方法,静态工厂方法
public static Singleton2 getSingleton2() {
// 被动创建,在真正需要使用时才去创建
if (singleton2 == null) {
singleton2 = new Singleton2();
}
return singleton2;
}
}
我们从懒汉式单例可以看到,单例实例被延迟加载,即只有在真正使用的时候才会实例化一个对象并交给自己的引用。
这种写法起到了Lazy Loading的效果,但是只能在单线程下使用。如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。
3、懒汉式单例-(单锁机制)
public class Singleton3 {
//构造函数私有化
private Singleton3(){}
private static Singleton3 singleton = null;
//构建实例化方法
public static synchronized Singleton3 getSingleton(){
if(singleton == null){
singleton = new Singleton3();
}
return singleton;
}
}
优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
4、懒汉式单例-(双重加锁机制)
//双重加锁机制
public class Singleton {
//声明一个私有的静态变量
private static Singleton instance;
//构造器私有化避免外部创建类的对象
private Singleton() {
}
//创建一个公有的方法,该方法用于创建对象,如果对象存在就不创建,不存在就创建
public static Singleton getInstance(long time) {
if (null == instance) {//提高效率的 双重检查
synchronized (Singleton.class) {
if (null == instance) {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Singleton();
}
}
}
return instance;
}
}
代码中所示,我们进行了两次if (singleton == null)检查,这样就可以保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),直接return实例化对象。
使用双重检测同步延迟加载去创建单例的做法是一个非常优秀的做法,其不但保证了单例,而且切实提高了程序运行效率
优点:线程安全;延迟加载;效率较高。
5、静态(类级)内部类单例
public class Singleton4 {
private Singleton4(){};
private static class Singleton4Holder{
//静态初始化器,由JVM来保证线程安全
private static Singleton4 singleton4 = new Singleton4();
}
public static Singleton4 getSingleton4(){
return Singleton4Holder.singleton4;
}
}
6、单例和枚举
public enum Singleton5 {
SINGLETON5;//定义枚举类型
private String name;
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
public static void main(String[] args) {
Singleton5 singleton1 = Singleton5.SINGLETON5;//枚举类型实例化(静态块中)
singleton1.setName("单例1");
System.out.println(singleton1.getName());
Singleton5 singleton2 = Singleton5.SINGLETON5;
singleton2.setName("单例2");
System.out.println(singleton2.getName());
System.out.println(singleton1.getName() + ":" + singleton2.getName());
}
}
几种方式对比: