论坛首页 Java企业应用论坛

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

浏览 2396 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2008-12-03   最后修改:2008-12-04

最近开发程序时发现了一个循环引用的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也没碰到这种问题,最近直接用单例来创建对象,才发现了这个问题,顺便编写代码测试了下。希望能和大家共同交流,到底允不允许对象的循环依赖?

论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics