-
Singleton(单例模式)
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。模式一:简单实用,但是在Test1被初始化时就创建了Test1的实例对象
public class Test1 { //静态变量,在初始化时只实例化一次 private static final Test1 INSTANCE =new Test1(); //私有的构造方法,拒绝外界通过new的方式实例化Test1 private Test1(){ } public static Test1 getInstance(){ return INSTANCE; } public static void main(String[] args) { Test1 t1 =Test1.getInstance(); Test1 t2 =Test1.getInstance(); System.out.println(t1==t2); } }
模式二:通过静态代码块方式实例化Test2对象,其实与模式一一样
public class Test2 { //静态变量,在初始化时只实例化一次 private static final Test2 INSTANCE ; static { INSTANCE=new Test2(); } //私有的构造方法,拒绝外界通过new的方式实例化Test1 private Test2(){ } public static Test2 getInstance(){ return INSTANCE; } public static void main(String[] args) { Test2 t1 = Test2.getInstance(); Test2 t2 = Test2.getInstance(); System.out.println(t1==t2); } }
模式三:懒汉式,虽然解决了懒加载问题,但是出现了线程安全问题
public class Test3 { //静态变量,在初始化时只实例化一次 private static Test3 INSTANCE; //私有的构造方法,拒绝外界通过new的方式实例化Test1 private Test3() { } public static Test3 getInstance() { if (INSTANCE == null) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } INSTANCE = new Test3(); } return INSTANCE; } public static void main(String[] args) { for (int i = 0; i < 100; i++) { new Thread(() -> { System.out.println(Test3.getInstance().hashCode()); }).start(); } } }
模式四:懒汉式虽然可以通过synchronized加锁,但是会带来性能的下降
public class Test4 { //静态变量,在初始化时只实例化一次 private static Test4 INSTANCE; //私有的构造方法,拒绝外界通过new的方式实例化Test1 private Test4() { } public static synchronized Test4 getInstance() { if (INSTANCE == null) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } INSTANCE = new Test4(); } return INSTANCE; } public static void main(String[] args) { for (int i = 0; i < 100; i++) { new Thread(() -> { System.out.println(Test4.getInstance().hashCode()); }).start(); } } }
模式五:通过缩小synchronized范围,但是依然存在线程安全问题
public class Test5 { //静态变量,在初始化时只实例化一次 private static Test5 INSTANCE; //私有的构造方法,拒绝外界通过new的方式实例化Test1 private Test5() { } public static Test5 getInstance() { if (INSTANCE == null) { //妄图通过减小同步代码块来提高性能,但依然不可行 synchronized (Test5.class){ try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } INSTANCE = new Test5(); } } return INSTANCE; } public static void main(String[] args) { for (int i = 0; i < 100; i++) { new Thread(() -> { System.out.println(Test5.getInstance().hashCode()); }).start(); } } }
模式六:双重判断
如果指令按照顺序执行倒也无妨,但JVM为了优化指令,提高程序运行效率,允许指令重排序。如此,在程序真正运行时以上指令执行顺序可能是这样的:
a)给instance实例分配内存;
b)将instance对象指向分配的内存空间;
c)初始化instance的构造器;
这时候,当线程一执行b)完毕,在执行c)之前,被切换到线程二上,这时候instance判断为非空,此时线程二直接来到return instance语句,拿走instance然后使用,接着就顺理成章地报错(对象尚未初始化)。
具体来说就是synchronized虽然保证了线程的原子性(即synchronized块中的语句要么全部执行,要么一条也不执行),但单条语句编译后形成的指令并不是一个原子操作(即可能该条语句的部分指令未得到执行,就被切换到另一个线程了)
public class Test6 {
//静态变量,在初始化时只实例化一次
private static volatile Test6 INSTANCE;
//私有的构造方法,拒绝外界通过new的方式实例化Test1
private Test6() {
}
public static Test6 getInstance() {
if (INSTANCE == null) {
//妄图通过减小同步代码块来提高性能,但依然不可行
synchronized (Test6.class) {
//双重判断
if (INSTANCE == null) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Test6();
}
}
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(Test6.getInstance().hashCode());
}).start();
}
}
}
模式七:通过静态内部类方式,JVM保证单例,加载外部类时不会加载内部类,这样可以实现懒加载
public class Test7 {
//私有的构造方法,拒绝外界通过new的方式实例化Test1
private Test7() {
}
//静态内部类,外部无法访问
private static class Test7Holder{
private static Test7 INSTANCE =new Test7();
}
public static Test7 getInstance() {
return Test7Holder.INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(Test7.getInstance().hashCode());
}).start();
}
}
}
模式八:不仅可以解决线程同步,还可以防止反序列化,之前几种方式可以根据class文件通过反射,newInstance根据构造方法得到一个实例,而枚举没有构造方法,所以无法反序列化
public enum Test8 {
INSTANCE;
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(Test8.INSTANCE.hashCode());
}).start();
}
}
}
github: https://github.com/lishanglei/design-patterns.git