单例模式(Singleton Pattern)是一种常用的设计模式,确保一个类只有一个实例,并提供一个全局访问点。它通常用于节省系统资源或实现共享的资源,比如配置文件、数据库连接等。单例模式可以通过多种方法实现,通常有以下几种主要形式:
1. 饿汉式单例
在这种实现中,单例实例在类加载时就创建好,无需等到使用时再创建。因为实例在类加载时创建,所以线程安全,且不允许有延迟初始化。
java
public class Singleton {
//1、创建私有对象
private static final Singleton singleton = new Singleton();
//2、创建私有构造函数
private Singleton(){
}
//3、提供对外获取对象的公共方法
public static Singleton getInstance(){
return singleton;
}
}
@Test(description = "多线程验证饿汉式-单例", threadPoolSize = 2, invocationCount = 10)
public void singletonTest(){
Singleton instance = Singleton.getInstance();
System.out.println("Hash Code: " + instance.hashCode());
}
go
// Singleton 定义一个结构体(类)
type Singleton struct {
name string
}
// 声明一个结构体实例(对象)
var singleton *Singleton
// 定义init函数构造结构体实例(对象)
func init() {
fmt.Println("Singleton initialized")
singleton = &Singleton{name: "饿汉式单例"}
}
// GetSingLeton 提供一个外获取Singleton对象的函数
func GetSingLeton() *Singleton {
return singleton
}
// TestGetInstance 多线程验证饿汉-单例
func TestGetInstance(t *testing.T) {
//声明一个计数器
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
leton := GetSingLeton()
fmt.Printf("inst1: %p\n", leton)
}(i)
}
wg.Wait()
fmt.Println("All workers done")
}
2. 懒汉式单例
java 为了确保线程安全,可以在 getInstance
方法中使用 synchronized
关键字。然而,这会导致性能下降,因为每次调用都必须进行同步。
public class SingletonLazy {
//1、创建私有对象
private static SingletonLazy singletonLazy ;
//2、创建私有构造函数
private SingletonLazy(){
}
//3、提供对外获取对象的公共方法
public static synchronized SingletonLazy getInstance(){
if (singletonLazy == null) {
singletonLazy = new SingletonLazy();
}
return singletonLazy;
}
}
@Test(description = "多线程验证懒汉式-单例", threadPoolSize = 2, invocationCount = 10)
public void singletonLazyTest(){
SingletonLazy instance = SingletonLazy.getInstance();
System.out.println("Hash Code: " + instance.hashCode());
}
go sync.Once 确保初始化逻辑只执行一次。是线程安全的
// SingletonLazy 定义一个结构体(类)
type SingletonLazy struct {
name string
}
// 声明对象
var (
lazySingleton *SingletonLazy
once sync.Once
)
func GetLazySingleton() *SingletonLazy {
// sync.Once 确保初始化逻辑只执行一次。
once.Do(func() {
fmt.Println("只执行一次")
lazySingleton = &SingletonLazy{name: "懒汉式-单例"}
})
return lazySingleton
}
// TestGetLazySingleton 多线程下验证懒汉式-单例
func TestGetLazySingleton(t *testing.T) {
// 声明一个计算期
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
getLazySingleton := GetLazySingleton()
fmt.Printf("inst1: %p\n", getLazySingleton)
}(i)
}
wg.Wait()
fmt.Println("All workers done")
}
3. 双重检查锁定
java
通过双重检查锁定来实现线程安全的懒汉式单例模式,减少了对性能的影响。
synchronized
synchronized
是 Java 提供的关键字,用于同步代码块。- 它确保同时只能有一个线程执行被同步的代码块,这样可以防止多个线程同时访问这段代码并创建多个实例。
(DoubleCheckedSingleton.class)
DoubleCheckedSingleton.class
表示对该类的类对象进行同步。- 这意味着任何线程在进入这个代码块之前必须获得
DoubleCheckedLockingSingleton
类的锁。 - 通过在类级别上同步,不同的实例之间不会相互干扰。
工作机制
当第一次调用 getInstance()
方法时,代码会进行如下操作:
-
第一次检查:在进入同步代码块之前,检查
instance
是否为null
。如果不是null
,则返回已有实例。此检查是为了在实例已经存在时避免不必要的加锁,提升性能。 -
同步块:如果
instance
为null
,进入同步代码块,并对类进行加锁。此时,只有获取了该锁的线程才能继续执行。 -
第二次检查:在同步块内,重新检查
instance
是否为null
。这是因为在不同线程之间,可能有线程在第一个检查后已经创建了实例。 -
实例化:如果依然为
null
,则创建一个新的实例并赋值给instance
。
总结
通过这种方式,只有在 instance
为 null
的情况下,才会使用同步机制,加锁几乎所有线程,以确保只有一个线程创建实例。这种“双重检查”模式在确保线程安全的同时,最大程度地减少性能开销。这样,你可以在多线程环境中安全地获取单例实例。
private static volatile DoubleCheckSingleton instance;
private DoubleCheckSingleton() {
// 私有构造函数
}
public static DoubleCheckSingleton getInstance() {
if (instance == null) {
synchronized (DoubleCheckSingleton.class) {
if (instance == null) {
instance = new DoubleCheckSingleton();
}
}
}
return instance;
}
@Test(description = "多线程验证双重检查式-单例", threadPoolSize = 2, invocationCount = 10)
public void doubleCheckSingletonTest(){
DoubleCheckSingleton instance = DoubleCheckSingleton.getInstance();
System.out.println("Hash Code: " + instance.hashCode());
}
go
sync.Once 本质就是双重校验的实现,下面是源码
func (o *Once) Do(f func()) {
//判断是否执行过该方法,如果执行过则不执行
if atomic.LoadUint32(&o.done) == 1 {
return
}
// Slow-path.
o.m.Lock()
defer o.m.Unlock()
//进行加锁,再做一次判断,如果没有执行,则进行标志已经扫行并调用该方法
if o.done == 0 {
defer atomic.StoreUint32(&o.done, 1)
f()
}
}
当然也可以自己实现
/锁对象
var lock sync.Mutex
//第一次判断不加锁,第二次加锁保证线程安全,一旦对象建立后,获取对象就不用加锁了。
func GetInstance() *Tool {
if instance == nil {
lock.Lock()
if instance == nil {
instance = new(Tool)
}
lock.Unlock()
}
return instance
}