什么是单例模式?
一个类只允许创建一个实例,那个类就是单例类。这个模式就是单例模式。
单例模式实现方式:
- 饿汉式:实现简单,线程安全,在类加载的期间初始化,但如果单例不使用依然会占用内存,浪费资源。但是不支持懒加载。项目中经常使用。
- 懒汉式(双重检测):实现相对复杂,线程安全,支持懒加载。项目中不常使用。
- Java 静态内部类:实现简单,线程安全,支持懒加载。项目中不常使用。
- Java 枚举:实现简单,线程安全,不支持懒加载,可以防止反射攻击与序列化攻击。项目中经常使用。
单例模式一般不使用懒加载,实现相对复杂且会将错误隐藏到第一次使用单例时才会出现从而影响运行。例如:一个读取配置文件单例,如果读取一个超大文件从而导致OOM,饿汉式项目启动时则会出错,但是懒汉式则会延迟到第一次初始化单例时出错。
以上仅代表本人观点(我用饿汉式仅仅是因为简单)
如何实现单例模式?
饿汉模式
Java
package singleton;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 饿汉式
*
* @author Eric
* @date Create 2022/11/27 21:16
*/
public class SingletonOne {
private static final SingletonOne SINGLETON_ONE = new SingletonOne();
private final AtomicInteger id = new AtomicInteger(0);
/**
* 私有化构造函数。可以禁止其他的类直接 new 创建实例。从而保证只有一个实例对象
*/
private SingletonOne() {
}
public static SingletonOne getInstance() {
return SINGLETON_ONE;
}
public Integer getId() {
return id.incrementAndGet();
}
}
// 测试。
import singleton.SingletonOne;
/**
* @author Eric
* @date Create 2022/11/27 22:03
*/
public class Main {
public static void main(String[] args) {
// 直接 new 编译器无法通过编辑。IDE 报错。
// SingletonOne s = new SingletonOne();
SingletonOne s = SingletonOne.getInstance();
SingletonOne s1 = SingletonOne.getInstance();
System.out.println(s == s1);
Integer id = s.getId();
Integer id1 = s1.getId();
System.out.println("id=" + id);
System.out.println("id1=" + id1);
}
}
枚举
public enum SingletonEnum {
/**
* 实例
*/
INSTANCE;
private final AtomicInteger id = new AtomicInteger(0);
public Integer getId() {
return id.incrementAndGet();
}
}
Go
package singleton
import "sync/atomic"
// singleton 单例结构体。首字母小写为私有模式。不支持外部直接创建实例,从而保证实例唯一。
type singleton struct {
id uint64
}
// singleton 定义单例
var s *singleton
// init 初始化实例
func init() {
s = &singleton{}
}
// GetInstance 公开方法,获取单例对象实例
func GetInstance() *singleton {
return s
}
// GetId
func (s *singleton) GetId() uint64 {
atomic.AddUint64(&s.id, 1)
return s.id
}
// 测试
package singleton_test
import (
"design/singleton"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestGetInstance(t *testing.T) {
s := singleton.GetInstance()
s1 := singleton.GetInstance()
assert.Same(t, s, s1)
}
func TestGetId(t *testing.T) {
s := singleton.GetInstance()
id := s.GetId()
id1 := s.GetId()
fmt.Printf("id:%d\n", id)
fmt.Printf("id1:%d\n", id1)
assert.Equal(t, id, id1-1)
}
懒汉式
Java
粗粒度锁
package singleton;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author Eric
* @date Create 2022/11/28 21:02
*/
public class SingletonTwo {
private static SingletonTwo SINGLETON_TWO = null;
private final AtomicInteger id = new AtomicInteger(0);
private SingletonTwo() {
}
// synchronized 增加一个大锁。用来保证多线程情况下。创建多个实例
public static synchronized SingletonTwo getInstance() {
if (SINGLETON_TWO == null) {
SINGLETON_TWO = new SingletonTwo();
}
return SINGLETON_TWO;
}
public Integer getId() {
return id.incrementAndGet();
}
}
细粒度锁
// 双重检测
public class SingletonTwo {
// new 实例,并非原子操作。volatile 禁止指令重排
private volatile static SingletonTwo SINGLETON_TWO = null;
private final AtomicInteger id = new AtomicInteger(0);
private SingletonTwo() {
}
// 双重检测,将方法上的大锁,优化为细粒度的锁。仅在第一次获取实例时,加锁。初始化实例后则不会再次加锁。
public static SingletonTwo getInstance() {
if (SINGLETON_TWO == null) {
// 细粒度锁.
synchronized (SingletonTwo.class) {
if (SINGLETON_TWO == null) {
// new 新建对象并非原子操作,编译为 class 后为四个指令
// 1. 分配对象内存空间
// 2. 初始化对象
// 3. 设置指针指向堆内存空间
// 4. 初次访问对象
// 操作系统在运行四个指令时会进行指令优化,四个指令可能会指令重排。
// 2,3 指令可以重排序。
// 假设2,3指令发生切换(1,3,2,4)。
// 线程1访问到 指令3 时线程1 被挂起创建
// 切换到线程2,线程2 进入获取实例方法,发现 SINGLETON_TWO 不为 null,直接返回。
// 但实例仅仅创建了内存空间,并未初始化。
SINGLETON_TWO = new SingletonTwo();
}
}
}
return SINGLETON_TWO;
}
public Integer getId() {
return id.incrementAndGet();
}
}
静态内部类
public class SingletonStatic {
private final AtomicInteger id = new AtomicInteger(0);
public SingletonStatic() {
}
// 静态内部类
private static class Inner {
private static final SingletonStatic INSTANCE = new SingletonStatic();
}
// 只有调用获取实例时,JVM 才会加载静态内部类。
// 从而实现单例且支持懒加载
public static SingletonStatic getInstance() {
return Inner.INSTANCE;
}
public Integer getId() {
return id.incrementAndGet();
}
}
Go
package singleton
import (
"sync"
"sync/atomic"
)
type singleton_lazy struct {
id uint64
}
var s_lazy *singleton_lazy
var once = &sync.Once{}
func GetLazyInstance() *singleton_lazy {
if s_lazy == nil {
once.Do(func() {
s_lazy = &singleton_lazy{}
})
}
return s_lazy
}
// GetId
func (s_lazy *singleton_lazy) GetLazyId() uint64 {
atomic.AddUint64(&s_lazy.id, 1)
return s_lazy.id
}
// test
package singleton_test
import (
"design/singleton"
"testing"
"github.com/stretchr/testify/assert"
)
func TestGetInstanceLazy(t *testing.T) {
s := singleton.GetLazyInstance()
s1 := singleton.GetLazyInstance()
assert.Same(t, s, s1)
}
func BenchmarkGetInstanceLazyGetId(b *testing.B) {
b.RunParallel(func(p *testing.PB) {
for p.Next() {
s := singleton.GetLazyInstance()
s1 := singleton.GetLazyInstance()
if s != s1 {
b.Error("test failed")
}
}
})
}
破坏单例模式
Java
public static void main(String[] args) throws Exception {
// 通过反射创建
Class<SingletonOne> oneClass = SingletonOne.class;
Constructor<SingletonOne> declaredConstructor = oneClass.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
SingletonOne s = declaredConstructor.newInstance();
// 单例获取
SingletonOne s1 = SingletonOne.getInstance();
System.out.println("反射破坏单例模式=" + (s == s1));
// 临时目录
String property = "java.io.tmpdir";
String tempDir = System.getProperty(property);
String path = tempDir + File.pathSeparator + "1.txt";
FileOutputStream out = new FileOutputStream(path);
// 将实例写入文件
ObjectOutputStream objout = new ObjectOutputStream(out);
objout.writeObject(s1);
FileInputStream in = new FileInputStream(path);
ObjectInputStream oin = new ObjectInputStream(in);
SingletonOne s2 = (SingletonOne) oin.readObject();
System.out.println("对象反序列化破坏单例模式=" + (s1 == s2));
}
go
func TestReflectObj(t *testing.T) {
// 通过反射破坏单例
s := singleton.GetInstance()
typeOfSingleton := reflect.TypeOf(s)
s1 := reflect.New(typeOfSingleton)
fmt.Printf("Instance:%p\n", s)
fmt.Printf("Instance:%p\n", &s1)
assert.NotSame(t, s, s1)
}
网友评论