`
hrtc
  • 浏览: 54612 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

关于对象的循环引用(单例模式之误用)

    博客分类:
  • java
阅读更多

最近开发程序时发现了一个循环引用的bug,想了解下到底是否需要避免对象的循环引用?(完整代码见附件,eclipse编写)

 

由单例模式说起

首先是懒汉法(用到时再创建对象)代码如下

public class SingletonLazy1 {
	private static SingletonLazy1 m_instance = null;
	private SingletonLazy2 s2 = null; 
	
	public static synchronized SingletonLazy1 getInstance(){
		if(m_instance == null){
			m_instance = new SingletonLazy1();
		}
		return m_instance;
	}
	
	private SingletonLazy1(){
		s2 = SingletonLazy2.getInstance();
		System.out.println("print SingletonLazy2 in SingletonLazy1=============="+s2);
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

 

public class SingletonLazy2 {
	private static SingletonLazy2 m_instance = null;
	private SingletonLazy1 s1 = null;

	public static synchronized SingletonLazy2 getInstance() {
		if (m_instance == null) {
			m_instance = new SingletonLazy2();
		}
		return m_instance;
	}

	private SingletonLazy2() {
		s1 = SingletonLazy1.getInstance();
		System.out
				.println("print SingletonLazy1 in SingletonLazy2=============="
						+ s1);
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

 

 上述两个类似乎没问题吧,创建时都加了同步,测试执行代码(多线程创建对象)如下

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class TestLazyThread extends Thread {

	@Override
	public void run() {
		SingletonLazy1 obj = SingletonLazy1.getInstance();
	}

	public static void main(String[] args) {
		lazyTest();
	}

	private static void lazyTest() {
		Thread t = new Thread(new Runnable() {

			public void run() {
				List arrList = new ArrayList();
				for (int i = 0; i < 10; i++) {
					Thread t1 = new TestLazyThread();
					t1.setDaemon(true);
					arrList.add(t1);
				}

				for (int i = 0; i < arrList.size(); i++) {
					Thread t1 = (Thread) arrList.get(i);
					t1.start();
				}

			}
		});
		t.setDaemon(true);
		t.start();
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}

 

输出结果:由于SingletonLazy1和SingletonLazy2创建时循环引用,导致堆栈溢出

java.lang.StackOverflowError
 at test.SingletonLazy2.getInstance(SingletonLazy2.java:9)
 at test.SingletonLazy1.<init>(SingletonLazy1.java:15)
 at test.SingletonLazy1.getInstance(SingletonLazy1.java:9)
 at test.SingletonLazy2.<init>(SingletonLazy2.java:15)
 at test.SingletonLazy2.getInstance(SingletonLazy2.java:9)
 at test.SingletonLazy1.<init>(SingletonLazy1.java:15)
 at test.SingletonLazy1.getInstance(SingletonLazy1.java:9)
 at test.SingletonLazy2.<init>(SingletonLazy2.java:15)
 at test.SingletonLazy2.getInstance(SingletonLazy2.java:9)

 

 

再来看看饿汉式(提前创建对象),代码如下

public class SingletonHungry1 {
	private static SingletonHungry1 m_instance = new SingletonHungry1();
	private SingletonHungry2 s2 = null; 
	
	public static SingletonHungry1 getInstance(){
		return m_instance;
	}
	
	private SingletonHungry1(){
		s2 = SingletonHungry2.getInstance();
		System.out.println("print SingletonHungry2 in SingletonHungry1=============="+s2);
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

 

SingletonHungry2和TestHungryThread代码与懒汉式类似故省略,如需要可下载附件中的完整测试代码

输出结果:第一次获得对象时为空,这也是在项目中碰到的情况

print SingletonHungry1 in SingletonHungry2==============null
print SingletonHungry2 in SingletonHungry1==============test.SingletonHungry2@9cab16

看来现在要打破这种循环创建对象的问题

有两种解决办法:

1 取消对象循环引用

   我不知道应不应该这样,因为有时候很难避免对象循环引用,比如hibernte中的一对一关系,一对多关系等,又如现在有UserService和ForumService,前者想获得论坛数据,后者想获得用户数据,这时怎么办呢,难道还要再调用一遍dao层方法?综上所述,我选择了第二种解决方法。不过我不确定需不需要避免对象循环引用,希望知道的人给个答案。

2 打破循环创建对象

A创建时依赖于B,B创建时依赖于A ,怎么办呢?别争了,由第三方创建吧

A、B代码如下,分别提供了构造函数和set注入方法

public class A {
	private B b = null; 

	public A(){
	}
	
	public A(B b){
		this.b = b;
		System.out.println("print B in A======" + b);
	}
	
	public void setB(B b){
		this.b = b;
		System.out.println("print B in setB======" + b);
	}
	
}

 

public class B {
	private A a = null;

	public B(){
	}
	
	public B(A a) {
		this.a = a;
		System.out.println("print A in B======" + a);
	}

	public void setA(A a) {
		this.a = a;
		System.out.println("print A in setA======" + a);
	}
	
}

 

 工厂代码

public class BeanFactory {
	private static A a = null;
	private static B b = null;
	
	public static synchronized A getAConstructor(){
		if(a == null){
			a = new A(getBConstructor());
		}
		return a;
	}
	
	public static synchronized B getBConstructor(){
		if(b == null){
			b = new B(getAConstructor());
		}
		return b;
	}
	
	public static synchronized A getA(){
		if(a == null){
			a =  new A();
			a.setB(getB());
		}
		return a;
	}
	
	public static synchronized B getB(){
		if(b == null){
			b =  new B();
			b.setA(getA());
		}
		return b;
	}
}

 

测试代码(构造函数注入)

import java.util.ArrayList;
import java.util.List;

public class TestFactoryConstructorThread extends Thread {

	@Override
	public void run() {		
		A aConstructor = BeanFactory.getAConstructor();
	}

	public static void main(String[] args) {
		factoryTest();
	}
	
	private static void factoryTest(){
		Thread t = new Thread(new Runnable(){

			public void run() {
				List arrList = new ArrayList();
				for (int i = 0; i < 10; i++) {
					Thread t1 = new TestFactoryConstructorThread();
					t1.setDaemon(true);
					arrList.add(t1);
				}
				
				for (int i = 0; i < arrList.size(); i++) {
					Thread t1 = (Thread) arrList.get(i);
					t1.start();
				}
				
			}});
		t.setDaemon(true);
		t.start();
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

 运行结果:又是堆栈溢出,分析代码发现仍没有打破循环创建对象,用构造函数注入的小心了

java.lang.StackOverflowError
 at test.BeanFactory.getAConstructor(BeanFactory.java:9)
 at test.BeanFactory.getBConstructor(BeanFactory.java:16)
 at test.BeanFactory.getAConstructor(BeanFactory.java:9)
 at test.BeanFactory.getBConstructor(BeanFactory.java:16)
 at test.BeanFactory.getAConstructor(BeanFactory.java:9) 

 

测试代码(set方法注入)

import java.util.ArrayList;
import java.util.List;

public class TestFactorySetThread extends Thread {

	@Override
	public void run() {

		A aSet = BeanFactory.getA();
		
	}

	public static void main(String[] args) {
		factoryTest();
	}
	
	private static void factoryTest(){
		Thread t = new Thread(new Runnable(){

			public void run() {
				List arrList = new ArrayList();
				for (int i = 0; i < 10; i++) {
					Thread t1 = new TestFactorySetThread();
					t1.setDaemon(true);
					arrList.add(t1);
				}
				
				for (int i = 0; i < arrList.size(); i++) {
					Thread t1 = (Thread) arrList.get(i);
					t1.start();
				}
				
			}});
		t.setDaemon(true);
		t.start();
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

 

运行结果:运行成功,呵呵终于解决了

print A in setA======test.A@1a46e30
print B in setB======test.B@3e25a5

 

 

以前编代码一直没注意到这个问题,后来用spring也没碰到这种问题,最近直接用单例来创建对象,才发现了这个问题,顺便编写代码测试了下。希望能和大家共同交流,到底允不允许对象的循环依赖?

分享到:
评论
1 楼 my_lovelove 2009-01-18  
不错,谢谢分享

相关推荐

    设计模式可复用面向对象软件的基础(C++)——强烈推荐

    创建型模式关注对象的创建,如单例模式、工厂模式和建造者模式,它们旨在提供一种灵活的、抽象的对象创建方式。结构型模式关注如何将对象组合成更大的结构,例如适配器模式、装饰器模式和代理模式。行为型模式则涉及...

    设计模式以及其应用方法与拓展讲解

    创建型模式如单例模式、工厂模式、抽象工厂模式、建造者模式和原型模式,主要关注对象的创建过程,使代码更易于管理。结构型模式如适配器模式、装饰器模式、代理模式、桥接模式、组合模式、外观模式和享元模式,关注...

    GoF23种经典模式+简单工厂模式讲解还有一篇心得

    整体心得文档可能会提供作者在学习过程中对设计模式的整体理解和感悟,包括哪些模式在何种场景下效果最佳,以及如何避免设计模式的误用等。 学习设计模式有助于提升代码质量和可维护性,促进团队间的沟通,使得软件...

    设计模式迷你手册最近的

    创建型模式关注对象的创建,如单例模式(Singleton)、工厂模式(Factory)和抽象工厂模式(Abstract Factory)。这些模式帮助我们有效地控制实例化过程,使代码更具灵活性和可扩展性。 结构型模式处理对象组合和类...

    java面向对象的设计方法

    在实际开发中,面向对象设计方法还包括了一些设计模式的应用,如工厂模式、单例模式、观察者模式等。这些模式是前人经验的总结,它们为解决特定问题提供了标准的解决方案,提高了代码的可读性和可维护性。 总的来说...

    java与模式

    创建型模式如单例(Singleton)、工厂方法(Factory Method)、抽象工厂(Abstract Factory)等,主要关注对象的创建过程,使得代码更加灵活且易于维护。结构型模式如适配器(Adapter)、装饰器(Decorator)、桥接...

    Design Pattern Explained

    创建型模式关注对象的创建过程,如工厂模式、抽象工厂模式、单例模式等;结构型模式涉及类和对象的组合,如适配器模式、装饰器模式、代理模式等;行为型模式主要关注对象之间的交互和责任分配,如策略模式、观察者...

    基于深度学习的API误用缺陷检测.pdf

    通过深度学习技术,尤其是循环神经网络模型,可以有效地学习和概括API的使用模式,并通过语句预测来识别代码中潜在的API误用缺陷。这不仅提高了代码的质量,还降低了开发人员在API使用过程中出现错误的风险。未来的...

    设计模式迷你手册(RedSword软件工作室).rar

    创建型模式关注对象的创建过程,如单例模式(Singleton)、工厂方法模式(Factory Method)和建造者模式(Builder)。这些模式可以帮助我们更好地控制对象的实例化,降低系统耦合度。 结构型模式关注如何组合对象和...

    MLDN魔乐JAVA_09深入引用、this关键字、对象比较.rar

    在Java编程语言中,深入理解和熟练运用引用、`this`关键字以及对象比较是提升编程技能的关键环节。本课程“MLDN魔乐JAVA_09深入引用、this关键字、对象比较”将带你深入探讨这些核心概念。 首先,让我们来讨论...

    Head First设计模式

    创建型模式主要关注对象的创建,如单例模式、工厂方法模式、抽象工厂模式等,它们提供了一种在运行时创建对象的最佳方式。结构型模式涉及如何组合类和对象以形成更大的结构,例如适配器模式、装饰器模式、桥接模式等...

    HeadFirst设计模式中文版

    1. 创建型模式:这类模式主要关注对象的创建过程,如单例模式(Singleton)、工厂方法模式(Factory Method)、抽象工厂模式(Abstract Factory)、建造者模式(Builder)和原型模式(Prototype)。这些模式可以帮助...

    javascript面向对象编程的几种模式详解

    面向对象编程(Object-Oriented Programming, OOP)是JavaScript的核心特性之一,它提供了多种模式来创建和操作对象。以下是对标题和描述中提到的几种JavaScript面向对象编程模式的详细解释: 1. **构造函数与字面...

    面向对象课程设计

    此外,可能还会涉及到设计模式的学习,如单例模式、工厂模式、观察者模式等,这些都是解决特定问题的成熟解决方案。 通过这个面向对象的课程设计,学员将有机会运用所学知识去解决实际问题,提升编程能力和软件设计...

    面向对象技术精粹

    面向对象的设计模式则是一组经过验证的设计解决方案,它们解决了特定问题中常见的设计挑战,比如工厂模式、单例模式、策略模式、装饰者模式等。这些模式提供了一种结构化的处理方法,可以帮助开发者构造出更加优雅和...

    java的23种设计模式01

    1. 单例模式(Singleton):确保一个类只有一个实例,并提供全局访问点。在Java中,通常通过私有构造函数和静态工厂方法实现。 2. 工厂方法模式(Factory Method):定义一个用于创建对象的接口,让子类决定实例化...

    Java Bug模式详解 pdf版

    1. **空指针异常**:这是Java中最常见的错误之一,书中会详细讲解如何避免和处理空指针异常,包括正确地初始化对象、使用Optional类等方法。 2. **并发问题**:Java的多线程特性使得并发编程变得复杂,书中可能会...

    c++面向对象程序设计实验报告和代码

    《C++面向对象程序设计》是一本深入探讨C++编程技术的教材,特别是关于面向对象编程(OOP)的理念和实践。面向对象编程是现代软件开发中的核心思想,它允许我们通过类和对象来组织和抽象复杂的问题,提高代码的...

    南开大学复习资料-面向对象程序设计10001.docx

    24. **面向对象设计模式**:如单例模式、观察者模式、装饰器模式、策略模式、建造者模式等,它们是解决特定问题的通用解决方案。 25. **继承的作用**:继承提高了代码的复用性,允许在已有的类基础上创建新的类,而...

Global site tag (gtag.js) - Google Analytics