设计模式
单例模式
介绍
意图: 保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决: 一个全局使用的类频繁地创建与销毁。
优点:
- 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
- 2、避免对资源的多重占用(比如写文件操作)。
缺点: 没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
使用场景:
- 1、要求生产唯一序列号。
- 2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
- 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
注意事项: getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。
简单实现:
单线程下最简单的单例模式:
public class LazySingletonTest {
public static void main(String[] args) {
LazySingleton instance = LazySingleton.getInstance();
LazySingleton instance1 = LazySingleton.getInstance();
System.out.println(instance == instance1);
//输出:true
}
}
class LazySingleton{
private static LazySingleton instance;
private LazySingleton(){
}
public static LazySingleton getInstance(){
if (instance == null){
instance = new LazySingleton();
}
return instance;
}
当多线程同时走到这段代码的时候,就会产生两个实例
public class LazySingletonTest {
public static void main(String[] args) {
new Thread( ()->{
LazySingleton instance = LazySingleton.getInstance();
System.out.println(instance);
}).start();
new Thread( ()->{
LazySingleton instance = LazySingleton.getInstance();
System.out.println(instance);
}).start();
//输出:
//com.sms.designpattern.LazySingleton@30f3ad2a
//com.sms.designpattern.LazySingleton@697d5cae
}
}
class LazySingleton{
private static LazySingleton instance;
private LazySingleton(){
}
public static LazySingleton getInstance(){
if (instance == null){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new LazySingleton();
}
return instance;
}
}
多线程下 实例方法新增synchronized关键字即可解决
public class LazySingletonTest {
public static void main(String[] args) {
new Thread( ()->{
LazySingleton instance = LazySingleton.getInstance();
System.out.println(instance);
}).start();
new Thread( ()->{
LazySingleton instance = LazySingleton.getInstance();
System.out.println(instance);
}).start();
//输出:
//com.sms.designpattern.LazySingleton@697d5cae
//com.sms.designpattern.LazySingleton@697d5cae
}
}
class LazySingleton{
private static LazySingleton instance;
private LazySingleton(){
}
public synchronized static LazySingleton getInstance(){
if (instance == null){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new LazySingleton();
}
return instance;
}
}
也就是说,这样子的话每次多线程运行都会进行一个加锁的操作,这样子能解决多实例的问题,但是这样做会损耗性能
我们加锁的目的是想要只有一个实例,把锁加在方法上,不管后面有没有走到加载实例的时候都会进行加锁,没有必要
懒加载最终单例模式:
public class LazySingletonTest {
public static void main(String[] args) {
new Thread( ()->{
LazySingleton instance = LazySingleton.getInstance();
System.out.println(instance);
}).start();
new Thread( ()->{
LazySingleton instance = LazySingleton.getInstance();
System.out.println(instance);
}).start();
}
}
class LazySingleton{
private volatile static LazySingleton instance; //volatile 禁止指令重排
private LazySingleton(){
}
public static LazySingleton getInstance(){
if (instance == null){
//当两个方法走到这里,同样会实例化两次,所以要再进行一次判断
//在这里多并发的情况还是会有性能损耗
synchronized (LazySingleton.class){
if (instance == null){
instance = new LazySingleton();
//字节码层 javap反编译
// JIT ,CPU 可能会进行指令重排 (第二步、第三步会乱序)
//1. 分配空间
//2. 初始化
//3. 引用赋值
}
}
}
return instance;
}
}
饿汉模式简单单例模式
public class HungrySingletonTest {
public static void main(String[] args) {
HungrySingleton instance = HungrySingleton.getInstance();
HungrySingleton instance1 = HungrySingleton.getInstance();
System.out.println(instance == instance1);
}
}
// 饿汉模式
class HungrySingleton{
private static HungrySingleton instance = new HungrySingleton();
private HungrySingleton(){
}
public static HungrySingleton getInstance(){
return instance;
}
}
基础JVM的类加载机制来保证线程安全
饿汉模式:
类加载的初始化阶段就完成了 实例的初始化。本质上就是借助于JVM类加载机制,保证实例的唯一性。
类加载过程:
- 加载二进制数据到内存中,生成对应的class数据结构
- 连接:a. 验证 b.准备(给类的静态成员变量赋默认值) c.解析
- 初始化:给类的静态变量赋初值
只有在真正使用对应的类时,才会触发初始化 如(当前类是启动类即main函数所在类,直接进行new操作,访问静态属性、访问静态方法,用反射访问类,初始化一个类的子类等)
静态内部类实现
public class InnerClassSingletonTest {
public static void main(String[] args) {
InnerClassSingleton instance = InnerClassSingleton.getInstance();
InnerClassSingleton instance1 = InnerClassSingleton.getInstance();
System.out.println(instance==instance1);
}
}
//静态内部类实现
//本质上也是jvm类加载机制实现线程安全
//懒加载,不主动调用也不会加载实例
class InnerClassSingleton{
private static class InnerClassHolder{
private static InnerClassSingleton instance = new InnerClassSingleton();
}
private InnerClassSingleton(){
}
//调用到此方法才会加载实例
public static InnerClassSingleton getInstance(){
return InnerClassHolder.instance;
}
}
枚举类(线程安全,调用效率高,不能延时加载,可以天然的防止反射和反序列化调用)
1 public enum SingletonDemo4 {
2
3 //枚举元素本身就是单例
4 INSTANCE;
5
6 //添加自己需要的操作
7 public void singletonOperation(){
8 }
9 }
反射修改多例
反射攻击可以对懒汉模式进行多实例且无法阻挡,饿汉模式和静态内部类可以防护
private final static HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton(){
// 【饿汉式】防止反射攻击
if(hungrySingleton != null){
throw new RuntimeException("单例构造器禁止反射调用");
}
}