单例模式:Singleton
单例模式的多种写法:
最常用的是第1种: 饿汉式, 写法简洁, 实际开发中常用.
最完美的是第5种:枚举的方式, 无懈可击
1. 饿汉式
public class Singleton_1 {
/**
* 私有化构造方法,不能在外部new实例
*/
private Singleton_1() {
}
/**
* 静态成员变量:在类加载的时候进行实例化
* JVM只会对同一个类加载一次,保证了只会有一个person的实例对象
* 私有化的构造方式,在类内部还是可以被调用的
*/
private static final Singleton_1 one = new Singleton_1();
public static Singleton_1 getInstance(){
return one;
}
}
public class Test {
public static void main(String[] args) {
//构造方法私有化,无法new
//Singleton_1 singleton = new Singleton_1();
Singleton_1 instance = Singleton_1.getInstance();
Singleton_1 instance1 = Singleton_1.getInstance();
System.out.println(instance == instance1);
//输出:true
}
}
不足: 只要类加载时,对象就会被实例化, 无论是否要被使用, 浪费资源.
2. 懒汉式
public class Singleton_2 {
/**
* 私有化构造方法,不能在外部new实例
*/
private Singleton_2() {
}
/**
* 只有当getInstance方法被调用时,才被实例化
*/
private static Singleton_2 one;
public static Singleton_2 getInstance(){
if (one == null){
one = new Singleton_2();
}
return one;
}
}
public class Test {
public static void main(String[] args) {
Singleton_2 instance = Singleton_2.getInstance();
Singleton_2 instance1 = Singleton_2.getInstance();
System.out.println(instance == instance1);
//输出:true
}
}
不足: 多线程访问时, 会存在不同的实例化对象.
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(() -> {
Singleton_2 instance = Singleton_2.getInstance();
System.out.println(instance.hashCode());
}).start();
//输出:
// 1158246010
// 736763851
// 275307607
// 736763851
// 1158246010
}
}
}
//这里为了效果明显, 睡了一下
//原因就是:
//线程1进入one == null的if判断,但没来得及new的时候,进程被停止;
//线程2执行,同样进入了one == null的if判断.这样两个线程最后均会new出来一个实例对象
public static Singleton_2 getInstance() {
if (one == null){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
one = new Singleton_2();
}
return one;
}
3. 使用synchronized锁的懒汉式
第一种: 给getInstance方法加锁
//整个方法都是阻塞的,同一时间只能有一个线程执行该方法, 最耗时间.
public static synchronized Singleton_2 getInstance() {
if (one == null){
one = new Singleton_2();
}
return one;
}
第二种: 双重判断 + 加锁
(DCL: double check lock), 变量one必须使用volatile关键字, 多线程场景下指令重排会导致return了半初始化状态下的Singleton_2对象
//相对于前一种加锁方式, 减小了加锁的范围, 执行效率更高
public static synchronized Singleton_2 getInstance() {
if (one == null){
synchronized ("lock"){
if (one == null){
one = new Singleton_2();
}
}
}
return one;
}
4. 以内部类实现的懒汉式
public class Singleton_3 {
/**
* 私有化构造方法,不能在外部new实例
*/
private Singleton_3() {
}
//方法调用时,SingletonBuilder加载,singleton被实例化
//同时由jvm保证了SingletonBuilder只会被加载一次,即singleton只实例化一次
public static Singleton_3 getInstance(){
return SingletonBuilder.singleton;
}
//通过静态内部类来实例化Singleton_3对象:
//在Singleton_3加载时,静态内部类不会被加载,因此singleton不会实例化
private static class SingletonBuilder{
private static Singleton_3 singleton = new Singleton_3();
}
}
缺点: 可以通过反射或反序列化的方式, 获得不同的实例对象(前述几种单例的写法也是).
5. 枚举实现
public enum Singleton_4 {
ONE;
private String name ="枚举单例模式";
public void doing(){
System.out.println(this.name);
}
}
来自《Effective Java》的单例模式写法,可以有效防止以反射或反序列化的方式产生不同的实例