Java实现单例模式的两种方式
单例模式在实际开发中有很多的用途,比如我们在项目中常用的工具类,数据库等资源的连接类。这样做的好处是避免创建多个对象,占用内存资源,自始自终在内存中只有一个对象为我们服务。
单例对象一般有两种实现方式。懒汉式和饿汉式。
饿汉式实现如下:
package com.day05; /** * 饿汉式 * * @author Administrator * */ public class Single { // 定义一个个私有静态本类对象变量,饿汉式是一上来就给该变量进行初始化,加上final是让s这个对象引用始终保持不变,更为严谨 private static final Single s = new Single(); // 构造方法私有化,让外部无法通过new 关键字来进行创建对象 private Single() { } // 暴露出一个外界可以获取该类对象的公共静态方法 public static Single getInstance() { return s; } }
测试类:
package com.day05; public class SingleDemo { public static void main(String[] args) { Single s1 = Single.getInstance(); Single s2 = Single.getInstance(); System.out.println(s1==s2); } }
运行结果:
true
即s1==s2说明了,s1和s2在内存中地址都相等,即s1、和s2是同一个对象。
懒汉式实现如下:
package com.day05; /** * 懒汉式 * * @author Administrator * */ public class Single { // 定义一个个私有静态本类对象变量,懒汉式是先赋值为null,当需要的时候在初始化 private static Single s = null; // 构造方法私有化,让外部无法通过new 关键字来进行创建对象 private Single() { } // 暴露出一个外界可以获取该类对象的公共静态方法 public static Single getInstance() { if (s == null) s = new Single(); return s; } }
以上的代码如果是单线程的话就不会存在问题,但是当有多线程操作的时候,就会存在线程安全问题,演示代码如下:
package com.day05; /** * 懒汉式 * * @author Administrator * */ public class Single { // 定义一个个私有静态本类对象变量,懒汉式是先赋值为null,当需要的时候在初始化 private static Single s = null; // 构造方法私有化,让外部无法通过new 关键字来进行创建对象 private Single() { } // 暴露出一个外界可以获取该类对象的公共静态方法 public static Single getInstance() { if (s == null) { try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } s = new Single(); } return s; } }
测试类如下:
package com.day05; public class SingleDemo implements Runnable { public static void main(String[] args) { SingleDemo sd = new SingleDemo(); new Thread(sd).start(); new Thread(sd).start(); } @Override public void run() { Single s = Single.getInstance(); System.out.println(s); } }
运行结果如下:
com.day05.Single@4081b5a4
com.day05.Single@64dcdaac
可以发现每次运行结果打印出获取对象不是同一个,即存在线程安全问题。
问题分析:
由此我们可以采用Java给我们提供的同步锁来解决以上的问题,修改代码如下:
package com.day05; /** * 懒汉式 * * @author Administrator * */ public class Single { // 定义一个个私有静态本类对象变量,懒汉式是先赋值为null,当需要的时候在初始化 private static Single s = null; // 构造方法私有化,让外部无法通过new 关键字来进行创建对象 private Single() { } // 暴露出一个外界可以获取该类对象的公共静态方法 public static synchronized Single getInstance() { if (s == null) { try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } s = new Single(); } return s; } }
这样就解决了上面的代码存在的线程安全问题,但是同步锁虽然可以解决了线程安全问题,但是却会存在效率问题,所以我们可以采用双重判断的方法来优化一下改代码如下所示:
package com.day05; /** * 懒汉式 * * @author Administrator * */ public class Single { // 定义一个个私有静态本类对象变量,懒汉式是先赋值为null,当需要的时候在初始化 private static Single s = null; // 构造方法私有化,让外部无法通过new 关键字来进行创建对象 private Single() { } // 暴露出一个外界可以获取该类对象的公共静态方法 // 这里需要注意的使静态共享数据使用的使该类的字节码对象即Single.class public static Single getInstance() { // 这里增加了一次判断,可以少一次进行锁的处理 if (s == null) { synchronized (Single.class) { if (s == null) { try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } s = new Single(); } } } return s; } }
总结:还是比较推荐使用饿汉式,因为写法简单,不存在线程安全问题。