锁定老帖子 主题:关于对象的循环引用(单例模式之误用)
精华帖 (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
再来看看饿汉式(提前创建对象),代码如下 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 看来现在要打破这种循环创建对象的问题 有两种解决办法: 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
测试代码(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
以前编代码一直没注意到这个问题,后来用spring也没碰到这种问题,最近直接用单例来创建对象,才发现了这个问题,顺便编写代码测试了下。希望能和大家共同交流,到底允不允许对象的循环依赖? 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
浏览 2396 次