单例模式是java中最简单的一种设计模式。所谓单例模式,就是整个程序只允许有一个类的实例,并且向整个系统中提供此事例。有点像C++中被定义为static的变量。
比如当我们某个程序只有一个关于打印机的端口,那么我们必须将这个端口设计为单例的,因为如果当多台电脑使用这个打印机的时候,只允许第一台电脑对此进行操作,否则打印机将引起混乱,
- 单例模式特点
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
- 优点:
- 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
- 避免对资源的多重占用(比如写文件操作)。
- 缺点:
- 没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
- 使用场景:
- 要求生产唯一序列号。
- WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
- 创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等
说了不少,但是还是不清楚什么是单例模式。作为程序猿,我们更直观的是通过代码了解一个模式。
在说代码之前,我们要说说单例模式的前提内容:
- java锁机制
上一种通俗意义上的单例模式代码:
public class SingleClass {
//创建对象
private static SingleClass singleClass = new SingleClass();
//设置为私有成员函数,无法在类外被实例化
private SingleClass(){}
//设置唯一获取singleClass对象的方法
public static SingleClass getSingleClass(){
return singleClass;
}
//设置方法表明实例化该对象
public void show(){
System.out.println("方法已被实例化");
}
}
public class SingleMode {
public static void main(String[] args) {
//会报错,由于这种实例化被设置为私有函数,不被允许类外访问
//SingleClass singleClass = new SingleClass();
SingleClass singleClass = SingleClass.getSingleClass();
//调用函数
singleClass.show();
}
}
单例模式的多种实现方式
- 第一种实现方式:懒汉式,线程不安全
public class SingleClass {
private static SingleClass singleClass;
private SingleClass() {}
public static SingleClass getSingleClass() {
if (singleClass == null) {
singleClass = new SingleClass();
}
return singleClass;
}
}
由于上述代码没有对SingleClass加锁,因此线程不安全。
- 第二种实现方式:懒汉式,线程安全
public class SingleClass {
private static SingleClass singleClass;
private SingleClass() {}
public static synchronized SingleClass getSingleClass() {
if (singleClass == null) {
singleClass = new SingleClass();
}
return singleClass;
}
}
对方法getSingleClass加锁,保证每次只能有一个线程访问该方法,可以用于多线程。但是也由于上锁之后,该方法效率降低。适用于不频繁调用个SingleClass的程序。
- 第三种实现方式:饿汉式,线程安全
public class SingleClass {
private static SingleClass singleClass = new SingleClass();
private SingleClass() {}
public static SingleClass getSingleClass() {
return singleClass;
}
}
该方法由于没有加锁,所以执行效率会比较高,但是类在加载的时候会初始化,造成内存浪费。
那么这个方法是线程安全的么?当然是。
这涉及到JVM的一些内容,在JVM中,类加载初始化时就创建好一个静态的对象供外部使用。因此本身就是线程安全的。(唉,不会的东西太多了,课程还复杂,有时间再看看《深入理解JAVA虚拟机》吧)
- 第四种实现方式:双检锁/双重校验锁(DCL,即 double-checked locking)
public class SingleClass {
private static volatile SingleClass singleClass;
private SingleClass() {}
public static SingleClass getSingleClass() {
if (singleClass == null) {
synchronized (SingleClass.class) {
if (singleClass == null) {
singleClass = new SingleClass();
}
}
}
return singleClass;
}
}
volatile关键字和synchronized关键字区别就是:volatile不具备原子性。因此多线程访问volatile修饰时,若未执行完将会回滚。
该实现方式利用了双重锁,当singleClass为空的时候,锁住该类,进行对象的初始化。线程安全,且在进入实例化方法的时候再对该类上锁,提升了程序的运行效率。
- 第五种实现方式:登记式/静态内部类
public class SingleClass {
private static class SingletonHolder {
private static final SingleClass singleClass = new SingleClass();
}
private SingleClass() {}
public static final SingleClass getSingleClass() {
return SingletonHolder.singleClass;
}
}
这种方式,利用public static final 关键字,在显式调用getSingleClass方法的时候,将类和变量初始化并装载在常量区(即不可被改变)。因此这条特性决定了多线程的安全。
- 第六种实现方式:枚举
public enum SingleClass{
singleClass;
public void method(){
System.out.println(singleClass+"枚举最简单!");
}
}
public class SingleMode {
public static void main(String[] args) {
SingleClass singleClass =SingleClass.values()[0];
singleClass.method();
}
}
虎躯一震······这也太简单了吧。。他自动支持序列化机制,绝对防止多次实例化。
singleClass是一个常量,enum本身也是线程安全的,因此也不存在被改写的情况。。。。
有一些地方待更新!