并发编程-单例
说在前面的话
今天的文章很短,但是很经典,值得你仔细阅读每一个文字…
正如我开篇所说,我们要整理一些java并发编程的学习文档,这一篇就是第八篇:单例模式。这一篇主要聊聊单例的几种实现方式已经适用的场景。
开整
稍微理解一下
专业解释:单例就是确保一个类只有一个实例,并且有一个全局获取这个实例的访问点。
简单的说呢就是一个类只能创建一个实例,任何时候使用这个类的实例对象都是同一个。
基本都是了减少这个类对象创建和销毁过程的消耗。
嗯!
思考思考,一个类如果只有一个实例,必然不能随便创建,所以单例类的关键代码就是构造方法是私有的,不允许在其他地方随便创建。
单例的优点非常明确:因为只有一个实例,所以减少了内存的开销,尤其是创建和销毁的时候减少了资源浪费。避免了对资源的多重占用。
单例的缺点呢:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
单例的使用场景
- 连接池:一个系统中一个数据源的连接池往往都是一个实例。
- 线程池:同上。
- 计数器:不用每次刷新都在数据库里加一次,用单例先缓存起来
- 创建一次消耗的资源过多的类对象。
单例的实现
单例有7种实现方式
方式1:线程不安全的懒汉模式
直接上菜:
- 构造方法私有化
- 提供静态的私有的本类实例对象作为成员变量
- 提供公共的静态的获取本类对象的方法
/**
* @author 戴着假发的程序员
*/
public class Singleton {
// 构造方法私有化
private Singleton(){}
// 私有静态当前类对象作为成员变量
private static Singleton instance;
// 静态的公共的可以获取当前类实例对象的方法
public static Singleton getInstance(){
// 判断instance是否存在,如果存在就直接返回,如果不存在就创建
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
上面的程序非常简单:如果要获取Singleton类的实例对象可以这样写Singleton.getInstance()
你会发现:
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2);// true 额。。。。。多执行几次,有可能也会是false
说明:
- 这个方式一开始并没有创建instance,在第一次使用的时候才会创建,这才是懒汉模式的真谛。有效的实现了延迟加载。节省了内存。
- 这个方式不是线程安全的,可能会有隐患。
- 如果线程A调用getInstance方法,判断了instance为null,正准备创建对象,结果CPU不给资源了,于是就稍微停了一会,这是线程B也调用了getInstance方法,结果判断instance依然是null,于是就创建了instance对象。之后线程A开始执行,线程A不会再判断了,直接创建instance对象,这样的话这个类的对象就被创建了两次。
方式2:线程安全的懒汉模式
上菜:
- 构造方法私有化
- 提供静态的私有的本类实例对象作为成员变量
- 提供公共的静态的同步的获取本类对象的方法
/**
* @author 戴着假发的程序员
*/
public class Singleton1 {
// 构造方法私有化
private Singleton1(){}
// 私有静态当前类对象作为成员变量
private static Singleton1 instance;
// 静态的公共的可以获取当前类实例对象的方法
public static synchronized Singleton1 getInstance(){
// 判断instance是否存在,如果存在就直接返回,如果不存在就创建
if(instance == null){
instance = new Singleton1();
}
return instance;
}
}
这个测试吗很简单:
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2);// true 额。。。这里无论执行多少次都是true
说明:
- 这种方式很明显就是在getInstance的方法上增加synchronized修饰符,这样就成功避免了线程安全的问题。
- 但是我们都知道一旦加锁了,程序效率就会略有降低(不过呢,在JDK1.6之后synchronized做了优化,如果没有大量并发的情况下其实效率影响也不大)
方式3:饿汉模式
饿汉模式关键在这个“饿”字。额… 记得以前我吃饭吃的很着急的时候,我亲爱的老母亲就说:“慢点吃,搞得饿死鬼上身似得”。非常庆幸的是老母亲现在也会这样说我。哈哈哈哈哈。。。。
言归正传:饿汉模式就是在一开始的时候直接创建实例对象
- 构造方法私有化
- 提供静态的私有的本类实例对象作为成员变量,并且直接实例化
- 提供公共的静态的获取本类对象的方法
/**
* @author 戴着假发的程序员
*/
public class Singleton2 {
// 构造方法私有化
private Singleton2(){}
// 私有静态当前类对象作为成员变量
private static Singleton2 instance = new Singleton2();
// 静态的公共的可以获取当前类实例对象的方法
public static synchronized Singleton2 getInstance(){
// 判断instance是否存在,如果存在就直接返回,如果不存在就创建
if(instance == null){
instance = new Singleton2();
}
return instance;
}
}
饿汉模式真的有点饿,一开始就创建了。
但是如果,现在不饿呢,直接把饭煮了是不是会有点浪费。
所以饿汉模式的问题就是如果一开始就把类对象创建好了, 但是这个对象长时间用不到,那么就是有些浪费资源了。
所以呀,还是要看实际的使用场景的。
方式4:双检锁/双重校验锁
程序,必须追求效率和省资源。所以我们希望单利模式中创建对象的方法也是效率高高滴。
但是我们一旦给方法加锁必然降低方法的执行效率,所以双重校验锁可能是一个不错的选择,既能能相对的提高程序效率,又能保证线程安全。
具体的实现就是:
/**
* @author 戴着假发的程序员
*/
public class Singleton3 {
// 构造方法私有化
private Singleton3(){}
// 私有静态当前类对象作为成员变量
private static Singleton3 instance;
// 静态的公共的可以获取当前类实例对象的方法
public static Singleton3 getInstance(){
// 判断instance是否存在,如果存在就直接返回,如果不存在就创建
if (instance==null) {
// 上锁
synchronized (Singleton3.class) {
// 再次判断instance是否存在,如果存在就直接返回,如果不存在就创建
if (instance == null) {
instance = new Singleton3();
}
}
}
return instance;
}
}
方式5:静态内部类
静态内部类的实现方式也叫登记式。是个啥情况呢:
直接上菜:
/**
* @author 戴着假发的程序员
*/
public class Singleton4 {
// 构造方法私有化
private Singleton4(){}
// 准备一个静态内部类
private static class SingletonHolder{
// 申明并且实例化一个外部单利类的实例对象,并且设置为常量。
private final static Singleton4 INSTANCE = new Singleton4();
}
// 静态的公共的可以获取当前类实例对象的方法
public static Singleton4 getInstance(){
// 直接返回内部类的成员常量
return SingletonHolder.INSTANCE;
}
}
这种方式有啥用?
- 在没有调用getInstance方法之前,静态内部了会延迟加载也就是对象的创建会延迟。
- 利用classloader的机制确保了在创建实例是肯定是单线程的。
- 当然了如果你不希望实例对象延迟加载那还是使用饿汉模式比较好。
方式6:枚举
这个方式实在太简单,也好理解(当然你首先要知道啥是枚举)。所以就不多做解释了。
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
关于单利模式的情况就是这样了。
还有其他的并发编程相关的内容,我会持续更新,欢迎关注。
我是”起点编程“的"戴着假发的程序员" 欢迎关注…欢迎评论。。。。。
起点编程-是你我的未来…