设计模式之——单例模式
引言
设计模式分为三种类型:
1)创建者模式:单例模式、抽象工厂模式、原型模式、建造者模式、工厂模式
2)结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式
3)行为型模式:模板方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式(责任链模式)
一、单例模式
所谓的单例模式,就是采取一定的方法保证在整个软件系统中,对于某个类只能存在一个实例对象,并且该类只对外提供一个获取该对象的方法(静态方法)
单例模式有七种方式:
1)饿汉式(静态常量)
2)饿汉式(静态代码块)
3)懒汉式(线程不安全)
4)懒汉式(线程安全,同步方法)
5)双重检查
6)静态内部类
7)枚举
1)饿汉式(静态常量)【主】
public class singleton1 { public static void main(String[] args) { Singleton singleton1 = Singleton.getInstance(); Singleton singleton2 = Singleton.getInstance(); System.out.println(singleton1 == singleton2); } } class Singleton{ private Singleton(){}//构造器私有化 private final static Singleton singleton= new Singleton(); public static Singleton getInstance() { return singleton; } }
这种方式在类初始化时就会完成 “单例对象” 的实例化,避免了线程安全问题。但同时这也是他的缺点,假如此类还有其他静态方法和属性,可能调用方调用的并不是getInstance方法,并不会使用此 “单例对象”,但是调用了类的其他静态方法非final成员势必导致类的初始化,而一旦类初始化,就会初始化此 “单例对象”,这会造成一定的空间浪费,没有达到懒加载的效果。
2)饿汉式(静态代码块)【次】
public class singleton2 { public static void main(String[] args) { Singleton singleton1 = Singleton.getInstance(); Singleton singleton2 = Singleton.getInstance(); System.out.println(singleton1 == singleton2); } } class Singleton{ private Singleton(){} private final static Singleton singleton;
//final修饰的变量可以在静态代码块、代码块和构造器中赋值 static { singleton = new Singleton(); } public static Singleton getInstance() { return singleton; } }
3)懒汉式(线程不安全)【次】
public class singleton1 {
public static void main(String[] args) {
for(int i=0;i<20;i++) {
new Thread(() -> {
Singleton singleton2 = Singleton.getInstance();
System.out.println(singleton2);
},"Thread"+i).start();
}
}
}
class Singleton{
private Singleton(){}
private static Singleton singleton;
public static Singleton getInstance() {
if(singleton == null)
return singleton = new Singleton();
return singleton;
}
}
这样的方式是线程不安全的,假设现在有两个线程A、B,线程A在进入 if(singleton == null)后并没有来得及创建对象,CPU就将资源转给了线程B,因为线程A此前并没有将类成员singleton实例化,所以线程B进入了if后创建了对象并返回,此时A线程拿到资源后加载上下文继续创建了对象。由此可知在多线程的情况下这种方式并不安全。所以在实际开发中不要使用这种方式。
com.qlu.singleton.Singleton@10d49a2
com.qlu.singleton.Singleton@10d49a2
com.qlu.singleton.Singleton@1fae4985
com.qlu.singleton.Singleton@10d49a2
.
.
.
4)懒汉式(线程安全,同步方法)【主】
public class singleton1 { public static void main(String[] args) { for(int i=0;i<20;i++) { new Thread(() -> { Singleton singleton2 = Singleton.getInstance(); System.out.println(singleton2); },"Thread"+i).start(); } } } class Singleton{ private Singleton(){} private static Singleton singleton; public static synchronized Singleton getInstance() {//加锁 while(singleton == null) return singleton = new Singleton(); return singleton; } }
但是这样效率比较低,不推荐使用。
5)双重检查
public class singleton1 { public static void main(String[] args) { for(int i=0;i<20;i++) { new Thread(() -> { Singleton singleton2 = Singleton.getInstance(); System.out.println(singleton2); },"Thread"+i).start(); } } } class Singleton{ private Singleton(){} private static volatile Singleton singleton; public static synchronized Singleton getInstance() { if(singleton == null) { synchronized (Singleton.class) { if(singleton == null) { return singleton = new Singleton(); } } } return singleton; } }
这种方式是线程安全的,假设现在有两个线程A、B,线程A在进入 if(singleton == null)后并没有来得及创建对象,CPU就将资源转给了线程B,因为线程A此前并没有将类成员singleton实例化,所以线程B进入了if后创建了对象并返回,此时A线程拿到资源后加载上下文开始if判断,由于B已经创建对象,所以并不能执行if里面的代码。由此可知在多线程的情况下这种方式是安全的,所以在实际开发中推荐使用这种方式。
6)静态内部类【推荐使用】
public class singleton1 { public static void main(String[] args) { for(int i=0;i<20;i++) { new Thread(() -> { Singleton singleton2 = Singleton.getInstance(); System.out.println(singleton2); },"Thread"+i).start(); } } } class Singleton{ private Singleton(){} private static class SingletonInstance{ private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return SingletonInstance.INSTANCE; } }
7)枚举
public class singleton1 { public static void main(String[] args) { for(int i=0;i<20;i++) { new Thread(() -> { Singleton singleton2 = Singleton.INSTANCE; singleton2.say(); },"Thread"+i).start(); } } } enum Singleton{ INSTANCE; public void say() { System.out.println("hello"); } }
二、JDK中使用的单例模式
饿汉式的RunTime:
三、单例模式的应用场景
1)单例模式的应用变量为什么要倍设计成static修饰呢?类本身多外暴露的接口时static的
2)单例模式保证了系统内存(方法区)中该类只要一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统的性能,对于一些经常使用到的 “重量级” 的对象、工具类对象、频繁访问数据库或文件系统的对象,使用单例模式也是很好的选择。