单例模式是最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问
其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:1、单例类只能有一个实例。2、单例类必须自己创建自己的唯一实例。3、单例类必须给所有其他对象提供
这一实例。
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决:一个全局使用的类频繁地创建与销毁。
何时使用:当您想控制实例数目,节省系统资源的时候。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码:构造函数是私有的。
应用实例:
1、一个班级只有一个班主任。
2、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文
件的现象,所以所有文件的处理必须通过唯一的实例来进行。
3、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印
机打印同一个文件。
优点:
1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓
存)。
2、避免对资源的多重占用(比如写文件操作)。
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例
化。
使用场景:
1、要求生产唯一序列号。
2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
注意事项:getInstance() 方法中需要使用同步锁防止多线程同时进入造成 instance 被多次实例化。
适用性:
当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时。
当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。
单例模式分为饿汉式和懒汉式:
饿汉式:在该类初始化的时候就创建实例对象,线程是安全的。
懒汉式:首次使用单例实例的时候创建,之后使用时再判断单例实例是否已创建,如果没有则创建实例。
单例模式采用了饿汉式和懒汉式两种实现,饿汉式的实现简单并且可以将问题及早暴露,懒汉式虽然支持延迟载,
但是这只是把冷启动时间放到了第一次使用的时候,并没有本质上解决问题,并且为了实现懒汉式还不可避免的需
要加锁。
package main
import "fmt"
type EagerSingleton struct {
}
var eagerSingletonObj *EagerSingleton
func init() {
eagerSingletonObj = &EagerSingleton{}
}
func GetInstance() *EagerSingleton {
return eagerSingletonObj
}
func main() {
// true
fmt.Println(GetInstance() == GetInstance())
}
package main
import (
"fmt"
"sync"
)
type LazySingleton struct {
}
var (
lazySingletonInstance *LazySingleton
lazyOnce = &sync.Once{}
)
func GetLazySingletonInstance() *LazySingleton {
if lazySingletonInstance == nil {
lazyOnce.Do(func() {
lazySingletonInstance = &LazySingleton{}
})
}
return lazySingletonInstance
}
func main() {
// true
fmt.Println(GetLazySingletonInstance() == GetLazySingletonInstance())
}
package main
import (
"fmt"
"sync"
)
type DoubleCheckSingleton struct {
}
var lock = &sync.Mutex{}
var doubleCheckSingletonInstance *DoubleCheckSingleton
func GetDoubleCheckSingletonInstance() *DoubleCheckSingleton {
if doubleCheckSingletonInstance == nil {
lock.Lock()
defer lock.Unlock()
if doubleCheckSingletonInstance == nil {
doubleCheckSingletonInstance = &DoubleCheckSingleton{}
}
}
return doubleCheckSingletonInstance
}
func main() {
// true
fmt.Println(GetDoubleCheckSingletonInstance() == GetDoubleCheckSingletonInstance())
}
Java 实现单例模式总共有 5 中方法。
下面的几种方式都是线程安全的。
饿汉式线程安全,调用效率高,但是不能延时加载。
package com.singleton;
public class Singleton1 {
private static Singleton1 instance = new Singleton1();
private Singleton1() {
}
public static Singleton1 getInstance() {
return instance;
}
public static void main(String[] args) {
Singleton1 a1 = Singleton1.getInstance();
Singleton1 a2 = Singleton1.getInstance();
// true
System.out.println(a1 == a2);
}
}
懒汉式线程不安全,调用效率不高,但是能延时加载。
懒汉式本身是线程不安全的,这里添加了 synchronize,所以是线程安全的。
package com.singleton;
public class Singleton2 {
private static Singleton2 instance;
private Singleton2() {
}
public static synchronized Singleton2 getInstance() {
if (instance == null) {
instance = new Singleton2();
}
return instance;
}
public static void main(String[] args) {
Singleton2 a1 = Singleton2.getInstance();
Singleton2 a2 = Singleton2.getInstance();
// true
System.out.println(a1 == a2);
}
}
双检锁也就是双重锁判断机制。
package com.singleton;
public class Singleton3 {
private volatile static Singleton3 instance;
private Singleton3() {
}
public static Singleton3 getInstance() {
if (instance == null) {
synchronized (Singleton3.class) {
if (instance == null) {
instance = new Singleton3();
}
}
}
return instance;
}
public static void main(String[] args) {
Singleton3 a1 = Singleton3.getInstance();
Singleton3 a2 = Singleton3.getInstance();
// true
System.out.println(a1 == a2);
}
}
线程安全,调用效率高,可以延时加载。
package com.singleton;
public class Singleton4 {
private static class SingletonClassInstance {
private static final Singleton4 instance = new Singleton4();
}
private Singleton4() {
}
public static Singleton4 getInstance() {
return SingletonClassInstance.instance;
}
public static void main(String[] args) {
Singleton4 a1 = Singleton4.getInstance();
Singleton4 a2 = Singleton4.getInstance();
// true
System.out.println(a1 == a2);
}
}
线程安全,调用效率高,不能延时加载,可以天然的防止反射和反序列化调用。
package com.singleton;
public enum Singleton5 {
// 枚举元素本身就是单例
INSTANCE;
// 添加自己需要的操作
public void singletonOperation() {
System.out.println("Operation!");
}
}
package com.singleton;
public class Test {
public static void main(String[] args) {
Singleton5 singletonA = Singleton5.INSTANCE;
Singleton5 singletonB = Singleton5.INSTANCE;
// true
System.out.println(singletonA == singletonB);
// Operation!
singletonA.singletonOperation();
// Operation!
singletonB.singletonOperation();
}
}
package com.singleton;
import java.util.concurrent.atomic.AtomicReference;
public class Singleton6 {
private static final AtomicReference<Singleton6> INSTANCE = new AtomicReference<>();
private Singleton6() {
}
public static final Singleton6 getInstance() {
while (true) {
Singleton6 instance = INSTANCE.get();
if (null != instance) {
return instance;
} else {
INSTANCE.compareAndSet(null, new Singleton6());
return INSTANCE.get();
}
}
}
public static void main(String[] args) {
// true
System.out.println(Singleton6.getInstance() == Singleton6.getInstance());
}
}
java 并发库提供了很多原⼦类来⽀持并发访问的数据安全性:
AtomicInteger 、 AtomicBoolean 、 AtomicLong 、 AtomicReference
AtomicReference 可以封装引⽤⼀个 V 实例,⽀持并发访问如上的单例⽅式就是使⽤了这样的⼀个特点。
使⽤ CAS 的好处就是不需要使⽤传统的加锁⽅式保证线程安全,而是是依赖于 CAS 的忙等算法,依赖于底层
硬件的实现,来保证线程安全。相对于其他锁的实现没有线程的切换和阻塞也就没有了额外的开销,并且可以
⽀持较⼤的并发性。
当然 CAS 也有⼀个缺点就是忙等,如果⼀直没有获取到将会处于死循环中。