目录
介绍
所谓的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)
比如Hebernate的SessionFactory,它充当数据存储源的代理,并负责创建Session对象,SessionFactory并不是轻量级的,一般情况下,一个项目通常只需要一个SessiobFacyory就够,这时就会使用到单例模式
单例模式有八种方式
1.饿汉式(静态常量)
2.饿汉式(静态代码块)
3.懒汉式(线程不安全)
4.懒汉式(线程安全,同步方法)
5.懒汉式(线程安全,同步代码块)
6.双重检查
7.静态内部类
8.枚举
饿汉式(静态常量) 优点:线程安全 缺点:可能浪费内存
优缺点说明:
1. 优点: 这种写法比较简单,就是在类装载的时候就完成实例化.避免了线程同步问题.
2. 缺点: 在类装载的时候就实例化,没有达到lazy loading的效果.如果从始致终从未使用过这个实例,会造成内存的浪费
3. 这种方式基于classloader机制避免了多线程的同步问题,不过,instance在类装载的时候就实例化,在单例模式大多数都是调用getInstance方法,
但是导致类装载的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候instance变量就没有达到懒加载的效果
补充:类加载的时机
对于什么时候加载,Java虚拟机规范中并没有约束,各个虚拟机都可以按自身需要来自由实现。但绝大多数情况下,都遵循“什么时候初始化”来进行加载。
什么时候初始化?Java虚拟机规范有明确规定,当符合以下条件时(包括但不限于),虚拟机内存中没有找到对应类型信息,则必须对类进行“初始化”操作:
使用new实例化对象时、读取或者设置一个类的静态字段或方法时
反射调用时,例如 Class.forName("com.xxx.MyTest")
初始化一个类的子类,会首先初始化子类的父类
Java虚拟机启动时标明的启动类
JDK8 之后,接口中存在default方法,这个接口的实现类初始化时,接口会其之前进行初始化
饿汉式(静态常量)
public class SingletonTest01 {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
Singleton instance1 = Singleton.getInstance();
System.out.println(instance == instance1); //true
System.out.println("instance.hashCode="+instance.hashCode()); //两个变量的hashCode相同
System.out.println("instance1.hashCode="+instance1.hashCode());
}
}
class Singleton {
//1.将构造器私有化,防止外部能new
private Singleton() {
}
//2. 在类内部创建对象实例
public static final Singleton instance = new Singleton();
//3. 提供一个公有的静态方法,返回实例对象
public static Singleton getInstance(){
return instance;
}
}
饿汉式(静态代码块)
public class SingletonTest01 {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
Singleton instance1 = Singleton.getInstance();
System.out.println(instance == instance1); //true
System.out.println("instance.hashCode="+instance.hashCode()); //两个变量的hashCode相同
System.out.println("instance1.hashCode="+instance1.hashCode());
}
}
class Singleton {
//1.将构造器私有化,防止外部能new
private Singleton() {
}
//2. 在类内部创建对象实例
private static Singleton instance;
static{ //静态代码块中实例化对象
instance = new Singleton()
}
//3. 提供一个公有的静态方法,返回实例对象
public static Singleton getInstance(){
return instance;
}
}
懒汉式
懒汉式(线程不安全)
/**
* 懒汉式(线程不安全)
*/
public class SingletonTest03 {
public static void main(String[] args) {
Singleton03 instance = Singleton03.getInstance();
Singleton03 instance1 = Singleton03.getInstance();
System.out.println(instance == instance1);
System.out.println("instance.hashCode="+instance.hashCode());
System.out.println("instance1.hashCode="+instance1.hashCode());
}
}
class Singleton03 {
private static Singleton03 instance;
private Singleton03() {
}
public static Singleton03 getInstance() {
if (instance == null) {
instance = new Singleton03();
}
return instance;
}
}
优缺点说明
<span style="background-color:#f8f8f8"><span style="color:#333333">1. 起到了懒加载的效果,但是只能在单线程下使用
2. 如果在多线程下,一个线程进入了if (instance == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例.所以在多线程环境 下不可使用这种方式</span></span>
结论:
在实际开发中,不要使用这种方式
懒汉式(线程安全,同步方法,效率不高)
/**
* 懒汉式(线程安全,同步方法)
*/
public class SingletonTest03 {
public static void main(String[] args) {
Singleton03 instance = Singleton03.getInstance();
Singleton03 instance1 = Singleton03.getInstance();
System.out.println(instance == instance1);
System.out.println("instance.hashCode="+instance.hashCode());
System.out.println("instance1.hashCode="+instance1.hashCode());
}
}
class Singleton03 {
private static Singleton03 instance;
private Singleton03() {
}
//加了synchronized,解决线程不安全问题
public static synchronized Singleton03 getInstance() {
if (instance == null) {
instance = new Singleton03();
}
return instance;
}
}
优缺点说明
1. 解决了线程不安全问题
2. 效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步.而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行 了.方法进行同步效率太低(锁的粒度太大)
3. 结论: 在实际开发中,不推荐使用
双重检查(推荐使用)
/**
*
* 双重检查
*
**/
public class SingletonTest04 {
public static void main(String[] args) {
}
}
class Singleton04{
private static volatile Singleton04 singleton04; //volatile关键字保证一个线程实例化了该变量之后,其他线程能马上发现,避免再次实例化
private Singleton04(){}
public static Singleton04 getInstance(){
if (singleton04==null){ //此判断拦截已经实例化后的线程,这些线程不需要再进入synchronized代码里,提高效率
synchronized (Singleton04.class){
if (singleton04==null){ //此判断拦截singleton04初始化时与初始化线程同时进入这里的线程,保证只有一个线程实例化singleton04
singleton04 = new Singleton04();
}
}
}
return singleton04;
}
}
优缺点说明
1. 双重检查概念是多线程开发中常使用的,如代码中所示,我们进行了两次if (singleton04==null)检查,第一个检查提升效率,避免所有线程都进入同步代码,第二个检查结合volatile保证线程安全
2. 这样,实例化代码只执行一次,后面再次访问时,判断if (singleton04==null)直接return实例化对象,也避免的反复进行方法同步
3. 线程安全,延迟加载,效率较高
4. 结论: 在实际开发中,推荐使用这种单例模式
静态内部类实现(推荐使用)
public class SingletonTest05 {
public static void main(String[] args) {
Singleton05.test();
Singleton05 instance = Singleton05.getInstance();
Singleton05 instance2 = Singleton05.getInstance();
System.out.println(instance == instance2);
}
}
/**
* 线程安全: 利用JVM的类装载实现线程安全
* 懒加载: 静态内部类只负责类的初始化,不负责其他事情,保证了只有被第一次Singleton05.getInstance()才会初始化
* */
class Singleton05{
private Singleton05(){};
public static void test(){
//这个方法被调用不会导致静态内部类的加载
System.out.println("外部类的静态方法被调用");
}
/**
* 使用静态内部类的巧妙之处:
* 1. 这个静态内部类只负责单例对象的初始化,不负责其他事情,其他事情会导致内部类的提前加载
*/
private static class SingletonInstance{
private static final Singleton05 instance = new Singleton05();
static {
System.out.println("内部类被加载...");
}
}
public static Singleton05 getInstance(){
//这个方法第一次被调用时,加载SingletonInstance内部类,并将static成员变量instance初始化
System.out.println("getInstance方法被调用");
return SingletonInstance.instance;
}
}
运行结果:
外部类的静态方法被调用
getInstance方法被调用
内部类被加载...
getInstance方法被调用
true
枚举实现(推荐使用)
/**
*优缺点说明:
* 1. 借助JDK1.5中添加的枚举来实现单例模式,不仅能避免多线程问题,而且还能防止反序列化重新创建新的对象
*
**/
public enum Singleton {
INSTANCE("张三","12");
private String name;
private String age;
Singleton() {
}
Singleton(String name, String age) {
this.name = name;
this.age = age;
}
public void ok(){
System.out.println("ok");
}
public static void main(String[] args) {
Singleton instance = Singleton.INSTANCE;
Singleton instance2 = Singleton.INSTANCE;
System.out.println(instance==instance2);
instance.ok();
}
}
JDK中的单例模式
java.lang.Runtime使用了饿汉式创建单例模式
单例模式注意事项和细节说明
<span style="background-color:#f8f8f8"><span style="color:#333333">1. 单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
2. 当想实例化一个单例类的时候,必须要记住使用相应获取对象的方法,而不是使用new
3. 单例模式使用的场景: 需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)</span></span>
版权声明:本文为m0_63393739原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。