单例模式

1.单例模式

Singleton模式主要作用是保证在Java应用程序中,一个类只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为,比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。Singleton模式限制了实例的个数,有利于Java垃圾回收(garbage collection)。单例写法众多,值得注意的是要保证线程安全性以及单例对象数据可见性。

2.并发编程三个概念

1)原子性

即一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行(事务回滚)。

2)可见性

指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

3)有序性

即程序执行的顺序按照代码的先后顺序执行。
指令重排序:处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。指令重排序不会影响单个线程的执行,但是会影响到线程并发执行的正确性。
在介绍单例模式前,先介绍一下

3.happens-before 原则

Java内存模型具备一些先天的“有序性”,即不需要通过任何手段就能够得到保证的有序性,这个通常也称为 happens-before 原则。如果两个操作的执行次序无法从happens-before原则推导出来,那么它们就不能保证它们的有序性,虚拟机可以随意地对它们进行重排序。
下面就来具体介绍下happens-before原则(先行发生原则):
程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作
volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始
  这8条原则摘自《深入理解Java虚拟机》。

4.volatile关键字

被volatile修饰的变量:
1.保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2.禁止进行指令重排序,确保指令重排序时排到内存屏障(lock前缀指令)时不会乱序执行内存屏障前后指令,强制对缓存的修改操作立即写入主内存,写操作导致其他CPU对应的缓存行无效。

5.单例模式8种写法及优缺点

package com.example.designmodel;

public class Singleton {

    private Singleton() {}

    /**
     * 饿汉式(静态常量) - 可用、可能内存浪费
     * 优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
     * 缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
     */
    private final static Singleton INSTANCE = new Singleton();
    public static Singleton getInstance1(){
        return INSTANCE;
    }

    /**
     * 饿汉式(静态代码块) - 可用、可能内存浪费
     * 优点:避免了线程同步问题。
     * 缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
     */
    private static Singleton instance2;
    static {
        instance2 = new Singleton();
    }
    public static Singleton getInstance2() {
        return instance2;
    }

    /**
     * 懒汉式(线程不安全) - 不可用、多线程下不安全
     * 这种写法起到了Lazy Loading的效果,但是只能在单线程下使用。如果在多线程下,两个线程同时通过if判断,
     * 这时便会产生多个实例。所以在多线程环境下不可使用这种方式。
     */
    private static Singleton instance3;
    public static Singleton getInstance3() {
        if (instance3 == null) {
            instance3 = new Singleton();
        }
        return instance3;
    }

    /**
     * 懒汉式(线程安全,同步方法) - 不推荐用
     * 为解决线程不安全问题,做个线程同步就可以了,于是就对getInstance()方法进行了线程同步。
     * 优点:线程安全
     * 缺点:效率低,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。
     * 而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低要改进。
     */
    private static Singleton instance4;
    public static synchronized Singleton getInstance4() {
        if (instance4 == null) {
            instance4 = new Singleton();
        }
        return instance4;
    }

    /**
     * 双重检查 Double Check Locking(线程安全,同步代码块) - 不可用
     * 每个线程都有一个缓存区,存放从主内存读取的数据,数据修改后先修改缓存数据,再将缓存数据更新到主内存。
     * 当线程1获取到锁时,会实例化Singleton,将线程1缓存的str从null更新为“str”,将a更新为1,
     * 这个涉及到指令重排序,也可能先将a更新为1,str更新为“str”。
     * 如果线程1在锁释放前将主内存中a更新为1,还未将主内存中str更新为“str”,这时线程2获取到Singleton的实例不为null,
     * 将数据返回a=1,str=null,产生了错误数据,相当于幻读。
     */
    private static Singleton instance5;
    private String str = null;
    private int a = 0;
    public Singleton(int b){
        str = "str";
        a = b;
    }
    public static Singleton getInstance5() {
        if (instance5 == null) {
            synchronized (Singleton.class) {
                instance5 = new Singleton(1);
            }
        }
        return instance5;
    }

    /**
     * 双重检查 Double Check Locking(volatile修饰实例,线程安全,同步代码块) - 推荐用
     * volatile禁止了指令重排序,volatile修饰的变量禁用线程缓存,变量修改后直接修改主内存,保证了变量可见性。
     */
    private static volatile Singleton instance6;
    public static Singleton getInstance6() {
        if (instance6 == null) {
            synchronized (Singleton.class) {
                if (instance6 == null) {
                    instance6 = new Singleton();
                }
            }
        }
        return instance6;
    }

    /**
     * 静态内部类 - 推荐用
     * 这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。
     * 不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,
     * 而静态内部类方式在Singleton类被装载时并不会立即实例化(static修饰的方法变量在加载类的过程中完成静态变量的内存分配),
     * 而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。
     * 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
     * 优点:避免了线程不安全,延迟加载,效率高。
     */
    private static class SingletonInstance {
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance7() {
        return SingletonInstance.INSTANCE;
    }

    /**
     * 枚举类 - 推荐用
     * 借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
     * 可能是因为枚举在JDK1.5中才添加,所以在实际项目开发中,很少见人这么写过。
     * 优点:系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能。
     * 缺点:当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new,可能会给其他开发人员造成困扰,特别是看不到源码的时候。
     */
	 public enum SingletonEnum {
	      INSTANCE;
	      public void getInstance8() {
	            
	      }
	  }
    
}

4.单例模式优缺点

1)优点

利用单例模式可以保证系统某些数据的一致性,达到系统一致性,比如登录人数等。节省系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提供系统性能。

2)缺点

当想实例化一个单例类的时候,需要记住使用相应的获取对象方法,而不是使用new来获取。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值