一、单例模式
所谓的单例模式,就是采取一定的方法,保证在整个的软件系统中,针对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(如:getInstance()方法)
二、单例模式注意事项和细节说明
1、单例模式保证了系统内存中只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高性能
2、当想实例化一个单例类的时候,必须记住要使用相应的获取对象的方法,而不是使用new关键字
3、单例模式的使用场景:
需要频繁进行创建和销毁的对象、创建对象时耗时过多或者耗费资源过多(即:重量级对象),但经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)
表示创建出来的对象,全局唯一,所有关于这个对象的操作,都用这个对象,在配置文件用scope关键字来设置
测试:
1、配置文件配置
<bean id="student" class="com.guohui.pojo.Student" c:name="李四" c:address="河北" scope="singleton"/>
</beans>
2、测试
@Test
public void TestPC(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Student student1 = context.getBean("student", Student.class);
Student student2 = context.getBean("student", Student.class);
System.out.println(student1 == student2);
}
}
3、控制台
一、理解什么是饿汉式
饿汉式单例模式就是会在创建对象的时候,会把类中的对象都创建出来,有可能会造成内存的浪费
二、手写饿汉式单例模式
原理是通过私有化构造器,外在的方法是无法创建此类对象的,那么每次调用getInstance方法,都会返回这个类中自己创建的同一个类,保证了创建对象的唯一性
//饿汉式单例
public class Hungry {
//饿汉式会在创建对象的时候把下面的对象都创建,可能会浪费空间
private byte[] data1 = new byte[1024*1024];
private byte[] data2 = new byte[1024*1024];
private byte[] data3 = new byte[1024*1024];
private byte[] data4 = new byte[1024*1024];
//私有化构造器,这样别人就无法创建这个单例对象了
private Hungry(){
}
//在单例模式的类中创建这个对象,这样就能保证单例的类是全局唯一的了
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
调用的时候直接通过对象.静态方法即可获得这个全局唯一的对象实例。通过验证可知,假设我们调用两次这个方法,那么比较两个方法创建对象的哈希值是相同的
三、饿汉式(静态常量)方法的优缺点
1、优点
这种写法比较简单,他是在类装载的时候就完成了实例化,避免了线程同步问题
2、缺点
在类装载的时候完成实例化,没有达到懒加载(懒加载,即我用到某个对象,再加载或者创建)的效果,如果从开始至终,从来没有使用这个实例,就会造成内存资源的浪费
一、静态代码单例模式块写法
这种方式的调用也是和静态常量的方法一致,通过类名.getInstance方法来获取唯一的对象实例
二、优缺点
这种方式的优缺点和静态常量的优缺点是一致的
一、线程不安全的懒汉式
二、优缺点
1、优点
确实起到了懒加载的效果,但是只能在单线程下使用
2、缺点
如果在多线程下,一个线程进入到if(instance == null)判断语句块还未来得及往下执行,另一线程也通过了这个判断语句,便会产生多个实例。所以在多线程的情况下不可以使用这种方式
三、结论
实际开发中,不要使用这种方式!
一、线程安全的懒汉式
这种方式确实能解决线程不安全的问题,当有多个线程执行这个getInstance方法的时候,后面的线程就会排队,等待释放锁再执行这个方法
主线程中运行这个方法,确实可以得到唯一的对象实例
二、优缺点
1、优点
解决了线程不安全的问题
2、缺点
效率太低了,每个线程想获得类的实例的时候,执行getInstance()方法都要进行同步。而实际上这个方法只执行一次实例化代码就够了,后面的想要获得这个对象实例直接通过return就行了,方法进行同步效率太低了
三、结论
实际开发中,不推荐使用这种方式
一、同步代码块方式
二、优缺点
一、理解什么是懒汉式
所谓懒汉式就是上来先不创建对象,如果我要用这个对象,再进行创建,实现懒加载!
二、手写DCL懒汉式
//懒汉式单例模式
public class LazyMan {
//私有构造器
private LazyMan(){
System.out.println(Thread.currentThread().getName() + "创建了LazyMan对象");
}
//加上volatile关键字,避免指令重排
private volatile static LazyMan lazyMan;
//双重检测锁模式的懒汉式 DCL懒汉式
public static LazyMan getInstance(){
if (lazyMan == null){
synchronized (LazyMan.class){ //这里直接用synchronized锁类的模板,全局唯一
if (lazyMan == null){
lazyMan = new LazyMan();
/*创建对象的过程并不是原子性的,存在以下的步骤
* 1、分配内存空间
* 2、执行构造方法,初始化对象
* 3、把这个对象指向这个空间
* 假设存在线程A和B
* 此时就会存在一个指令重排的现象,如果A正常执行期望的123,是没有问题的
* 如果线程A执行了132,线程B再执行的时候会直接认为lazyMan!=null,此时的lazyMan还没完成构造,空间是虚无的
* 因此,必须要使lazyMan对象避免指令重排,在定义它的时候加上volatile关键字!!!
* */
}
}
}
return lazyMan;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
getInstance(); //此时控制台就会只打印构造器中的一条sout语句,并且只能创建1个对象
}).start();
}
}
}
二、优缺点
1、优点
(1)Double-Check双重检查是多线程中经常用到的,如代码中所示,我们进行了两次的if(lazyMan == null)检查,这样就可以保证线程安全了
(2)这样,实例化代码只用执行一次,后面再次访问的时候,通过if判断就会直接return实例化对象,也避免了反复进行方法的同步
(3)终于实现了线程安全、懒加载、高效率
三、结论
在实际开发中,推荐使用这种单例设计模式
一、静态内部类单例模式
手写Holder静态内部类单例模式
//Holder静态内部类单例模式
public class Holder {
//凡是单例模式,都要先私有化构造器
private Holder(){}
//创建getInstance获取单例类的方法,里面调用内部类,创建Holder类对象
public static Holder getInstance(){
return InnerClass.HOLDER;
}
//创建静态内部类
public static class InnerClass{
private static final Holder HOLDER = new Holder();
}
}
实现原理:
当外部的Holder类装载的时候,静态内部类不会跟着装载,这样就实现了懒加载的情况;
当内部类开始装载的时候,首先要了解类在装载的时候是线程安全的,所以这种设计模式也保证了线程的安全
二、优缺点
一、代码实现
二、优缺点
至此,关于设计模式中的单例模式介绍完毕,内容比较多,建议收藏反复学习,后续还会持续更新,敬请期待~~~