`
hrtc
  • 浏览: 54983 次
  • 性别: 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  
不错,谢谢分享

相关推荐

    java/J2ee的23种设计模式

    1. **Singleton(单例模式)** - **定义**:保证一个类仅有一个实例,并提供一个全局访问点。 - **应用场景**:日志控制、线程池管理等需要单个实例来协调工作的情况。 - **优点**: - 减少内存占用,避免对资源...

    .NET Gotachas

    11. **设计模式**:理解并应用常见的设计模式(如工厂模式、单例模式、观察者模式等)可以提高代码质量,减少后期维护的难度。 12. **泛型**:泛型提供了类型安全和效率,但误用可能导致代码复杂度增加。了解何时...

    东北大学 研究生高级Java语言试题2018

    封装是面向对象编程的基本概念之一,指的是将数据(或状态)和行为(或功能)捆绑在一个单独的单元内,这个单元就是类。封装可以隐藏对象的内部实现细节,只暴露有限的接口供外部使用,从而提高系统的安全性和稳定性...

    C# 9.0文档及编程指南中文版可编辑.rar

    - **设计模式**:理解常见设计模式,如工厂模式、单例模式、观察者模式等,提升代码设计能力。 7. **调试与性能优化** - **调试技巧**:学习如何利用Visual Studio或VS Code进行调试,查找并修复代码错误。 - **...

    Spring Bean重复执行两次(实例被构造两次)问题分析

    如果一个BeanA依赖于另一个BeanB,同时BeanB也依赖于BeanA,那么Spring在处理依赖关系时可能会陷入循环引用,导致两个Bean都实例化两次。 3. **@PostConstruct与初始化回调**:Spring允许我们在Bean初始化后执行...

    Absurd-Confusion

    9. **设计模式的误解**:设计模式是解决常见问题的最佳实践,但误用或滥用可能导致代码结构复杂,难以维护,如单例模式的线程安全问题。 10. **反射和动态代理**:虽然这两项技术提供了强大的功能,但过度依赖或...

Global site tag (gtag.js) - Google Analytics