1、概念
(1)单例模式
采用某种方法保证在整个软件设计中,某一个类只存在一个对象实例,并且该类只提供一个取得其对象的方法。Hibernate的SessionFactory不是轻量级的,采用的是单例模式,通常一个项目只有一个
(2)实现方式
饿汉式(静态常量)、饿汉式(静态代码块)、懒汉式(线程不安全)、懒汉式(线程安全、同步方法)、懒汉式(线程安全、同步代码块)、双重检查、静态内部类、枚举
- 饿汉式可以使用,但是要保证实例一定会被使用,否则会造成内存的浪费
- 推荐使用:双重检查、静态内部类、枚举三种
- 懒汉式要注意线程安全的问题
(3)使用场景
单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建和销毁的对象,可以使用单例模式来提高系统的性能。当需要实例化一个单例类的时候,需要使用的是获取对象的方法,而不是采用new的方式
- 需要进行频繁的创建和销毁的对象
- 创建对象时耗时过多或消耗资源过多但又经常需要使用的对象
- 工具类对象
- 频繁访问数据库或文件的对象(如:数据源、session工厂等)
2、饿汉模式
(1)静态常量方式
public class Mgr01 { private static final Mgr01 INSTANCE = new Mgr01(); private Mgr01() {}; public static Mgr01 getInstance() { return INSTANCE; } public static void main(String[] args) { Mgr01 m1 = Mgr01.getInstance(); Mgr01 m2 = Mgr01.getInstance(); System.out.println(m1 == m2); } }
true
优点:写法比较简单,类加载到内存后就实例化一个对象,避免了线程同步的问题
缺点:没有达到懒加载的效果,如果对象未得到使用就会造成内存的浪费
(2)静态代码块方式
public class Mgr02 { private static final Mgr02 INSTANCE; static { INSTANCE = new Mgr02(); } private Mgr02() {}; public static Mgr02 getInstance() { return INSTANCE; } public void m() { System.out.println("m"); } public static void main(String[] args) { Mgr02 m1 = Mgr02.getInstance(); Mgr02 m2 = Mgr02.getInstance(); System.out.println(m1 == m2); } }
只是类实例化的代码放到了静态代码块中,优缺点和静态常量方式一样
(3)懒汉式(线程不安全)
public class Mgr03 { private static Mgr03 INSTANCE; private Mgr03() { } public static Mgr03 getInstance() { if (INSTANCE == null) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } INSTANCE = new Mgr03(); } return INSTANCE; } public void m() { System.out.println("m"); } public static void main(String[] args) { for(int i=0; i<100; i++) { new Thread(()-> System.out.println(Mgr03.getInstance().hashCode()) ).start(); } } }
1850443050 1052444169 2040686731 1919392881 1887032973 1887032973 145632894 145632894 1219201818 1413709757 1413709757 1413709757 1413709757 1413709757
刚开始多个线程都走到了判断对象是否为空的语句,便会产生多个对象,也就是说此种方式是线程不安全的
(4)懒汉式(线程安全、同步方法)
public class Mgr04 { private static Mgr04 INSTANCE; private Mgr04() { } public static synchronized Mgr04 getInstance() { if (INSTANCE == null) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } INSTANCE = new Mgr04(); } return INSTANCE; } public void m() { System.out.println("m"); } public static void main(String[] args) { for(int i=0; i<100; i++) { new Thread(()->{ System.out.println(Mgr04.getInstance().hashCode()); }).start(); } } }
将获取实例的方法换成同步方法,但是效率会下降,且该方法执行一次就够了,后面直接返回对象
(5)减小同步区域,但是不可行
public class Mgr05 { private static Mgr05 INSTANCE; private Mgr05() { } public static Mgr05 getInstance() { if (INSTANCE == null) { synchronized (Mgr05.class) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } INSTANCE = new Mgr05(); } } return INSTANCE; } public static void main(String[] args) { for(int i=0; i<100; i++) { new Thread(()->{ System.out.println(Mgr05.getInstance().hashCode()); }).start(); } } }
不再采用同步方法的方式来提高代码的效率,但是会带来线程安全的问题,因为在多线程的情况下依旧会有多个线程执行到非空判断语句
(6)双重检查
public class Mgr06 { private static volatile Mgr06 INSTANCE; private Mgr06() { } public static Mgr06 getInstance() { if (INSTANCE == null) { //双重检查 synchronized (Mgr06.class) { if(INSTANCE == null) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } INSTANCE = new Mgr06(); } } } return INSTANCE; } public void m() { System.out.println("m"); } public static void main(String[] args) { for(int i=0; i<100; i++) { new Thread(()->{ System.out.println(Mgr06.getInstance().hashCode()); }).start(); } } }
即使有多个线程跨过了非空判断,但是只有第一个线程能跨过第二层的非空判断,因为volatile修饰后的变量修改后能够立即同步回主内存,第二个线程执行的时候已经不为空
优点:懒加载,效率较高、线程安全、只创建一次实例
(7)静态内部类
public class Mgr07 { private Mgr07() { } private static class Mgr07Holder { private final static Mgr07 INSTANCE = new Mgr07(); } public static Mgr07 getInstance() { return Mgr07Holder.INSTANCE; }
public static void main(String[] args) { for(int i=0; i<100; i++) { new Thread(()->{ System.out.println(Mgr07.getInstance().hashCode()); }).start(); } } }
根据静态内部类外部类加载的时候内部类不会被加载的特点,实现了懒加载,内部类只有在调用INSTANCE变量的时候才会被装载
且在内部类装载的时候是单线程的,保证了线程的安全
(8)枚举
public enum Mgr08 { INSTANCE; public void m() {} public static void main(String[] args) { for(int i=0; i<100; i++) { new Thread(()->{ System.out.println(Mgr08.INSTANCE.hashCode()); }).start(); } } }
不存在线程安全的问题、能够防止反序列化重新创建新的对象
3、在JDK中的使用
public class Runtime { private static Runtime currentRuntime = new Runtime(); /** * Returns the runtime object associated with the current Java application. * Most of the methods of class <code>Runtime</code> are instance * methods and must be invoked with respect to the current runtime object. * * @return the <code>Runtime</code> object associated with the current * Java application. */ public static Runtime getRuntime() { return currentRuntime; }