Java 单例模式

简介: 前言:昨天公司计划把项目中的部分功能做出SDK的形式,供其他公司的产品使用,所以不得不重新研究一下单例模式。   为什么单例 1、在内存中只有一个对象,节省内存空间。避免频繁的创建销毁对象,可以提高性能。
前言:昨天公司计划把项目中的部分功能做出SDK的形式,供其他公司的产品使用,所以不得不重新研究一下单例模式。

 

为什么单例

1、在内存中只有一个对象,节省内存空间。避免频繁的创建销毁对象,可以提高性能。避免对共享资源的多重占用。可以全局访问。

2、确保一个类只有一个实例,自行实例化并向系统提供这个实例

 

单例需要注意的问题

1、线程安全问题

2、资源使用问题

 

实际上本文就是在讨论这两个问题 

 

1、方式1 (饿汉式)

package com;

public class Singleton {

	private static Singleton instance = new Singleton() ;


	private Singleton(){

	}

	public static Singleton getInstance() {  
		return  instance ;  
	}  

}

  优点:在未调用getInstance() 之前,实例就已经创建了,天生线程安全

      缺点:如果一直没有调用getInstance() , 但是已经创建了实例,造成了资源浪费。

 

2、方式1 (懒汉式)

package com;

public class Person {
	private static Person person ;

	private Person(){
		
	}
	
	public static Person get(){
		if ( person == null ) {
			person = new Person() ;
		}
		return person ;
	}

}

优点:get() 方法被调用的时候,才创建实例,节省资源。

缺点:线程不安全。

  这种模式,可以做到单例模式,但是只是在单线程中是单例的,如果在多线程中操作,可能出现多个实例。

     测试:启动20个线程,然后在线程中打印 Person 实例的内存地址

package com;

public class A1  {
	
	public static void main(String[] args) {
		
		for ( int i = 0 ;  i < 20 ; i ++ ) {
			new Thread( new Runnable() {
				
				@Override
				public void run() {
					System.out.println( Person.get().hashCode() );
				}
			}).start(); ;
			
		}
		
	}
}

  结果:可以看到出现了两个实例

    造成的原因:

线程A希望使用Person,调用get()方法。因为是第一次调用,A就发现 person 是null的,于是它开始创建实例,就在这个时候,CPU发生时间片切换,线程B开始执行,它要使用 Person ,调用get()方法,同样检测到 person 是null——注意,这是在A检测完之后切换的,也就是说A并没有来得及创建对象——因此B开始创建。B创建完成后,切换到A继续执行,因为它已经检测完了,所以A不会再检测一遍,它会直接创建对象。这样,线程A和B各自拥有一个 Person 的对象——单例失败!

总结:1、可以实现单线程单例 

        2、多线单例无法保证

改进:1、加锁

 

3、 用synchronized  加锁同步

package com;

public class Person {
	private static Person person ;

	private Person(){

	}

	public synchronized static Person get(){
		if ( person == null ) {
			person = new Person() ;
		}
		return person ;
	}



}

  经过测试,已经可以满足多线程的安全问题了,synchronized修饰的同步块可是要比一般的代码段慢上几倍的!如果存在很多次get()的调用,那性能问题就不得不考虑了!

优点:

1、满足单线程的单例

2、满足多线程的单例

缺点:

1、性能差

 

4、改进性能  双重校验

package com;

public class Person {
	private static Person person ;

	private Person(){
	}

	public synchronized static Person get(){
		if ( person == null ) {
			synchronized ( Person.class ){
				if (person == null) { 
					person = new Person(); 
				} 
			}
		}
		return person ;
	}

}

  首先判断person 是不是为null,如果为null,加锁初始化;如果不为null,直接返回 person 。整个设计,进行了双重校验。

优点:

1、满足单线程单例

2、满足多线程单例

3、性能问题得以优化

 

缺点:第一次加载时反应不快,由于java内存模型一些原因偶尔失败

 

5、volatile 关键字,解决双重校验带来的弊端

package com;

public class Person {

	private static volatile Person person = null ;


	private Person(){

	}

	public static Person getInstance(){
		if ( person == null ) {
			synchronized ( Person.class ){
				if ( person == null ) {
					person = new Person() ;
				}
			}
		}

		return person ;
	}

}

  假设没有关键字volatile的情况下,两个线程A、B,都是第一次调用该单例方法,线程A先执行 person = new Person(),该构造方法是一个非原子操作,编译后生成多条字节码指令,由于JAVA的指令重排序,可能会先执行 person 的赋值操作,该操作实际只是在内存中开辟一片存储对象的区域后直接返回内存的引用,之后 person 便不为空了,但是实际的初始化操作却还没有执行,如果就在此时线程B进入,就会看到一个不为空的但是不完整 (没有完成初始化)的 Person对象,所以需要加入volatile关键字,禁止指令重排序优化,从而安全的实现单例。

     补充:看了图片加载框架 Glide (3.7.0版) 源码,发现glide 也是使用volatile 关键字的双重校验实现的单例,可见这种方法是值得信赖的。

  

6、静态内部类

package com;

public class Person {

	private Person(){

	}

	private static class PersonHolder{
		/**
		 * 静态初始化器,由JVM来保证线程安全
		 */
		private static Person instance = new Person();
	}


	public static Person getInstance() {
		return PersonHolder.instance;
	}

}

  优点:

1、资源利用率高,不执行getInstance()不被实例,可以执行该类其他静态方法

 

7、枚举类实现单例

package com;

public enum Singleton {
	INSTANCE ;

	public void show(){
		// Do you need to do things 
	}
}

  使用

获取实例对象:Singleton.INSTANCE

调用其他方法:Singleton.INSTANCE.show();

     测试

package com;

public class A1  {
	
	public static void main(String[] args) {
		
		for ( int i = 0 ;  i < 20 ; i ++ ) {
			new Thread( new Runnable() {
				
				@Override
				public void run() {
					System.out.println( Singleton.INSTANCE.hashCode() ) ;
				}
			}).start(); ;
			
		}
	}
}

      结果:

  

 

   可以看到用20个线程去访问对象的内存地址, 内存地址都是一样的,保证了线程的安全性。

 

 

总结:

1、上面的7中方法,都实现了某种程度的单例,各有利弊,根据使用的场景不同,需要满足的特性不同,选取合适的单例方法才是正道。

2、对线程要求严格,对资源要求不严格的推荐使用:1    饿汉式

3、对线程要求不严格,对资源要求严格的推荐使用:2    懒汉式

4、对线程要求稍微严格,对资源要求严格的推荐使用:4  双重加锁

5、同时对线程、资源要求非常严格的推荐使用:5 、 6 

 

 

相关文章
|
3月前
|
设计模式 缓存 安全
Java设计模式的单例模式应用场景
Java设计模式的单例模式应用场景
42 4
|
1月前
|
设计模式 存储 负载均衡
【五】设计模式~~~创建型模式~~~单例模式(Java)
文章详细介绍了单例模式(Singleton Pattern),这是一种确保一个类只有一个实例,并提供全局访问点的设计模式。文中通过Windows任务管理器的例子阐述了单例模式的动机,解释了如何通过私有构造函数、静态私有成员变量和公有静态方法实现单例模式。接着,通过负载均衡器的案例展示了单例模式的应用,并讨论了单例模式的优点、缺点以及适用场景。最后,文章还探讨了饿汉式和懒汉式单例的实现方式及其比较。
【五】设计模式~~~创建型模式~~~单例模式(Java)
|
21天前
|
设计模式 安全 Java
Java 单例模式,背后有着何种不为人知的秘密?开启探索之旅,寻找答案!
【8月更文挑战第30天】单例模式确保一个类只有一个实例并提供全局访问点,适用于需全局共享的宝贵资源如数据库连接池、日志记录器等。Java中有多种单例模式实现,包括饿汉式、懒汉式、同步方法和双重检查锁定。饿汉式在类加载时创建实例,懒汉式则在首次调用时创建,后者在多线程环境下需使用同步机制保证线程安全。单例模式有助于提高代码的可维护性和扩展性,应根据需求选择合适实现方式。
29 1
|
24天前
|
SQL 设计模式 安全
Java编程中的单例模式深入解析
【8月更文挑战第27天】本文旨在探索Java中实现单例模式的多种方式,并分析其优缺点。我们将通过代码示例,展示如何在不同的场景下选择最合适的单例模式实现方法,以及如何避免常见的陷阱。
|
20天前
|
设计模式 安全 Java
Java编程中的单例模式深度解析
【8月更文挑战第31天】 单例模式,作为设计模式中的经典之一,在Java编程实践中扮演着重要的角色。本文将通过简洁易懂的语言,逐步引导读者理解单例模式的本质、实现方法及其在实际应用中的重要性。从基础概念出发,到代码示例,再到高级应用,我们将一起探索这一模式如何优雅地解决资源共享和性能优化的问题。
|
20天前
|
设计模式 安全 Java
Java中的单例模式:理解与实践
【8月更文挑战第31天】在软件设计中,单例模式是一种常用的设计模式,它确保一个类只有一个实例,并提供一个全局访问点。本文将深入探讨Java中实现单例模式的不同方法,包括懒汉式、饿汉式、双重校验锁以及静态内部类等方法。每种方法都有其适用场景和潜在问题,我们将通过代码示例来展示如何根据具体需求选择合适的实现方式。
|
20天前
|
设计模式 安全 Java
Java编程中的单例模式实现与应用
【8月更文挑战第31天】在Java的世界里,单例模式是构建高效且资源友好应用的基石之一。本文将深入浅出地介绍如何通过单例模式确保类只有一个实例,并提供一个全局访问点。我们将探索多种实现方法,包括懒汉式、饿汉式和双重校验锁,同时也会讨论单例模式在多线程环境下的表现。无论你是Java新手还是资深开发者,这篇文章都将为你打开一扇理解并有效应用单例模式的大门。
|
29天前
|
设计模式 SQL 缓存
Java编程中的设计模式:单例模式的深入理解与应用
【8月更文挑战第22天】 在Java的世界里,设计模式是构建可维护、可扩展和灵活的软件系统的基石。本文将深入浅出地探讨单例模式这一经典设计模式,揭示其背后的哲学思想,并通过实例演示如何在Java项目中有效运用。无论你是初学者还是资深开发者,这篇文章都将为你打开一扇洞悉软件设计深层逻辑的大门。
26 0
|
2月前
|
设计模式 安全 Java
Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
60 1
|
1月前
|
设计模式 SQL 安全
单例模式大全:细说七种线程安全的Java单例实现,及数种打破单例的手段!
设计模式,这是编程中的灵魂,用好不同的设计模式,能使你的代码更优雅/健壮、维护性更强、灵活性更高,而众多设计模式中最出名、最广为人知的就是Singleton Pattern单例模式。通过单例模式,我们就可以避免由于多个实例的创建和销毁带来的额外开销,本文就来一起聊聊单例模式。