设计模式(2)——单例模式


单例模式

定义:单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局都可以该实例的访问方法。

注意

  1. 单例类只能有一个实例。
  2. 单例类必须自己创建自己的唯一实例。
  3. 单例类必须给所有其他对象提供这一实例。

优点

  • 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例
  • 避免对资源的多重占用

缺点

  • 无抽象层,扩展难
  • 单例类职责过重,不符合单一职责的设计原则。即充当着工厂角色,提供了工厂方法。又充当了产品角色,包含了一些业务方法。将产品的创建和产品自身的功能融为一体。

适用:业务要求控制实例数目,节省系统资源的时候。此时判断系统是否已经有这个单例,如果有则返回,如果没有则创建


单例模式简单实例

说明:两次调用getInstance()获取的对象为统一对象,构造器为private外部无法实例化对象。

实现注意

  • 单例类的构造器为私有 Singleton1(){}
  • 提供一个自身的静态私有成员变量 instance
  • 提供一个公有的静态工厂方法 getInstance()

在这里插入图片描述
代码

Singleton1

public class Singleton1 {

    //多线程访问安全
    private static Singleton1 instance=null;
	//私有构造器保证了实例的唯一,外部无法进行实例化
    private Singleton1(){}
    //从而保证了每次返回的对象都是唯一的一个
    public static Singleton1 getInstance()
    {
        if(instance==null)
        {
            instance=new Singleton1();
        }
        return instance;
    }
}
public class Test1 {
    public static void main(String[] args) {
        Singleton1 s1=Singleton1.getInstance();
        Singleton1 s2=Singleton1.getInstance();
        //结果为true
        System.out.println(s1==s2); 
    }
}


饿汉式单例类

类加载时立即被实例化,在线程还没有的时候就创建了单例对象,保证了线程的安全,不存在多线程访问的安全问题。

优点:没有加任何锁,执行效率高。
缺点:类加载的时候已经实例化,占空间,费内存。不管该实例用于不用都存在

举例说明

饿汉Singleton1

public class Singleton1 {
    //类加载过程中就有了实例对象 因此多线程访问安全
    //2种实例均可 new Singleton1() 和 static
    private static final Singleton1 singleton1;//=new Singleton1();
    static
    {
        singleton1=new Singleton1();
    }
    private Singleton1(){}
    public static Singleton1 getInstance()
    {
        return singleton1;
    }
}

测试Client

public class Client extends Thread{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+" "+Singleton1.getInstance());
    }
    public static void main(String[] args) {
        Thread t1=new Client();
        t1.start();

        Thread t2=new Client();
        t2.start();
        //结果
        //Thread-0 com.baiwenxuan.Singleton.hungry.Singleton1@c10f5b9
		//Thread-1 com.baiwenxuan.Singleton.hungry.Singleton1@c10f5b9
    }
}

以上两个线程同时获取实例,所得到的运行结果成功说明了饿汉式单例类是线程安全的。


懒汉式单例类

类加载时不会实例化,只有被外部调用的时候,内部类才会加载。多线程情况下,必须对getInstance方法进行加锁操作。使得方法变成线程同步,一个处于Running,其他处于Monitor监控状态。

优点:用时创建,释放了不必要占据的空间
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。

举例说明
a.不加锁多线程实例

LazySingle

public class LazySingle {
    private static  LazySingle lazysingle=null;
    private LazySingle(){}
    public static LazySingle getInstance()
    {
        if(lazysingle==null)
        {
            lazysingle=new LazySingle();
        }
        return lazysingle;
    }
}

测试

public class ExectorThread implements Runnable {
    @Override
    public  void  run() {
        LazySingle lazySingle=LazySingle.getInstance();
        System.out.println(Thread.currentThread().getName()+" "+lazySingle);
    }
}
public class Test {
    public static void main(String[] args) {
        //不加锁的话 结果不一样
        new Thread(new ExectorThread()).start();
        new Thread(new ExectorThread()).start();
        new Thread(new ExectorThread()).start();
        new Thread(new ExectorThread()).start();
        /*结果如下
        *Thread-1 com.baiwenxuan.Singleton.lazy.LazySingle@2e36b8b3
		Thread-0 com.baiwenxuan.Singleton.lazy.LazySingle@7aa3aa20
		Thread-2 com.baiwenxuan.Singleton.lazy.LazySingle@7aa3aa20
		Thread-3 com.baiwenxuan.Singleton.lazy.LazySingle@2e36b8b3
        */
    }
}

该不加锁懒汉单例类,在多线程不能正常工作。

b.加锁多线程实例

LazySingle

public class LazySingle {
    private static  LazySingle lazysingle=null;
    private LazySingle(){}
    //加锁 synchronized
    public synchronized static LazySingle getInstance()
    {
        if(lazysingle==null)
        {
            lazysingle=new LazySingle();
        }
        return lazysingle;
    }
}

测试

public class Test {
    public static void main(String[] args) {
        //不加锁的话 结果不一样
        for(int i=0;i<100;i++)
        new Thread(new ExectorThread()).start();
        //结果
        //所有线程获得的对象实例一致
    }
}

加锁可以有效使得多线程访问更加安全。


双重检查锁单例模式

这种方式采用volatile 、synchronized 双锁机制,安全且在多线程情况下能保持高性能。

synchronized 在创建实例时,进行加锁。因为如果锁住方法,虽然解决了对象重复创建的问题,但是在高并发下,频繁调用getInstance()方法都需要加锁,导致了性能的下降。

volatile 对变量使用volatile,可以保证各个线程对某变量的即时可见性。jvm中多线程的内存模型是每个线程有自己的工作缓存(即主存的一个副本),工作前从内存读取,工作完成后需要把工作缓存中更新过的值刷新到内存中。

//双重检查锁懒汉单例
public class LazySingle1 {
    private  volatile  static  LazySingle1 lazysingle1=null;
    private LazySingle1(){}
    public static LazySingle1 getInstance()
    {
        if(lazysingle1==null)
        {
            synchronized(LazySingle1.class)
            {
                if(lazysingle1==null)
                {
                    lazysingle1=new LazySingle1();
                }
            }

        }
        return lazysingle1;
    }
}

说明
可能同一时间多个线程一起通过了第1次代码 if(lazysingle1==null) 的判断,但同一时间只有一个线程获得锁后进入临界区,进入临界区后还要再判断一次单例类是否已被其它线程实例化,以避免多次实例化。由于双重加锁实现仅在实例化单例类时需要加锁,多线程情况下能保持高性能。


静态内部类单例模式

这种方式同样利用了 classloader 机制来保证初始化 LIZY时只有一个线程,能达到双检锁一样的功效,但实现更简单。对静态域使用延迟初始化。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。

这种方式是 HungrySingle2 类被装载了,LIZY不一定被初始化。因为 HungryHolder类没有被主动使用,只有通过显式调用 getInstance() 方法时,才会显式装载 HungryHolder类,从而实例化 LIZY。

public class HungrySingle2 {

    /*静态内部类单例模式:这种方式能达到双检锁方式一样的功效,但实现更简单。解决了饿汉的内存浪费问题,
    相当于synchronized,提升了性能。内部类是在线程访问前初始化,避免了线程不安全的问题。*/
    private HungrySingle2(){}
    public static HungrySingle2 getInstance()
    {
        return HungryHolder.LIZY;
    }

    private static class HungryHolder
    {
        private static final HungrySingle2 LIZY=new HungrySingle2();
    }
}

枚举类单例模式

序列化:把对象转换为字节序列的过程称为对象的序列化。
反序列化:把字节序列恢复为对象的过程称为对象的反序列化。

对象的序列化主要有两种用途:

  • 硬盘存储和读取,把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
  • 网络通信发送和接收,在网络上传送对象的字节序列。

注意:序列化和反序列化会导致单例模式失败

举例说明

EnumSingleTest

public class EnumSingleTest {
    public static void main(String[] args) {
        try{
            //过程序列化
            HungrySingle3 instance1=null;
            HungrySingle3 instance2=HungrySingle3.getInstance();
            //instance2.setData(new Object());
            //把对象写入流
            FileOutputStream fos=new FileOutputStream("HungrySingle3.obj");
            ObjectOutputStream oos=new ObjectOutputStream(fos);
            oos.writeObject(instance2);
            oos.flush();
            oos.close();

            //读出对象
            FileInputStream fis=new FileInputStream("HungrySingle3.obj");
            ObjectInputStream ois=new ObjectInputStream(fis);
            HungrySingle3 instance3=(HungrySingle3) ois.readObject();
            ois.close();
            System.out.println(instance1);
            System.out.println(instance2);
            System.out.println(instance3);
            System.out.println(instance3==instance2);
			
			/*得到结果如下
			null
			com.baiwenxuan.Singleton.reg.HungrySingle3@5cad8086
			com.baiwenxuan.Singleton.reg.HungrySingle3@6acbcfc0
			false
			*/

        }catch(Exception e){
            e.printStackTrace();
        }
    }
}
/**
 * 饿汉单例模式类
 * 显示序列化和反序列化对单例模式的影响
 */
public class HungrySingle3 implements Serializable {
    private static HungrySingle3 instance=new HungrySingle3();
    private HungrySingle3(){}
    public static HungrySingle3 getInstance()
    {
        return instance;
    }
}

解决方案:使用枚举类实现单例模式类
优点:不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。

代码:

public class Test {
    public static void main(String[] args) {
        try{
            EnumSingleton instance1=null;
            EnumSingleton instance2=EnumSingleton.getInstance();
            instance2.setData(new Object());
            //把对象写入流
            FileOutputStream fos=new FileOutputStream("EnumSingleton.obj");
            ObjectOutputStream oos=new ObjectOutputStream(fos);
            oos.writeObject(instance2);
            oos.flush();
            oos.close();
            //读出对象
            FileInputStream fis=new FileInputStream("EnumSingleton.obj");
            ObjectInputStream ois=new ObjectInputStream(fis);
            instance1=(EnumSingleton) ois.readObject();
            ois.close();
            System.out.println(instance1.getData());
            System.out.println(instance2.getData());
            System.out.println(instance1);
            System.out.println(instance2);
            System.out.println(instance1.getData()==instance2.getData());
			/*运行结果如下
			java.lang.Object@5f184fc6
			java.lang.Object@5f184fc6
			INSTANCE
			INSTANCE
			true
			*/
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

/*
枚举式单例模式:在静态代码中给INSTANCE进行了赋值,是饿汉单例模式的实现。
枚举类是通过类名和类对象找到一个唯一的枚举对象。
而不是通过类加载多次加载枚举对象的,因此解决了序列化破坏单例模式问题。
 */
public enum EnumSingleton {
    INSTANCE;
    private Object data;

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }
    public static EnumSingleton getInstance()
    {
        return INSTANCE;
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值