单例_单例模式 - CSDN
精华内容
参与话题
  • JAVA设计模式之单例模式

    万次阅读 多人点赞 2014-04-16 06:51:34
    本文继续介绍23种设计模式系列之单例模式。 概念:  java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍三种:懒汉式单例、饿汉式单例、登记式单例。  单例模式有以下特点:  1、单例类...

    本文继续介绍23种设计模式系列之单例模式。

    概念:
      java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍三种:懒汉式单例、饿汉式单例、登记式单例。
      单例模式有以下特点:
      1、单例类只能有一个实例。
      2、单例类必须自己创建自己的唯一实例。
      3、单例类必须给所有其他对象提供这一实例。
      单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。


    一、懒汉式单例

     

    //懒汉式单例类.在第一次调用的时候实例化自己 
    public class Singleton {
        private Singleton() {}
        private static Singleton single=null;
        //静态工厂方法 
        public static Singleton getInstance() {
             if (single == null) {  
                 single = new Singleton();
             }  
            return single;
        }
    }

     

    Singleton通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。

    (事实上,通过Java反射机制是能够实例化构造方法为private的类的,那基本上会使所有的Java单例实现失效。此问题在此处不做讨论,姑且掩耳盗铃地认为反射机制不存在。)

    但是以上懒汉式单例的实现没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton实例,要实现线程安全,有以下三种方式,都是对getInstance这个方法改造,保证了懒汉式单例的线程安全,如果你第一次接触单例模式,对线程安全不是很了解,可以先跳过下面这三小条,去看饿汉式单例,等看完后面再回头考虑线程安全的问题:

     

    1、在getInstance方法上加同步

     

    public static synchronized Singleton getInstance() {
             if (single == null) {  
                 single = new Singleton();
             }  
            return single;
    }

     

     

     

    2、双重检查锁定

     

    public static Singleton getInstance() {
            if (singleton == null) {  
                synchronized (Singleton.class) {  
                   if (singleton == null) {  
                      singleton = new Singleton(); 
                   }  
                }  
            }  
            return singleton; 
        }

     

    3、静态内部类

     

    public class Singleton {  
        private static class LazyHolder {  
           private static final Singleton INSTANCE = new Singleton();  
        }  
        private Singleton (){}  
        public static final Singleton getInstance() {  
           return LazyHolder.INSTANCE;  
        }  
    }  

    这种比上面1、2都好一些,既实现了线程安全,又避免了同步带来的性能影响。

     

     

     

     

     

     

    二、饿汉式单例

     

    //饿汉式单例类.在类初始化时,已经自行实例化 
    public class Singleton1 {
        private Singleton1() {}
        private static final Singleton1 single = new Singleton1();
        //静态工厂方法 
        public static Singleton1 getInstance() {
            return single;
        }
    }

    饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。

     

     

     

     

    三、登记式单例(可忽略)

    //类似Spring里面的方法,将类名注册,下次从里面直接获取。
    public class Singleton3 {
        private static Map<String,Singleton3> map = new HashMap<String,Singleton3>();
        static{
            Singleton3 single = new Singleton3();
            map.put(single.getClass().getName(), single);
        }
        //保护的默认构造子
        protected Singleton3(){}
        //静态工厂方法,返还此类惟一的实例
        public static Singleton3 getInstance(String name) {
            if(name == null) {
                name = Singleton3.class.getName();
                System.out.println("name == null"+"--->name="+name);
            }
            if(map.get(name) == null) {
                try {
                    map.put(name, (Singleton3) Class.forName(name).newInstance());
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
            return map.get(name);
        }
        //一个示意性的商业方法
        public String about() {    
            return "Hello, I am RegSingleton.";    
        }    
        public static void main(String[] args) {
            Singleton3 single3 = Singleton3.getInstance(null);
            System.out.println(single3.about());
        }
    }

     登记式单例实际上维护了一组单例类的实例,将这些实例存放在一个Map(登记薄)中,对于已经登记过的实例,则从Map直接返回,对于没有登记的,则先登记,然后返回。 

    这里我对登记式单例标记了可忽略,我的理解来说,首先它用的比较少,另外其实内部实现还是用的饿汉式单例,因为其中的static方法块,它的单例在类被装载的时候就被实例化了。

     

    饿汉式和懒汉式区别

    从名字上来说,饿汉和懒汉,

    饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,

    而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。

    另外从以下两点再区分以下这两种方式:

     

    1、线程安全:

    饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,

    懒汉式本身是非线程安全的,为了实现线程安全有几种写法,分别是上面的1、2、3,这三种实现在资源加载和性能方面有些区别。


     

    2、资源加载和性能:

    饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成,

    而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。

    至于1、2、3这三种实现又有些区别,

    第1种,在方法调用上加了同步,虽然线程安全了,但是每次都要同步,会影响性能,毕竟99%的情况下是不需要同步的,

    第2种,在getInstance中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗

    第3种,利用了classloader的机制来保证初始化instance时只有一个线程,所以也是线程安全的,同时没有性能损耗,所以一般我倾向于使用这一种。

     

    什么是线程安全?

    如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

    或者说:一个类或者程序所提供的接口对于线程来说是原子操作,或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题,那就是线程安全的。

     

    应用

    以下是一个单例类使用的例子,以懒汉式为例,这里为了保证线程安全,使用了双重检查锁定的方式:

     

    public class TestSingleton {
    	String name = null;
    
            private TestSingleton() {
    	}
    
    	private static volatile TestSingleton instance = null;
    
    	public static TestSingleton getInstance() {
               if (instance == null) {  
                 synchronized (TestSingleton.class) {  
                    if (instance == null) {  
                       instance = new TestSingleton(); 
                    }  
                 }  
               } 
               return instance;
    	}
    
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    
    	public void printInfo() {
    		System.out.println("the name is " + name);
    	}
    
    }

    可以看到里面加了volatile关键字来声明单例对象,既然synchronized已经起到了多线程下原子性、有序性、可见性的作用,为什么还要加volatile呢,原因已经在下面评论中提到,

    还有疑问可参考http://www.iteye.com/topic/652440
    和http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

     

     

     

    public class TMain {
    	public static void main(String[] args){
    		TestStream ts1 = TestSingleton.getInstance();
    		ts1.setName("jason");
    		TestStream ts2 = TestSingleton.getInstance();
    		ts2.setName("0539");
    		
    		ts1.printInfo();
    		ts2.printInfo();
    		
    		if(ts1 == ts2){
    			System.out.println("创建的是同一个实例");
    		}else{
    			System.out.println("创建的不是同一个实例");
    		}
    	}
    }
    

     运行结果:

    结论:由结果可以得知单例模式为一个面向对象的应用程序提供了对象惟一的访问点,不管它实现何种功能,整个应用程序都会同享一个实例对象。

    对于单例模式的几种实现方式,知道饿汉式和懒汉式的区别,线程安全,资源加载的时机,还有懒汉式为了实现线程安全的3种方式的细微差别。

    更多设计模式:23种设计模式系列

    作者:jason0539

    博客:http://blog.csdn.net/jason0539(转载请说明出处)

    展开全文
  • 单例模式6种实现方式

    千次阅读 2018-06-04 17:21:36
    一:饿汉式public class MyObject { // 立即加载方式==饿汉模式 private static MyObject myObject = new MyObject(); private MyObject() { } public static MyObject getInstance() { ...

    一:饿汉式

    public class MyObject {
    
    	// 立即加载方式==饿汉模式
    	private static MyObject myObject = new MyObject();
    
    	private MyObject() {
    	}
    
    	public static MyObject getInstance() {
    		// 此代码版本为立即加载
    		// 此版本代码的缺点是不能有其它实例变量
    		// 因为getInstance()方法没有同步
    		// 所以有可能出现非线程安全问题
    		return myObject;
    	}
    }
    public class MyThread extends Thread {
    
    	@Override
    	public void run() {
    		System.out.println(MyObject.getInstance().hashCode());
    	}
    }
    public static void main(String[] args) {
    		MyThread t1 = new MyThread();
    		MyThread t2 = new MyThread();
    		MyThread t3 = new MyThread();
    
    		t1.start();
    		t2.start();
    		t3.start();
    
    	}

    多线程获取单例,打印的hashcode是同一个值,说明对象是同一个 

    二:懒汉式(1)

    public class MyObject {
    
    	private static MyObject myObject;
    
    	private MyObject() {
    	}
    
    	// 设置同步方法效率太低了
    	// 整个方法被上锁
    	synchronized public static MyObject getInstance() {
    		try {
    			if (myObject != null) {
    			} else {
    				// 模拟在创建对象之前做一些准备性的工作
    				Thread.sleep(3000);
    				myObject = new MyObject();
    			}
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		return myObject;
    	}
    
    }
    public class MyThread extends Thread {
    
    	@Override
    	public void run() {
    		System.out.println(MyObject.getInstance().hashCode());
    	}
    
    }
    public static void main(String[] args) {
    		MyThread t1 = new MyThread();
    		MyThread t2 = new MyThread();
    		MyThread t3 = new MyThread();
    
    		t1.start();
    		t2.start();
    		t3.start();
    
    	}


    二:懒汉式(2)


    public class MyThread extends Thread {
    
    	@Override
    	public void run() {
    		System.out.println(MyObject.getInstance().hashCode());
    	}
    
    }
    public class MyObject {
    
    	private volatile static MyObject myObject;
    
    	private MyObject() {
    	}
    
    	// 使用双检测机制来解决问题
    	// 即保证了不需要同步代码的异步
    	// 又保证了单例的效果
    	public static MyObject getInstance() {
    		try {
    			if (myObject != null) {
    			} else {
    				// 模拟在创建对象之前做一些准备性的工作
    				Thread.sleep(3000);
    				synchronized (MyObject.class) {
    					if (myObject == null) {
    						myObject = new MyObject();
    					}
    				}
    			}
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		return myObject;
    	}
    	// 此版本的代码称为:
    	// 双重检查Double-Check Locking
    
    }
    public static void main(String[] args) {
    		
    		
    		MyThread t1 = new MyThread();
    		MyThread t2 = new MyThread();
    		MyThread t3 = new MyThread();
    
    		t1.start();
    		t2.start();
    		t3.start();
    
    	}

    三:使用静态内置类实现单例模式

    public class MyObject {
    
    	// 内部类方式
    	private static class MyObjectHandler {
    		private static MyObject myObject = new MyObject();
    	}
    
    	private MyObject() {
    	}
    
    	public static MyObject getInstance() {
    		return MyObjectHandler.myObject;
    	}
    
    }
    public class MyThread extends Thread {
    
    	@Override
    	public void run() {
    		System.out.println(MyObject.getInstance().hashCode());
    	}
    
    }
    public static void main(String[] args) {
    		MyThread t1 = new MyThread();
    		MyThread t2 = new MyThread();
    		MyThread t3 = new MyThread();
    
    		t1.start();
    		t2.start();
    		t3.start();
    
    	}

    四:使用静态代码块实现单例模式

    public class MyObject {
    
    	private static MyObject instance = null;
    
    	private MyObject() {
    	}
    
    	static {
    		instance = new MyObject();
    	}
    
    	public static MyObject getInstance() {
    		return instance;
    	}
    
    }
    public class MyThread extends Thread {
    
    	@Override
    	public void run() {
    		for (int i = 0; i < 5; i++) {
    			System.out.println(MyObject.getInstance().hashCode());
    		}
    	}
    }
    public static void main(String[] args) {
    		MyThread t1 = new MyThread();
    		MyThread t2 = new MyThread();
    		MyThread t3 = new MyThread();
    
    		t1.start();
    		t2.start();
    		t3.start();
    
    	}

    五:使用枚举实现单例模式

    public enum MyObject {
    	connectionFactory;
    
    	private Connection connection;
    
    	private MyObject() {
    		try {
    			System.out.println("调用了MyObject的构造");
    			String url = "jdbc:sqlserver://localhost:1079;databaseName=ghydb";
    			String username = "sa";
    			String password = "";
    			String driverName = "com.microsoft.sqlserver.jdbc.SQLServerDriver";
    			Class.forName(driverName);
    			connection = DriverManager.getConnection(url, username, password);
    		} catch (ClassNotFoundException e) {
    			e.printStackTrace();
    		} catch (SQLException e) {
    			e.printStackTrace();
    		}
    	}
    
    	public Connection getConnection() {
    		return connection;
    	}
    }
    public class MyThread extends Thread {
    
    	@Override
    	public void run() {
    		for (int i = 0; i < 5; i++) {
    			System.out.println(MyObject.connectionFactory.getConnection()
    					.hashCode());
    		}
    	}
    }
    public static void main(String[] args) {
    		MyThread t1 = new MyThread();
    		MyThread t2 = new MyThread();
    		MyThread t3 = new MyThread();
    
    		t1.start();
    		t2.start();
    		t3.start();
    
    	}


    六:序列化与反序列化的单例模式实现

    public class MyObject implements Serializable {
    
    	private static final long serialVersionUID = 888L;
    
    	// 内部类方式
    	private static class MyObjectHandler {
    		private static final MyObject myObject = new MyObject();
    	}
    
    	private MyObject() {
    	}
    
    	public static MyObject getInstance() {
    		return MyObjectHandler.myObject;
    	}
    
    	protected Object readResolve() throws ObjectStreamException {
    		System.out.println("调用了readResolve方法!");
    		return MyObjectHandler.myObject;
    	}
    
    }
    public class SaveAndRead {
    
    	public static void main(String[] args) {
    		try {
    			MyObject myObject = MyObject.getInstance();
    			
    			FileOutputStream fosRef = new FileOutputStream(new File(
    					"myObjectFile.txt"));
    			ObjectOutputStream oosRef = new ObjectOutputStream(fosRef);
    			oosRef.writeObject(myObject);
    			oosRef.close();
    			fosRef.close();
    			System.out.println(myObject.hashCode());
    		} catch (FileNotFoundException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		} catch (IOException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    
    		try {
    			FileInputStream fisRef = new FileInputStream(new File(
    					"myObjectFile.txt"));
    			ObjectInputStream iosRef = new ObjectInputStream(fisRef);
    			MyObject myObject = (MyObject) iosRef.readObject();
    			iosRef.close();
    			fisRef.close();
    			System.out.println(myObject.hashCode());
    		} catch (FileNotFoundException e) {
    			e.printStackTrace();
    		} catch (IOException e) {
    			e.printStackTrace();
    		} catch (ClassNotFoundException e) {
    			e.printStackTrace();
    		}
    
    	}
    
    }




    展开全文
  • 设计模式之单例

    2018-04-07 15:30:48
    单例 单例模式的使用场景 举一个小例子,在我们的windows桌面上,我们打开了一个回收站,当我们试图再次打开一个新的回收站时,Windows系统并不会为你弹出一个新的回收站窗口。,也就是说在整个系统运行的过程中...

    单例

    单例模式的使用场景

    举一个小例子,在我们的windows桌面上,我们打开了一个回收站,当我们试图再次打开一个新的回收站时,Windows系统并不会为你弹出一个新的回收站窗口。,也就是说在整个系统运行的过程中,系统只维护一个回收站的实例。这就是一个典型的单例模式运用。

    继续说回收站,我们在实际使用中并不存在需要同时打开两个回收站窗口的必要性。假如我每次创建回收站时都需要消耗大量的资源,而每个回收站之间资源是共享的,那么在没有必要多次重复创建该实例的情况下,创建了多个实例,这样做就会给系统造成不必要的负担,造成资源浪费。

    再举一个例子,网站的计数器,一般也是采用单例模式实现,如果你存在多个计数器,每一个用户的访问都刷新计数器的值,这样的话你的实计数的值是难以同步的。但是如果采用单例模式实现就不会存在这样的问题,而且还可以避免线程安全问题。同样多线程的线程池的设计一般也是采用单例模式,这是由于线程池需要方便对池中的线程进行控制

    同样,对于一些应用程序的日志应用,或者web开发中读取配置文件都适合使用单例模式,如HttpApplication 就是单例的典型应用。

    从上述的例子中我们可以总结出适合使用单例模式的场景和优缺点:

    场景

    1.封装一些常用的工具类,保证整个应用常用的数据统一
    2.方便资源相互通信的环境,保存一些共享数据在内存中,其他类随时可以读取。
    3.需要生成唯一序列的环境

    优点:

    1、提供了对唯一实例的受控访问。
    2、由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
    3、允许可变数目的实例。

    缺点:

    1、由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
    2、单例类的职责过重,在一定程度上违背了“单一职责原则”。
    3、滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。

    展开全文
  • 深入理解单例模式——只有一个实例

    万次阅读 多人点赞 2018-05-25 19:32:06
    初遇设计模式在上个寒假,当时把每个设计模式过了一遍,对设计模式有了一个最初级的了解。这个学期借了几本设计模式的书籍看,听了老师的设计模式课,对设计模式算是有个更进一步的认识。后面可能会不定期更新一下...

    我自己总结的Java学习的系统知识点以及面试问题,已经开源,目前已经 35k+ Star。会一直完善下去,欢迎建议和指导,同时也欢迎Star: https://github.com/Snailclimb/Java-Guide

    前言

    初遇设计模式在上个寒假,当时把每个设计模式过了一遍,对设计模式有了一个最初级的了解。这个学期借了几本设计模式的书籍看,听了老师的设计模式课,对设计模式算是有个更进一步的认识。后面可能会不定期更新一下自己对于设计模式的理解。每个设计模式看似很简单,实则想要在一个完整的系统中应用还是非常非常难的。然后我的水品也非常非常有限,代码量也不是很多,只能通过阅读书籍、思考别人的编码经验以及结合自己的编码过程中遇到的问题来总结。

    怎么用->怎么用才好->怎么与其他模式结合使用,我想这是每个开发人员都需要逾越的一道鸿沟。

    一 单例模式简介

    1.1 定义

    保证一个类仅有一个实例,并提供一个访问它的全局访问点。

    1.2 为什么要用单例模式呢?

    在我们的系统中,有一些对象其实我们只需要一个,比如说:线程池、缓存、对话框、注册表、日志对象、充当打印机、显卡等设备驱动程序的对象。事实上,这一类对象只能有一个实例,如果制造出多个实例就可能会导致一些问题的产生,比如:程序的行为异常、资源使用过量、或者不一致性的结果。

    简单来说使用单例模式可以带来下面几个好处:

    • 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销;
    • 由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间。

    1.3 为什么不使用全局变量确保一个类只有一个实例呢?

    我们知道全局变量分为静态变量和实例变量,静态变量也可以保证该类的实例只存在一个。
    只要程序加载了类的字节码,不用创建任何实例对象,静态变量就会被分配空间,静态变量就可以被使用了。

    但是,如果说这个对象非常消耗资源,而且程序某次的执行中一直没用,这样就造成了资源的浪费。利用单例模式的话,我们就可以实现在需要使用时才创建对象,这样就避免了不必要的资源浪费。 不仅仅是因为这个原因,在程序中我们要尽量避免全局变量的使用,大量使用全局变量给程序的调试、维护等带来困难。

    二 单例的模式的实现

    通常单例模式在Java语言中,有两种构建方式:

    • 饿汉方式。指全局的单例实例在类装载时构建
    • 懒汉方式。指全局的单例实例在第一次被使用时构建。

    不管是那种创建方式,它们通常都存在下面几点相似处:

    • 单例类必须要有一个 private 访问级别的构造函数,只有这样,才能确保单例不会在系统中的其他代码内被实例化;
    • instance 成员变量和 uniqueInstance 方法必须是 static 的。

    2.1 饿汉方式(线程安全)

        public class Singleton {
           //在静态初始化器中创建单例实例,这段代码保证了线程安全
            private static Singleton uniqueInstance = new Singleton();
            //Singleton类只有一个构造方法并且是被private修饰的,所以用户无法通过new方法创建该对象实例
            private Singleton(){}
            public static Singleton getInstance(){
                return uniqueInstance;
            }
        }
    

    所谓 “饿汉方式” 就是说JVM在加载这个类时就马上创建此唯一的单例实例,不管你用不用,先创建了再说,如果一直没有被使用,便浪费了空间,典型的空间换时间,每次调用的时候,就不需要再判断,节省了运行时间。

    2.2 懒汉式(非线程安全和synchronized关键字线程安全版本 )

    public class Singleton {  
          private static Singleton uniqueInstance;  
          private Singleton (){
          }   
          //没有加入synchronized关键字的版本是线程不安全的
          public static Singleton getInstance() {
              //判断当前单例是否已经存在,若存在则返回,不存在则再建立单例
    	      if (uniqueInstance == null) {  
    	          uniqueInstance = new Singleton();  
    	      }  
    	      return uniqueInstance;  
          }  
     }
    

    所谓 “ 懒汉式” 就是说单例实例在第一次被使用时构建,而不是在JVM在加载这个类时就马上创建此唯一的单例实例。

    但是上面这种方式很明显是线程不安全的,如果多个线程同时访问getInstance()方法时就会出现问题。如果想要保证线程安全,一种比较常见的方式就是在getInstance() 方法前加上synchronized关键字,如下:

          public static synchronized Singleton getInstance() {  
    	      if (instance == null) {  
    	          uniqueInstance = new Singleton();  
    	      }  
    	      return uniqueInstance;  
          }  
    

    我们知道synchronized关键字偏重量级锁。虽然在JavaSE1.6之后synchronized关键字进行了主要包括:为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁以及其它各种优化之后执行效率有了显著提升。

    但是在程序中每次使用getInstance() 都要经过synchronized加锁这一层,这难免会增加getInstance()的方法的时间消费,而且还可能会发生阻塞。我们下面介绍到的 双重检查加锁版本 就是为了解决这个问题而存在的。

    2.3 懒汉式(双重检查加锁版本)

    利用双重检查加锁(double-checked locking),首先检查是否实例已经创建,如果尚未创建,“才”进行同步。这样以来,只有一次同步,这正是我们想要的效果。

    public class Singleton {
    
        //volatile保证,当uniqueInstance变量被初始化成Singleton实例时,多个线程可以正确处理uniqueInstance变量
        private volatile static Singleton uniqueInstance;
        private Singleton() {
        }
        public static Singleton getInstance() {
           //检查实例,如果不存在,就进入同步代码块
            if (uniqueInstance == null) {
                //只有第一次才彻底执行这里的代码
                synchronized(Singleton.class) {
                   //进入同步代码块后,再检查一次,如果仍是null,才创建实例
                    if (uniqueInstance == null) {
                        uniqueInstance = new Singleton();
                    }
                }
            }
            return uniqueInstance;
        }
    }
    

    很明显,这种方式相比于使用synchronized关键字的方法,可以大大减少getInstance() 的时间消费。

    我们上面使用到了volatile关键字来保证数据的可见性,关于volatile关键字的内容可以看我的这篇文章:
    《Java多线程学习(三)volatile关键字》: https://blog.csdn.net/qq_34337272/article/details/79680771

    注意: 双重检查加锁版本不适用于1.4及更早版本的Java。
    1.4及更早版本的Java中,许多JVM对于volatile关键字的实现会导致双重检查加锁的失效。

    2.4 懒汉式(登记式/静态内部类方式)

    静态内部实现的单例是懒加载的且线程安全。

    只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance(只有第一次使用这个单例的实例的时候才加载,同时不会有线程安全问题)。

    public class Singleton {  
        private static class SingletonHolder {  
        private static final Singleton INSTANCE = new Singleton();  
        }  
        private Singleton (){}  
        public static final Singleton getInstance() {  
        return SingletonHolder.INSTANCE;  
        }  
    }   
    

    2.5 饿汉式(枚举方式)

    这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。 它更简洁,自动支持序列化机制,绝对防止多次实例化 (如果单例类实现了Serializable接口,默认情况下每次反序列化总会创建一个新的实例对象,关于单例与序列化的问题可以查看这一篇文章《单例与序列化的那些事儿》),同时这种方式也是《Effective Java 》以及《Java与模式》的作者推荐的方式。

    public enum Singleton {
    	 //定义一个枚举的元素,它就是 Singleton 的一个实例
        INSTANCE;  
        
        public void doSomeThing() {  
    	     System.out.println("枚举方法实现单例");
        }  
    }
    

    使用方法:

    public class ESTest {
    
    	public static void main(String[] args) {
    		Singleton singleton = Singleton.INSTANCE;
    		singleton.doSomeThing();//output:枚举方法实现单例
    
    	}
    
    }
    

    《Effective Java 中文版 第二版》

    这种方法在功能上与公有域方法相近,但是它更加简洁,无偿提供了序列化机制,绝对防止多次实例化,即使是在面对复杂序列化或者反射攻击的时候。虽然这种方法还没有广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。 —-《Effective Java 中文版 第二版》

    《Java与模式》

    《Java与模式》中,作者这样写道,使用枚举来实现单实例控制会更加简洁,而且无偿地提供了序列化机制,并由JVM从根本上提供保障,绝对防止多次实例化,是更简洁、高效、安全的实现单例的方式。

    2.6 总结

    我们主要介绍到了以下几种方式实现单例模式:

    • 饿汉方式(线程安全)
    • 懒汉式(非线程安全和synchronized关键字线程安全版本)
    • 懒汉式(双重检查加锁版本)
    • 懒汉式(登记式/静态内部类方式)
    • 饿汉式(枚举方式)

    参考:

    《Head First 设计模式》

    《Effective Java 中文版 第二版》

    【Java】设计模式:深入理解单例模式

    公众号

    如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。

    《Java面试突击》: 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本公众号后台回复 “Java面试突击” 即可免费领取!

    Java工程师必备学习资源: 一些Java工程师常用学习资源公众号后台回复关键字 “1” 即可免费无套路获取。

    我的公众号

    展开全文
  • 1、什么是单例呢?顾名思义单例就是单个实例,单个对象的意思。 2、单例设计模式的功能:可以保证一个类只生成一个类的实例,也就是说在整个程序空间里只存在一个该类的实例。 一、案例引申 1、栗子 如上图:首先...
  • 单例模式实例(全)

    千次阅读 2018-07-14 09:57:45
    什么是单例模式? 定义:确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例场景,也就是说:确保某个类有且只有一个对象的场景,避免产生多个对象消耗过多的资源,或者某种类型的对象应该有且只有一...
  • 单例模式和多例的区别

    千次阅读 2018-11-21 21:47:16
    设计者模式-单例模式和多例的区别 单例模式的关键有两点: 1、构造方法为私有,这样外界就不能随意调用。 2、get的方法为静态,由类直接调用 多例模式(Multiton) 1 、多例类可以有多个实例 2 、多例类必须能够自我...
  • 单例

    2016-02-21 00:25:40
    // // singleton.h // singleton // // Created by zmx on 16/2/20. // Copyright © 2016年 zmx. All rights reserved. // #define singleton_h(name) + (instancetype)shared##...
  • java单例模式

    万次阅读 多人点赞 2018-05-25 14:59:26
     本文首先概述了单例模式产生动机,揭示了单例模式的本质和应用场景。紧接着,我们给出了单例模式在单线程环境下的两种经典实现:饿汉式和懒汉式,但是饿汉式是线程安全的,而懒汉式是非线程安全的。在多线程环境下...
  • 单例对象

    千次阅读 2018-10-25 09:57:16
    Java实现单例的5种方式 1. 什么是单例模式 单例模式指的是在应用整个生命周期内只能存在一个实例。单例模式是一种被广泛使用的设计模式。他有很多好处,能够避免实例对象的重复创建,减少创建实例的系统开销,节省...
  • 单例模式

    万次阅读 2018-12-25 16:49:30
    单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。 这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不...
  • 单例模式 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保...
  • Java实现单例模式的9种方法

    万次阅读 多人点赞 2019-01-14 15:01:04
    因进程需要,有时我们只需要某个类同时保留一个对象,不希望有更多对象,此时,我们则应考虑单例模式的设计。 二. 单例模式的特点 1、单例模式只能有一个实例。 2、单例类必须创建自己的唯一实例。 3、单例类...
  • 单例模式的使用总结

    万次阅读 2020-04-21 11:15:59
    一、单例模式的定义和应用场景 (一)定义及基本要点 (二)应用场景 二、饿汉式单例模式 (一)基本代码展示分析 (二)基本分析和建议 三、懒汉式单例模式(双重检查锁) (一)基本代码展示分析 (二)...
  • 单例模式,参考完整代码在GitHub 地址:https://github.com/zhang-xiaoxiang/patter23 或者https://github.com/zhang-xiaoxiang/DesignPatterns23 一般三步走,1初始化2构造器私有3提供获取实例的方法 1单例模式---...
  • Spring中Bean的单例和多例

    万次阅读 2019-03-25 16:47:09
    在Spring中,bean可以被定义为两种模式:prototype(多例)和singleton(单例) singleton(单例):只有一个共享的实例存在,所有对这个bean的请求都会返回这个唯一的实例。 prototype(多例):对这个bean的每次...
  • 深入理解单例模式:静态内部类单例原理

    万次阅读 多人点赞 2018-05-26 02:00:54
    本文主要介绍java的单例模式,以及详细剖析静态内部类之所以能够实现单例的原理。OK,废话不多说,进入正文。 首先我们要先了解下单例的四大原则: 1.构造私有。 2.以静态方法或者枚举返回实例。 3.确保实例只有...
  • Java实现单例的5种方式

    万次阅读 多人点赞 2018-04-01 00:07:35
    Java实现单例的5种方式 1. 什么是单例模式 单例模式指的是在应用整个生命周期内只能存在一个实例。单例模式是一种被广泛使用的设计模式。他有很多好处,能够避免实例对象的重复创建,减少创建实例的系统开销,...
  • 单例模式讨论篇:单例模式与垃圾回收

    万次阅读 多人点赞 2012-03-08 09:14:35
    Jvm的垃圾回收机制到底会不会回收掉长时间不用的单例模式对象,这的确是一个比较有争议性的问题。将这一部分内容单独成篇的目的也是为了与广大博友广泛的讨论一下这个问题。为了能让更多的人看到这篇文章,请各位...
  • java 单例模式的几种实现方式

    万次阅读 多人点赞 2018-03-27 12:56:29
    1.懒汉式 public class Singleton { //2.本类内部创建对象实例 private static Singleton instance = null; /** * 1.构造方法私有化,外部不能new */ ...//3.... public static Single...
1 2 3 4 5 ... 20
收藏数 341,972
精华内容 136,788
关键字:

单例