Singleton 单例模式(单态模式)
单例模式的原理:
单例模式的类中有且仅有一个实例被创建,其他的类要使用单例对象时都要通过这个类提供的特殊渠道来进行获取。
- 如果不想有那么多的实例,构造方法私有化。
- 提供一种方式来获取该实例,且保证实例只有一个
单例模式的优点:
• 单例模式可以保证内存里只有一个实例,减少了内存的开销。
• 可以避免对资源的多重占用。
• 单例模式设置全局访问点,可以优化和共享资源的访问。
单例模式的缺点:
• 单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则。
• 在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象。
• 单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。
Adapter 适配器 Proxy 代理模式
单例模式的七种形式:
1、饿汉式
饿汉式是单例模式设计中比较经典的实现方式。实现代码如下:
package com.Singlenten.pojo;
//饿汗单例
public class Singleten1 {
private static final Singleten1 s = new Singleten1();
private Singleten1() {
}
public static Singleten1 getInstance(){
return s;
}
public static void Println() {
System.out.println("这是饿汉式!");
}
}
懒汉式
所谓懒汉式就是在使用类实例的时候再去创建,也就是说用到的时候我再创建,这样就可以避免类在初始化的时候提前创建过早地占用内存空间。实现代码如下:
package com.Singlenten.pojo;
//懒汉单例
public class Singleten {
private static Singleten s = null;
private Singleten() {
}
public static Singleten getInstance(){
if(s == null) {
s=new Singleten();
}
return s;
}
public static void Println() {
System.out.println("这是懒汉式!");
}
}
懒汉式+同步方法
懒汉式的单例实现方式可以保证实例的懒加载,但是却无法保证实例的唯一性。在多线程环境下由于instance为共享数据,当多个线程访问使用时,需要保证数据的同步性,所以如果需要保证懒汉式实例的唯一性,我们可以通过同步的方式来实现。代码如下:
package com.Singlenten.pojo;
public final class SingletenSync {
private static SingletenSync s = null;
private SingletenSync() {
}
public static synchronized SingletenSync getInstance(){
if(s == null) {
s=new SingletenSync();
}
return s;
}
public static void Println() {
System.out.println("这是懒汉式+同步方法!");
}
}
采用懒汉式+数据同步的方法既满足了懒加载又能够100%保证instance实例的唯一性。但是,synchronized关键字的排它性会导致getInstance()方法同一时刻只能被一个线程访问,性能会比较低下。
Double Check
Double-Check是一种比较聪明的设计方式,它提供了一种高效的数据同步策略,那就是首次初始化的时候加锁,之后则允许多个线程同时进行getInstance()方法的调用来获得类的实例。代码如下:
package com.Singlenten.pojo;
import java.net.Socket;
import java.sql.Connection;
//final 不允许被继承
public final class SingletenDoubleCheck {
private static SingletenDoubleCheck s = null;//定义实例,但是不直接初始化
Connection con;
Socket socket;
private SingletenDoubleCheck(Connection con,Socket socket) {
this.con = con;
this.socket = socket;
}
public static SingletenDoubleCheck getInstance(){
if(null == s ) {
synchronized(SingletenDoubleCheck.class){
if(null == s) {
s=new SingletenDoubleCheck(null, null);
}
}
}
return s;
}
public static void Println() {
System.out.println("这是Double Check!");
}
}
当两个线程发现nullinstance成立时,只有一个线程有资格进入同步代码块,完成对instance的初始化,随后的线程发现nullinstance不成立则无须进行任何操作,以后对getInstance的访问就不会再需要进行数据同步了。
此种方式看起来是既满足了懒加载,又保证了instance实例的唯一性,并且还提供了比较高效的数据同步策略,可以允许多个线程同时对getInstance进行访问。但是这种方式在多线程的情况下,可能会引起空指针异常,这是因为如果在如上代码的构造方法中还存在初始化其他资源的情况的话,由于JVM运行时存在指令重排的情况,这些资源在实例化时顺序并无前后关系的约束,那么在这种情况下,就极有可能是instance最先被实例化,而con和socket并未完成实例化,而未完成实例化的实例在调用其方法时将会抛出空指针异常。
volatile+双重锁
为了解决Double-Check指令重排导致的空指针问题,可以用volatile关键字防止这种重排序的发生。因此代码只需要稍作修改就能满足多线程下的单例、懒加载以及实例的高效性了。代码如下:
package com.Singlenten.pojo;
import java.net.Socket;
import java.sql.Connection;
//final 不允许被继承
public final class SingletenVolatileDoubleCheck {
private static volatile SingletenVolatileDoubleCheck s = null;//定义实例,但是不直接初始化
Connection con;
Socket socket;
private SingletenVolatileDoubleCheck(Connection con,Socket socket) {
this.con = con;
this.socket = socket;
}
public static SingletenVolatileDoubleCheck getInstance(){
if(null == s ) {
synchronized(SingletenVolatileDoubleCheck.class){
if(null == s) {
s=new SingletenVolatileDoubleCheck(null, null);
}
}
}
return s;
}
public static void Println() {
System.out.println("这是Volatile+双重锁!");
}
}
Holder方式
Holder方式完全借助了类加载的特点。代码如下:
package com.Singlenten.pojo;
public final class SingletenHolder {
private SingletenHolder() {
}
private static class Holder{
private static SingletenHolder s = new SingletenHolder();
}
public static SingletenHolder getInstance() {
return Holder.s;
}
public static void Println() {
System.out.println("这是Holder方式!");
}
}
在单例类中并没有instance的静态成员,而是将其放到了静态内部类Holder之中,因此单例类在初始化的过程中并不会创建SingletonHolder的实例,内部类Holder中定义了SingletonHolder的静态变量,并且直接进行了实例化,只有当Holder被主动引用的时候才会创建SingletonHolder的实例。
SingletonHolder实例的创建过程在Java程序编译时期收集至()方法中,该方法又是同步方法,可以保证内存的可见性、JVM指令的顺序性和原子性。Holder方式的单例模式设计是最好的设计之一,也是目前使用比较广的设计。
枚举方式
枚举方式在很多开源框架中也应用得比较广泛,枚举类型不允许被继承,同样是线程安全的,并且只能被实例化一次,但是枚举类型不能够实现懒加载。用枚举类型,实现单例模式的代码如下:
package com.Singlenten.pojo;
public class SingletenEnum {
private SingletenEnum() {
}
private enum EnumHolder {
INSTANCE;
private SingletenEnum instance;
EnumHolder(){
this.instance = new SingletenEnum();
}
private SingletenEnum getInstance() {
return instance;
}
}
public static SingletenEnum getInstance() {
return EnumHolder.INSTANCE.getInstance();
}
public static void Println() {
System.out.println("这是枚举方式!");
}
}