单例获取对象赋值后为null的现象

本文通过一个具体的案例,解析了在使用单例模式时,由于不同对象引用导致的变量值无法正确获取的问题,并提出了相应的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题描述:

        在项目开发过程中,我们建立许多的工具类,比如说:网络请求、图片加载,判断服务是否启动等。其调用方式最常用的有两种:1.直接使方法静态,这样就可以用类名.方法名直接调用。大家都知道,静态方法在一个项目中出现多次会影响项目的内存消耗,所以有好多人会选择第二种方式。2.方法不静态化,使用单例创建对象后,再调用方法。那么这个Bug就是在使用第2种方式调用方法的时候所造成的。

代码如下:

 

public class BugUtil {

    private static BugUtil instance = null;
    public String str=null;

    public BugUtil() {
    }

    /**
     * 加上synchronized,但是每次调用实例时都会加载
     */
    public static BugUtil getInstance() {
        synchronized (BugUtil.class) {
            if (instance == null) {
                instance = new BugUtil();
            }
        }
        return instance;
    }

    /**
     * 对str进行赋值
     */
    public void setStr(){
        str="我已经给str赋过值了";
    }
}

在MainActivity中调用打印log

 

 

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    BugUtil bugUtil=new BugUtil();
    bugUtil.setStr();
    Log.i("sunmenglong", "onCreate: ..............."+BugUtil.getInstance().str);
}

打印出的log:

 

10-28 13:46:16.501 6718-6718/? I/sunmenglong: onCreate: ...............null

我当时的疑惑就是,明明在上一行的代码当中,已经给str赋值过了,为什么打印的还是为空?

我们再次看一下这个调用的代码:

 

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    BugUtil bugUtil=new BugUtil();
    bugUtil.setStr();
    Log.i("sunmenglong", "onCreate: ..............."+BugUtil.getInstance().str);
}

区别就在于:我在赋值的时候是new出来的一个对象,而在打印的时候是调用的单例。所以,赋值的str和取值的str不是同一个实例,造成了这样的误解。

 

如果我们在使用单例写工具类的时候,一定要使无参构造方法私有化,这样外界在调用的时候就创建不了实例了!

希望这篇博客对于遇到同样问题的码农们有所帮助!

 

<< 模式(Singleton Pattern)确保某一个类只有一个实,并且自行提供这个全局访问点。根据其实现方式的不同,模式可以分为以下几种: ### 1. 饿汉式 在饿汉式中,类加载时就完成了实化操作,因此线程安全并且简高效,但可能会导致资源浪费。 ```java public class Singleton { private static final Singleton INSTANCE = new Singleton(); // 私有构造器防止被外部实化 private Singleton() {} public static Singleton getInstance() { return INSTANCE; } } ``` **优点**: 类在装载时就已经完成初始化, 不用担心多线程下的安全性问题; **缺点**: 占用了内存间(即使没有使用); --- ### 2. 懒汉式 懒汉式直到第一次调用 `getInstance` 方法才创建实,节省了内存开销;但由于可能存在多个线程同时调用该方法的情况,需要进行同步处理。 #### (1) **非线程安全版** ```java public class Singleton { private static Singleton instance; private Singleton() {} public static Singleton getInstance(){ if(instance == null){ instance = new Singleton(); } return instance; } } ``` **注意**: 这种写法不是线程安全的,在高并发情况下可能出现多个实的问题。 #### (2) **线程安全版 - synchronized关键字** ```java public class Singleton { private static Singleton instance; private Singleton() {} public static synchronized Singleton getInstance(){ if(instance==null) instance=new Singleton(); return instance; } } ``` **虽然解决了线程安全问题**,但是synchronized锁降低了性能. --- ### 3. 双重检查锁定(DCL) 双重校验加锁机制既保证了线程的安全性又提高了效率。 ```java public class Singleton{ private volatile static Singleton singleton;//volatile避免指令重排序 private Singleton(){} public static Singleton getInstatnce(){ if(singleton==null){ //第一层检测 synchronized(Singleton.class){ //上锁只针对instance为null的情况下发生. if(singleton==null){ //第二层检测 singleton= new Singleton();//new 实分配对象到堆内存可能产生三步过程:分配、构建及赋值等步骤, //volatile可避免此情况中的异常现象即未完全构建好就被引用. } } } return singleton; } } ``` **优势**: 解决了延迟加载和线程安全两大难题; **劣势**: 相对复杂一点的理解成本较高。 --- ### 4. 静态内部类 当静态内部类StaticHolder被加载时才会初始化singleton,此时才能确定是否要建立SingleInstance类的对象,从而实现了lazy loading的效果,而且由JVM来帮我们做了线程同步的工作,所以它是线程安全的。 ```java public class Singleton { private Singleton(){} private static class StaticHolder{ private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance(){ return StaticHolder.INSTANCE; } } ``` **特点**: 利用classloader机制保证了线程安全性和唯一性;不会增加过多额外负担的同时也达到了延时加载的目的。 --- ### 5. 枚举类型实现 这种方式可能是最简洁明了的一种形式,也是Joshua Bloch推荐的方式之一。它的主要特点是天然地支持序列化与反序列化,并能有效地抵御反射攻击。 ```java public enum SingletonEnum { INSTANCE; } // 使用枚举的方式获取 SingletonEnum singleton = SingletonEnum.INSTANCE; ``` **优点**: 最安全可靠的做法;自动具备序列化的功能;无法通过反射破坏规则; **总结**: 如果不考虑兼容旧版本的语言特性的需求的话,那么枚举类型的模式就是最好的选择方案!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值