应用场景
只需要一个实例,在代码和内存方面只希望它有一个实例出来
关键点
第一:构造方法设计成私有的
无法new 出来
第二:提供获取实例的方法
相关概念
1.class.forName(“”)可以加载类名,静态变量load到内存,就会实例化
2.哈希码相同也不一定是同一个对象
3.final修饰的变量 必须要初始化
写法相关
饿汉式
优点:
类加载到内存 就实例化一个单利 jvm保证线程安全,简单实用
缺点:不管用到与否,类装载时就完成了实例化
代码块
public class Single1 {
private static final Single1 INSTANCE = new Single1();
private Single1() {};
public static Single1 getINSTANCE() {
return INSTANCE;
}
public void m() {System.out.println("m");}
public static void main(String[] args) {
Single1 m1 = Single1.getINSTANCE();
Single1 m2 = Single1.getINSTANCE();
System.out.println(m1 == m2);
}
}
懒汉式
优点:达到了按需加载的目的
缺点:带来了线程不安全的问题
代码块
public class Single2 {
//如果加 final 必须要初始化
private static Single2 INSTANCE;
private Single2() {}
public static Single2 getINSTANCE() {
if (INSTANCE == null) {
try {
//睡一毫秒 都会产生对象的不同的实例化实例
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Single2();
}
return INSTANCE;
}
public void m() {
System.out.println("m");
}
public static void main(String[] args) {
for (int i=0; i<100; i++) {
new Thread(()-> {
System.out.println(Single2.getINSTANCE().hashCode());
//不同对象的哈希码是不同的
}).start();
}
}
}
以下几个代码段是进阶版的饿汉式
加上synchronized 锁定了当前语句块,但是效率下降
代码块
public class Single3 {
//如果加 final 必须要初始化
private static Single3 INSTANCE;
private Single3() {}
//每次都加锁 效率就降低了
public static synchronized Single3 getINSTANCE() {
if (INSTANCE == null) {
try {
//睡一毫秒 都会产生对象的不同的实例化实例
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Single3();
}
return INSTANCE;
}
public void m() {
System.out.println("m");
}
public static void main(String[] args) {
for (int i=0; i<100; i++) {
new Thread(()-> {
System.out.println(Single3.getINSTANCE().hashCode());
//不同对象的哈希码是不同的
}).start();
}
}
}
通过减小同步代码块的方式提高效率,多线程情况下也不能做到同一个实例
代码块
public class Single4 {
//如果加 final 必须要初始化
private static Single4 INSTANCE;
private Single4() {}
//每次都加锁 效率就降低了
public static Single4 getINSTANCE() {
if (INSTANCE == null) {
synchronized (Single4.class) {
try {
//睡一毫秒 都会产生对象的不同的实例化实例
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Single4();
}
}
return INSTANCE;
}
public void m() {
System.out.println("m");
}
public static void main(String[] args) {
for (int i=0; i<100; i++) {
new Thread(()-> {
System.out.println(Single4.getINSTANCE().hashCode());
//不同对象的哈希码是不同的
}).start();
}
}
}
双重检查单例判断
代码块
public class Single5 {
//如果加 final 必须要初始化 volatile 禁止指令重排
private static volatile Single5 INSTANCE;
private Single5() {}
//每次都加锁 效率就降低了
public static Single5 getINSTANCE() {
if (INSTANCE == null) {
synchronized (Single5.class) {
//双重检查
if (INSTANCE == null) {
try {
//睡一毫秒 都会产生对象的不同的实例化实例
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Single5();
}
}
}
return INSTANCE;
}
public void m() {
System.out.println("m");
}
public static void main(String[] args) {
for (int i=0; i<100; i++) {
new Thread(()-> {
System.out.println(Single5.getINSTANCE().hashCode());
//不同对象的哈希码是不同的
}).start();
}
}
}
静态内部类方式
相关知识点:
1.jvm保证单例,加载外部类时不会加载内部类,可以实现懒加载
2.私有化,只有内部类才可以new出来、
3.内部类不会被随便加载,被调用时才会进行加载
4.虚拟机加载一个class的时候,只加载一次
代码块
public class Single6 {
private Single6() {}
private static class Single6Holder {
private final static Single6 INSTANCE = new Single6();
}
public static Single6 getInstance() {
return Single6Holder.INSTANCE;
}
public void m() {
System.out.println("m");
}
public static void main(String[] args) {
for (int i=0; i<100; i++) {
new Thread(()->{
System.out.println(Single6.getInstance().hashCode());
}).start();
}
}
}
枚举类方式
相关知识点:
1.不仅可以解决线程同步,还可以防止反序列化
2.单例 防止反序列化,把类反射,load到内存
3.不会被反序列化的原因是:枚举类没有构造方法,就算是反序列化只会拿到枚举类的值
4.语法上是最完美的
代码块
public enum Single7 {
INSTANCE;
public void m() {}
public static void main(String[] args) {
for (int i=0;i<100; i++) {
new Thread(()->{
System.out.println(Single7.INSTANCE.hashCode());
}).start();
}
}
}
相关
spring框架中的bean工厂来保证单例