*3.单例设计模式
一.概念
所谓单例设计模式,就是采取一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)
二.饿汉式
1.第一种:静态常量
步骤如下:
构造器私有化(防止new得到对象)、类的内部创建对象、向外暴露一个静态的公共方法(getInstance()返回该类的实例)
优点:
这种写法比较简单,就是在类装载的时候完成实例化,避免了线程同步问题
缺点:
在类加载的时候完成实例化,没有达到Lazy loading的效果,如果从始至终从未使用过这个实例,则会造成内存的浪费。
总结:
可用,可能会造成内存浪费。
public class SingletonTest01 {
public static void main(String[] args) {
Singleton instance=Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1 == instance2);//true
System.out.println(instance1.hashCode());//两个的hashCode输出一致
System.out.println(instance2.hashCode());
}
}
//饿汉式(静态变量)
class Singleton{
// 1.构造器私有化,外部不能new
private Singleton(){}
// 2.本类内部创建对象实例
private final static Singleton instance=new Singleton();
// 3.提供一个公有的静态方法,返回实例对象
public static Singleton getInstance() {
return instance;
}
}
2.第二种:静态代码块
这种方式和上面的方式类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,,初始化类的实例,优缺点和上面一样
同样可能会造成内存的浪费
package singleton;
public class SingletonTest02 {
public static void main(String[] args) {
Singleton2 instance2=Singleton2.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1 == instance2);//true
System.out.println(instance1.hashCode());//两个的hashCode输出一致
System.out.println(instance2.hashCode());
}
}
class Singleton{
// 1.构造器私有化,外部不能new
private Singleton(){}
// 2.本类内部创建对象实例
private static Singleton instance;
static {//在静态代码块中创建单例对象
instance=new Singleton();
}
// 3.提供一个公有的静态方法,返回实例对象
public static Singleton getInstance() {
return instance;
}
}
三.懒汉式
第三种:使用时加载(线程不安全:不推荐使用)
1.起到了lazy loading(用到才去加载,没用到就不加载)的效果,但是只能在单线程下使用
2.如果在多线程下,一个线程进入了if (singleton==null)判断语句,还未来得及往下执行,另一个线程也通过了这个判断语句,这是会产生多个实例,所以在多线程环境下不能使用这种方式。
3.在实际开发中,不要使用这种方式。
public class SingletonTest03 {
public static void main(String[] args) {
System.out.println("懒汉式1,线程不安全");
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1 == instance2);
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}
class Sinleton{
private static Sinleton instance;
private Sinleton() {}
// 提供一个静态的公有方法,当使用到该方法时才去创建instance
// 即懒汉式
public static Sinleton getInstance() {
if(instance==null) {
instance=new Sinleton();
}
return instance;
}
}
第四种:(线程安全:使用synchronized同步方法)
同步方法
在getInstance()方法前加synchronized,这样每个线程在想获得类实例时候都要进行同步,而其实这个方法只执行一次实例化代码就够了,后面想获得该类实例,直接return就行,
在实际开发中,不推荐使用这种方式(效率低,每次调用方法都需要同步)
public class SingletonTest04 {
public static void main(String[] args) {
System.out.println("懒汉式1,线程不安全");
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1 == instance2);
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}
class Sinleton{
private static Sinleton instance;
private Sinleton() {}
// 提供一个静态的公有方法,当使用到该方法时才去创建instance
// 即懒汉式
// 加入synchronized,实现同步处理,解决线程安全问题
public static synchronized Sinleton getInstance() {
if(instance==null) {
instance=new Sinleton();
}
return instance;
}
}
第五种:同步代码块(X不能使用)
同步代码块(不能用,在多线程时还是可能会产生多个实例)
public static Sinleton getInstance() {
if(instance==null) {
synchronized(Sinleton.class){
instance=new Sinleton();
}
}
return instance;
}
四.第六种–双重检查方式(✔推荐使用)
volatile关键字
保证每个线程能够获取该变量的最新值,这样第二个线程就不会进入if
public class SingletonTest06 {
public static void main(String[] args) {
System.out.println("懒汉式1,线程不安全");
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1 == instance2);
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}
class Sinleton{
//volatile让修改值立即更新到主存,可以理解为轻量级synchronized
private static volatile Sinleton instance;
private Sinleton() {}
// 提供一个静态的公有方法,加入双重检查代码,解决安全问题,同时解决懒加载问题
// 即懒汉式
// 加入synchronized
public static Sinleton getInstance() {
if(instance==null) {
synchronized(Sinleton.class) {
if(instance==null) {
instance=new Sinleton();
}
}
}
return instance;
}
}
线程安全;延迟加载;效率较高
五.静态内部类–第七种(✔推荐使用)
特点
1.外部类被加载时内部类不会被加载,在调用时才会被加载(懒加载)
2.在类被加载时,线程安全
public class SingletonTest7 {
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1 == instance2);
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}
class Sinleton{
private Sinleton() {}
// 写一个静态内部类,该类中有一个静态属性,JVM底层保证类是安全的
private static class SingletonInstance{
private static final Sinleton INSTANCE=new Sinleton();
}
// 提供一个静态的公有方法,直接返回
public static Sinleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
优缺点:
(1)这种方式采用了类装载机制来保证初始化实例时只有一个线程。
(2)静态内部类方式在Singleton类被装载时并不会立即实例化(外部类被加载时内部类不会被加载),在调用时才会被加载
六.枚举–第八种(✔推荐使用)
不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象
package singleton3;
public class SingletonTest08 {
public static void main(String[] args) {
Singleton instance1 = Singleton.INSTANCE;
Singleton instance2 = Singleton.INSTANCE;
System.out.println(instance1 == instance2);
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}
enum Singleton{
INSTANCE;//属性
public void sayOK() {
System.out.println("ok~");
}
}
七、实战
单例模式在JDK中,java.lang.Runtime就是经典的单例模式
public class Runtime {
private static Runtime currentRuntime = new Runtime();
/**
* Returns the runtime object associated with the current Java application.
* Most of the methods of class <code>Runtime</code> are instance
* methods and must be invoked with respect to the current runtime object.
*
* @return the <code>Runtime</code> object associated with the current
* Java application.
*/
public static Runtime getRuntime() {
return currentRuntime;
}
/** Don't let anyone else instantiate this class */
private Runtime() {}
分析上述Runtime类源码发现,Runtime使用饿汉式-静态常量的方式实现单例
八、注意事项和细节说明
- 单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使
用单例模式可以提高系统性能 - 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new
- 单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级
对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂 等)