单例模式
为什么要使用单例模式
有时候系统只需要一个对象,比如 数据库连接对象、线程池对象 等
线程池这种对象如果建立多个就会失去它的意义,因为线程池本来就是用来管理多个线程的只需要一个即可
数据库连接对象使用单例并不是指只有一个数据库连接,数据库连接对象里面保存的只是数据库连接相关的信息,比如数据库类型、用户名、密码、URL、字符集等
实际创建个多少TCP连接和实际的并发量有关,因此数据库连接对象也不必每次查询SQL的时候去创建对象,这样就显得多余和麻烦了
这种为了让系统中只存在一个对象的模式就叫 单例模式
单例模式实现方法
- 饿汉式
- 方法级别锁、双重检查锁
- 静态内部类
- 枚举
饿汉式单例
饿汉式单例指的就是在系统初始化的时候就创建对象
优点是无需考虑多线程问题
缺点是无法懒加载,万一对象始终用不到那就白加载了
Java类成员变量初始化实现
public class DataBaseManager {
//初始化时就创建
private final static DataBaseManager db = new DataBaseManager("lyer","55555");
private String username;
private String password;
//必须隐藏构造方法 外界无法创建
private DataBaseManager(String username,String password){
this.username = username;
this.password = password;
}
public static DataBaseManager getInstance(){
return db;
}
}
Go全局变量实现
go中可以通过 全局变量 或则init
函数实现,不过这不是最佳实现方式
var database1 = &DataBase{}
func main() {
database1.Ping()
}
懒汉单例
懒汉单例就是在需要时候再进行创建,只有在第一次需要的时候再创建对象,懒汉模式唯一需要确定的就是 并发安全问题 如果在判断是否为null
的过程中触发了线程调度那么就会破坏单例,因此就需要加锁、双重检查等方式来保证单例
Java方法锁
这种方式每次进入都需要加锁,锁的粒度太大
synchronized public static LoadBalancer getInstance() {
if (lb == null) {
lb = new LoadBalancer();
}
return lb;
}
Java双重检查锁
这种方式的锁粒度更细,但是需要两次检查是否为null
,可能第一次if判断由于线程调度可能会进入两个多个线程,最终只有一个线程能抢到锁并且创建对象,进入第一次if的线程会继续抢锁然后创建对象,因此需要在一次判断对象是否为null
防止创建多个对象,如果后面抢到锁的线程则判断对象不为null就不会再创建对象了
指令重排可以参照这篇文章一个单例模式中volatile关键字引发的思考
public class LoadBalancer {
// 阻止变量访问前后的指令重排
private static volatile LoadBalancer lb;
//Server name collection
private List<String> serverList;
private Integer counter;
private LoadBalancer() {
this.serverList = new ArrayList<String>();
this.counter = 0;
}
//double check 双重检查
public static LoadBalancer getInstance(){
if(lb==null){
synchronized (LoadBalancer.class){
if (lb==null){
lb = new LoadBalancer();
}
}
}
return lb;
}
}
Java静态内部类实现
内部静态类不会自动初始化,只有调用静态内部类的方法,静态域,或者构造方法的时候才会加载静态内部类,这种也可以实现懒汉式单例,同时也是并发安全的
public class DataBaseManagerStatic {
private String username;
private String password;
private DataBaseManagerStatic(String username, String password){
this.username = username;
this.password = password;
}
//static inner class
private static class DataBaseManager{
private final static DataBaseManagerStatic db = new DataBaseManagerStatic("lyer","55555");
}
public static DataBaseManagerStatic getInstance(){
return DataBaseManager.db; //如果第一次调用,此时静态内部类才会被初始化
}
}
Go Once实现 (最佳模式)
go可以使用标准库的once
对象,此函数能保证只执行一次代码
type DataBase struct{}
func (db *DataBase) Ping() {
}
var (
database *DataBase
once sync.Once
)
func DataBaseInstance() *DataBase {
once.Do(func() {
database = &DataBase{}
})
return database
}
Java枚举实现单例(最佳模式)
下面是一个负载均衡器的单例模式实现,使用Java枚举的特性,该实现方式是Java单例的最佳实现方式,枚举其实是一种 饿汉式单例 ,enum
类型里面其实是用 静态类+静态代码块 来实现的,在枚举类被加载的时候就会立即创建对象
enum LoadBalancerEnum {
LB;
//Server name collection
private List<String> serverList = new ArrayList<String>();
private Integer counter = 0;
public void addServer(String serverName) {
//if contain then skip
if (this.serverList.contains(serverName)) {
return;
}
this.serverList.add(serverName);
}
public void delServer(String serverName) {
this.serverList.remove(serverName);
}
//RoundRobin
public String getServer() {
String result = this.serverList.get(this.counter);
this.counter = (this.counter + 1) % this.serverList.size();
return result;
}
}
//3个都是同一个对象
LoadBalancerEnum lb1 = LoadBalancerEnum.LB;
LoadBalancerEnum lb2 = LoadBalancerEnum.LB;
LoadBalancerEnum lb3 = LoadBalancerEnum.LB;
lb1.addServer("localhost:9090");
lb1.delServer("localhost:9090");
lb1.getServer();
单例在Spring中的应用
Spring中所有的Bean
都是单例的,如果显式声明需要多个对象则会使用原型模式创建多个对象