class EagerSingleton {
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() { }
public static EagerSingleton getInstance() {
return instance;
}
}
当类被加载时,静态变量instance会被初始化,此时类的私有构造函数会被调用,单例类的唯一实例将被创建。如果使用饿汉式单例来实现负载均衡器LoadBalancer类的设计,则不会出现创建多个单例对象的情况,可确保单例对象的唯一性。
为什么要static修饰getInstance方法?
因为使得静态和非静态方法都可以调用,而且因为是静态对象,一般使用静态方法来创建。
饿汉式的优点:
优点 :
1.线程安全
2.在类加载的同时已经创建好一个静态对象,调用时反应速度快
缺点 :
资源效率不高,可能getInstance()永远不会执行到,但执行该类的其他静态方法或者加载了该类(class.forName),那么这个实例仍然初始化
懒汉式
class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() { }
synchronized public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
这种写法每次调用getInstance()时都需要进行线程锁定判断,在多线程高并发访问环境中,将会导致系统性能大大降低,使用下面的写法:
public static LazySingleton getInstance() {
if (instance == null) {
synchronized (LazySingleton.class) {
instance = new LazySingleton();
}
}
return instance;
}
这里是尝试解决效率低的一种方案,将synchronized少包一些代码
但是这里线程不安全,因为没有将if()判断包裹在内。
最终解决方案:
public class Singleton{
private volatile static Singleton singleton = null;
private Singleton() { }
public static Singleton getInstance() {
if (singleton== null) {
synchronized (Singleton.class) {
if (singleton== null) {
singleton= new Singleton();
}
}
}
return singleton;
}
这里将if的判断语句包裹在内,同时增加一个外层的判断,使得大部分线程不需要进入同步监视器。这里用volatile是为了防止指令重排,防止拿到null的情况发生。
Java语言提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程。当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。
在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比sychronized关键字更轻量级的同步机制。
当对非 volatile 变量进行读写的时候,每个线程先从内存拷贝变量到CPU缓存中。如果计算机有多个CPU,每个线程可能在不同的CPU上被处理,这意味着每个线程可以拷贝到不同的 CPU cache 中。
而声明变量是 volatile 的,JVM 保证了每次读变量都从内存中读,跳过 CPU cache 这一步。
当一个变量定义为 volatile 之后,将具备两种特性:
1.保证此变量对所有的线程的可见性,这里的“可见性”,(可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程修改的结果。另一个线程马上就能看到。比如:用volatile修饰的变量,就会具有可见性。volatile修饰的变量不允许线程内部缓存和重排序,即直接修改内存。所以对其他线程是可见的)volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。但普通变量做不到这点,普通变量的值在线程间传递均需要通过主内存来完成。
2.禁止指令重排序优化。有volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU访问内存时,并不需要内存屏障;(什么是指令重排序:是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理)。
volatile 性能:
volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。
这里的 volatile 关键字主要是为了防止指令重排。因为new 的是一个引用对象,在堆内存里面,不存在不可见性。所以主要是防止指令重排。
如果不用 ,singleton = new Singleton();,这段代码其实是分为三步:
分配内存空间。(1)
初始化对象。(2)
将 singleton 对象指向分配的内存地址。(3)
加上 volatile 是为了让以上的三步操作顺序执行,如果(3)在2之前完成就可能拿到singleton未初始化的对象,就是null。
使用静态内部类:
public class SingletonDemo {
private static class SingletonHolder{
private static SingletonDemo instance=new SingletonDemo();
}
private SingletonDemo(){
System.out.println("Singleton has loaded");
}
public static SingletonDemo getInstance(){
return SingletonHolder.instance;
}
}
静态内部类
静态内部类就是个独立的类。之所以要弄这么个东西,只是起到一个注释的效果,而且这个注释可静态检查。比如有A,B两个类,B有点特殊,虽然可以独立存在,但只被A使用。这时候怎么办?如果把B并入A里,复杂度提高,搞得A违反单一职责。如果B独立,又可能被其他类(比如同一个包下的C)依赖,不符合设计的本意。所以不如将其变成A.B,等于添加个注释,告诉其他类别使用B了,它只跟A玩。非静态的才是真正的内部类,对其外部类有个引用。
根据Oracle官方的说法:Nested classes are divided into two categories: static and non-static. Nested classes that are declared static are called static nested classes. Non-static nested classes are called inner classes.从字面上看,一个被称为静态嵌套类,一个被称为内部类。从字面的角度解释是这样的:什么是嵌套?嵌套就是我跟你没关系,自己可以完全独立存在,但是我就想借你的壳用一下,来隐藏一下我自己。什么是内部?内部就是我是你的一部分,我了解你,我知道你的全部,没有你就没有我。(所以内部类对象是以外部类对象存在为前提的)
具体可见下面博客:
静态内部类
不能从静态成员内部类中访问非静态外部类对象
静态内部类有2个特点:
1.在外部类装载的时候它不装载.
2.在调用静态内部类的时候他会装载,而且装载过程是线程安全的
利用这两个特性,我们构建了静态内部类来实现单例模式。
使用枚举方式
enum SingletonDemo{
INSTANCE;
public void otherMethods(){
System.out.println("Something");
}
}
INSTANCE是 SingletonDemo的实例