设计模式有23种,其中比较简单的就是单例模式,单例模式属于创造型模式,这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象,我们要获取这个类对象的时候直接用这个提供的方式就可以了。
注意:
1. 单例类只能有一个实例。
2. 单例类必须自己创建自己的唯一实例。
3. 单例类必须给所有其他对象提供这一实例。
代码如下:
首先我们创建一个单例类。
public class Penson {
//创建自己的唯一实例
private static Penson penson = new Penson();
//构造方法私有化,确保类不会被实例化
private Penson(){};
//创建获取唯一可用对象的方法
public Penson getPenson() {
return penson;
}
public void run(){
System.out.println("penson is running!");
}
}
然后从Penson类获取唯一的对象
public class SinglePensonDemo {
public static void main(String[] args){
//只能通过Penson类提供的getPenson()方法来获取Penson对象,不能通过new关键字来创建对象
Penson penson = Penson.getPenson();
penson.run();
}
}
结果:
penson is running!
我们可以看到在设计单例类的时候有三个比较关键的步骤,第一就是创建自己的唯一实例,第二就是构造方法私有化,第三就是提供获取实例的方法。
对于单例模式的实现有几种方法,下面来简单介绍一下:
1.懒汉式,线程不安全
这种方式很容易实现,所谓懒汉式就是我需要实例没有时才创建一个实例给你,而不是像刚才的代码一开始就在类里创建好了自己的实例,由于没有加锁 synchronized,所以严格意义上这个不算单例模式,代码如下:
public class Penson {
private static Penson penson;
//构造方法私有化,确保类不会被实例化
private Penson(){};
//创建获取唯一可用对象的方法
public static Penson getPenson() {
if(penson==null){
penson = new Penson();
}
return penson;
}
public void run(){
System.out.println("penson is running!");
}
}
2.懒汉式,线程安全
该方式与第一种相比就是线程安全了,通过加锁 synchronized来保证线程安全,但是效率比较低,代码如下:
public class Penson {
//创建自己的唯一实例
private static Penson penson;
//构造方法私有化,确保类不会被实例化
private Penson(){};
//创建获取唯一可用对象的方法
public static synchronized Penson getPenson() {
if(penson==null){
penson = new Penson();
}
return penson;
}
public void run(){
System.out.println("penson is running!");
}
}
这里顺便说一下synchronized这个关键字,可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,
同一时刻最多只有一个线程执行这个段代码。当两个并发线程访问同一个对象object中的这个加锁同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。然而,当一个线程访问object的一个加锁代码块时,另一个线程仍然可以访问该object中的非加锁代码块。
3.饿汉式
对于最开始写的代码就是饿汉式的,在这里就不重复显示代码了,所谓饿汉式就是不管有没有需要,在单例类里就创建了一个实例。这种方式比较常用,但容易产生垃圾对象。优点是没有加锁,执行效率会提高。
缺点是类加载时就初始化,浪费内存。
4.双检锁/双重校验锁
这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
代码如下:
public class Penson {
private volatile static Penson penson;
//构造方法私有化,确保类不会被实例化
private Penson(){};
//创建获取唯一可用对象的方法
public static synchronized Penson getPenson() {
if(penson==null){
synchronized (Penson.class) {
if(penson==null){
penson = new Penson();
}
}
}
return penson;
}
public void run(){
System.out.println("penson is running!");
}
}
5.登记式/静态内部类
public class Penson {
private static class PensonHolder {
private static final Penson PENSON = new Penson();
}
private Penson() {
}
public static final Penson getPenson() {
return PensonHolder.PENSON;
}
public void run() {
System.out.println("penson is running!");
}
}
要注意我这里的方式跟第三种相比,不会在类加载的时候就实例化penson。
6.枚举
class Resource {
}
public enum Pensons {
INSTANCE;
private Resource instance;
Pensons() {
instance = new Resource();
}
public Resource getInstance() {
return instance;
}
}
在网上找到的经验之谈说:一般情况下,不建议使用第 1 种和第 2 种懒汉方式,建议使用第 3 种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 5 种登记方式。如果涉及到反序列化创建对象时,可以尝试使用第 6 种枚举方式。如果有其他特殊的需求,可以考虑使用第 4 种双检锁方式。至于实际用到的时候,以后再详细说。