今天我想总结一下关于设计模式的学习过程。首先是第一个单例模式的学习,整个设计模式的学习视频网址如下
https://www.bilibili.com/video/av29579073/?p=1
https://www.bilibili.com/video/av29579073/?p=2
https://www.bilibili.com/video/av29579073/?p=3
之前在学习过程中,相信大家多多少少都会接触过关于单例模式的一些知识。比如单例模式分为懒汉模式和饿汉模式,懒汉模式是在类加载的时候创建一个静态实例化对象,而饿汉模式是我们在调用getInstance方法时才第一次进行创建实例化对象。看了这个老师讲课的视频,我对单例模式有了更深一步的理解。
单例模式的实现方式,除了刚才的两个以外,还有三个方法。所以单例方式的实现一共是五种。那么单例模式的使用场景有哪些呢。
– Windows的Task Manager(任务管理器)就是很典型的单例模式
– windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
– 项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,每次new一个对象去读取。 – 网站的计数器,一般也是采用单例模式实现,否则难以同步。
– 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作 ,否则内容不好追加。
– 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
– 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。 – Application 也是单例的典型应用(Servlet编程中会涉及到)
– 在Spring中,每个Bean默认就是单例的,这样做的优点是Spring容器可以管理 – 在servlet编程中,每个Servlet也是单例 – 在spring MVC框架/struts1框架中,控制器对象也是单例。
总结起来,通常在一个系统中,读取配置文件的类只需要在服务器启动时进行一次实例化就足够了,所以涉及到配置的类往往只是采用单例模式。
目录
1.懒汉模式和饿汉模式
2.三种新的单例模式实现方法
3.单例模式是否真的是“单例”?
4.四种实现方法的时间
在展开对单例模式的学习前,我打算先把懒汉模式和饿汉模式的代码进行复习。
public class SingletonDemo1 {
//类初始化时,立即加载这个对象(没有延时加载的优势)。加载类时,天然的是线程安全的!
private static SingletonDemo1 instance = new SingletonDemo1();
private SingletonDemo1(){
}
//方法没有同步,调用效率高!
public static SingletonDemo1 getInstance(){
return instance;
}
}
饿汉模式的代码如上。可以看到,饿汉模式的代码没有涉及到同步,所以不用考虑线程的问题,但是会在类初始化时就立即加载对象,会造成内存的占用。
public class SingletonDemo2 {
//类初始化时,不初始化这个对象(延时加载,真正用的时候再创建)。
private static SingletonDemo2 instance;
private SingletonDemo2(){ //私有化构造器
}
//方法同步,调用效率低!
public static synchronized SingletonDemo2 getInstance(){
if(instance==null){
instance = new SingletonDemo2();
}
return instance;
}
}
那么饿汉式单例模式,采用了方法的同步,因为有可能两个线程在instance为null的时候同时访问到getInstance()方法,这样就有可能创建两个实例化对象。因为采用了方法同步,所以效率会低一些。
public class SingletonDemo3 {
private static SingletonDemo3 instance = null;
public static SingletonDemo3 getInstance() {
if (instance == null) {
SingletonDemo3 sc;
synchronized (SingletonDemo3.class) {
sc = instance;
if (sc == null) {
synchronized (SingletonDemo3.class) {
if(sc == null) {
sc = new SingletonDemo3();
}
}
instance = sc;
}
}
}
return instance;
}
这种叫做双重检测锁方法,它将对于同步的内容放到了if的内部,提高了执行效率。 不必每次获取对象时都进行同步,只有第一次才同步 。但是这种方法受到编译器和jvm的影响,不建议使用。
public class SingletonDemo4 {
private static class SingletonClassInstance {
private static final SingletonDemo4 instance = new SingletonDemo4();
}
private SingletonDemo4(){
}
//方法没有同步,调用效率高!
public static SingletonDemo4 getInstance(){
return SingletonClassInstance.instance;
}
}
静态内部类实现单例模式,这种方法在我看来是将懒汉模式和饿汉模式进行了结合。首先它没有饿汉模式的同步,调用效率会提高。其次外部类的实例化对象交由内部类进行保管,这样在类加载时并不会创建instance实例化对象,而是在调用getInstance()时才会创建instance对象。
public enum SingletonDemo5 {
//这个枚举元素,本身就是单例对象!
INSTANCE;
//添加自己需要的操作!
public void singletonOperation(){
}
}
枚举类,这种方法是最安全的,因为它不会被反射破坏单例模式。
单例模式我也说过了只会创建一个对象,那么刚才提到的五种方法是不是真的保证运行过程中只会创建一个单例对象呢。
首先以懒汉模式为例进行测试
public class SingletonDemo6 implements Serializable {
//类初始化时,不初始化这个对象(延时加载,真正用的时候再创建)。
private static SingletonDemo6 instance;
private SingletonDemo6(){
if(instance!=null){
}
}
//方法同步,调用效率低!
public static synchronized SingletonDemo6 getInstance(){
if(instance==null){
instance = new SingletonDemo6();
}
return instance;
}
}
测试代码如下
通过反射的方式直接调用私有构造器
Class clazz = (Class) Class.forName("com.bjsxt.singleton.SingletonDemo6");
Constructor c = clazz.getDeclaredConstructor(null);
c.setAccessible(true);
SingletonDemo6 s3 = c.newInstance();
SingletonDemo6 s4 = c.newInstance();
System.out.println(s3);
System.out.println(s4);
通过打印可以发现s3和s4的值并不相同,可以得出反射破坏了单例模式的唯一性。
解决办法,就是在私有构造方法里面加上一个判断,如果instance对象不为null时,直接抛出异常阻止实例化对象的过程。
private SingletonDemo6(){ //私有化构造器,这样即使使用反射通过构造方法也无法创造两个对象
if(instance!=null){
throw new RuntimeException();
}
}
除了反射可以破坏单例模式,使用反序列化的方式,也是可以的。
FileOutputStream fos = new FileOutputStream("d:/a.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s1);
oos.close();
fos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/a.txt"));
SingletonDemo6 s3 = (SingletonDemo6) ois.readObject();
System.out.println(s3);
这里的s3和s1(只是一个单例对象)是不相同的,解决的办法就是在原本的类中加上如下的方法。
//反序列化时,如果定义了readResolve()则直接返回此方法指定的对象。而不需要单独再创建新对象!
private Object readResolve() throws ObjectStreamException {
return instance;
}
饿汉式 22ms 懒汉式 636ms 静态内部类式 28ms 枚举式 32ms 双重检查锁式 65ms
测试的代码如下:
public class Client3 {
public static void main(String[] args) throws Exception {
long start = System.currentTimeMillis();
int threadNum = 10;
final CountDownLatch countDownLatch = new CountDownLatch(threadNum);
for(int i=0;i
其中引入了一个CountDownLatch类,这个类是为了在所有线程结束之后再进行main线程的执行,也就是执行
long end = System.currentTimeMillis();语句
这样测试的时间才是我声明的10个线程的执行时间。
~~ 画外音
博主最近要考研了,每天都是晚上回来用一些时间总结和学习java,个人非常喜欢java的学习,以后考研呢也是要走大数据的方向,如果你对大数据或者java非常喜欢可以加我的qq: 1641530151。我们一起学习努力,非诚勿扰yo。