一、介绍
单例模式是一种创建型设计模式, 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。
本文给出了Java和Golang的代码实现,即使语言不同,思想是相同的,但考虑到不同语言的特性,会存在些许的差别。
二、Java实现单例模式
Java实现单例模式的方式主要有5种,分别是饿汉模式、懒汉模式、双重检测机制实现、静态内部类实现、枚举类实现。
2.1、饿汉模式
代码:
public final class Singleton {
private static Singleton instance = new Singleton("Foo");
public String value;
private Singleton(String value) {
this.value = value;
}
public static Singleton getInstance() {
return instance;
}
}
在类加载的时候就对实例进行初始化,没有线程安全问题;获取实例的静态方法没有使用同步,调用效率高;但是没有使用懒加载,如果该实例从始至终都没被使用过,则会造成内存浪费。
总结:线程安全、非懒加载、效率高。
是否推荐:可以使用,但不推荐。
2.2、懒汉模式
2.2.1、非线程安全
代码:
public final class Singleton {
private static Singleton instance;
public String value;
private Singleton(String value) {
this.value = value;
}
public static Singleton getInstance(String value) {
if (instance == null) {
instance = new Singleton(value);
}
return instance;
}
}
这种实现方式在第一次使用的时候才进行初始化,达到了懒加载的效果,较为节省内存资源。但如果遇到并发情况,会出现线程安全问题。
总结:懒加载,效率低
是否推荐:不推荐。
2.2.2、线程安全
代码:
public final class Singleton {
private static Singleton instance;
public String value;
private Singleton(String value) {
this.value = value;
}
public static synchronized Singleton getInstance(String value) {
if (instance == null) {
instance = new Singleton(value);
}
return instance;
}
}
线程安全的懒汉模式是比较常见的一种写法。由于获取实例的静态方法用synchronized修饰,所以也没有线程安全的问题;但是,这种写法每次获取实例都要进行同步(加锁),因此效率较低,并且可能很多同步都是没必要的。
总结:线程安全、懒加载、效率低。
是否推荐:可以使用,但不推荐。
2.3、双重检测机制
代码:
public final class Singleton {
private static volatile Singleton instance; //注意volatile修饰符
public String value;
private Singleton(String value) {
this.value = value;
}
public static Singleton getInstance(String value) {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton(value);
}
}
}
return instance;
}
}
在第一次使用的时候才进行初始化,达到了懒加载的效果;
在进行初始化的时候会进行同步(加锁),没有线程安全问题;并且只有第一次进行初始化才进行同步,因此效率较高。
总结:线程安全、懒加载、效率高。
是否推荐:可以使用。
memory = allocate(); // 1、分配对象的内存空间
ctorInstance(memory);// 2、初始化对象
instance=memory; // 3、设置instance指向刚才分配的内存地址
memory = allocate(); // 1、分配对象的内存空间
instance=memory; // 3、设置instance指向刚才分配的内存地址
ctorInstance(memory);// 2、初始化对象
2.4、静态内部类
代码:
public final class Singleton {
//静态内部类
private static class SingletonHolder {
private static final Singleton instance = new Singleton("FOO");
}
public String value;
private Singleton(String value) {
this.value = value;
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
SingletonHolder
getInstanceSingletonHolderSingleton
总结:线程安全、懒加载、效率高。
是否推荐:推荐使用
注:《Java Concurrency in Practice》作者Brian Goetz推荐使用的方式
2.5、枚举类
代码:
public enum Singleton {
INSTANCE("FOO");
public String value;
private Singleton(String val) {
this.value = val;
}
}
enum
枚举类实现是很简洁的一种实现方式,提供了序列化机制,保证线程安全,绝对防止多次实例化,即使是在面对复杂的序列化或者反射攻击的时候。
总结:线程安全、非懒加载、效率高。
是否推荐:推荐使用
注:《Effective Java》作者Joshua Bloch推荐使用的方式。
三、Golang实现单例模式
Golang并不是纯粹意义上的面向对象语言,在实现单例模式上与Java并不完全相同。
实现方式也可分为饿汉式、懒汉式、双重检测,除此之外,也可以利用Golang的sync包实现。
3.1、饿汉模式
这种实现方式在大多数情况下并不推荐使用。
3.1.1、普通实现
代码:
package singleton
type singleton struct {
Val string
}
var instance = &singleton{"FOO"}
func NewInstance() *singleton {
return instance
}
这种方式的单例对象是在包加载时立即被创建。
3.1.1、init函数实现
代码:
type singleton struct {
Val string
}
var instance *singleton
func NewInstance() *singleton {
return instance
}
func init() {
instance = &singleton{"FOO"}
}
init
init
3.2、懒汉模式
3.2.1、非协程安全:
package singleton
type singleton struct {
Val string
}
var instance *singleton
func NewInstance(v string) *singleton {
if instance == nil{
instance = &singleton{Val: v}
}
return instance
}
懒汉模式,能够懒加载,但会造成协程不安全。
不推荐使用。
3.2.2、协程安全:
package singleton
type singleton struct {
val string
}
var (
instance *singleton
m sync.Mutex
)
func NewInstance(v string) *singleton {
m.Lock()
defer m.Unlock()
if instance == nil {
instance = &singleton{Val: v}
}
return instance
}
协程安全,但加锁操作会降低效率。
不推荐使用。
3.3、双重检测
package singleton
type singleton struct {
Val string
}
var (
instance *singleton
m sync.Mutex
)
func NewInstance(v string) *singleton {
if instance == nil {
m.Lock()
defer m.Unlock()
if instance == nil {
instance = &singleton{Val: v}
}
}
return instance
}
在进行初始化的时候会进行加锁,没有线程安全问题,并且只有第一次进行初始化才进行同步,因此效率较高。
可以使用。
3.4、使用sync.once
package singleton
type singleton struct {
Val string
}
var (
instance *singleton
once sync.Once
)
func NewInstance(v string) *singleton {
once.Do(func() {
instance = &singleton{Val: v}
})
return instance
}
Once 是一个结构体,在执行 Do 方法的内部通过 atomic 操作和加锁机制来保证并发安全,且 once.Do 能够保证多个 goroutine 同时执行时 &singleton {} 只被创建一次。
其实 Once 并不神秘,其内部实现跟上面使用的双重锁定机制非常类似,只不过把 instance == nil 换成了 atomic 操作,感兴趣的同学可以查看下其对应源码。
推荐使用。
四、总结
方式 | 优点 | 缺点 |
---|---|---|
饿汉式 | 线程安全、效率高 | 非懒加载 |
非线程安全懒汉式 | 懒加载、效率高 | 非线程安全 |
线程安全懒汉式 | 线程安全、懒加载 | 效率低 |
双重检测 | 线程安全、懒加载、效率高 | 无 |
静态内部类(Java) | 线程安全、懒加载、效率高 | 无 |
枚举(Java) | 线程安全、效率高 | 非懒加载 |
使用sync.once (Golang) | 线程安全、效率高、懒加载 | 无 |
参考: