最近开发程序时发现了一个循环引用的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. **Singleton(单例模式)** - **定义**:保证一个类仅有一个实例,并提供一个全局访问点。 - **应用场景**:日志控制、线程池管理等需要单个实例来协调工作的情况。 - **优点**: - 减少内存占用,避免对资源...
11. **设计模式**:理解并应用常见的设计模式(如工厂模式、单例模式、观察者模式等)可以提高代码质量,减少后期维护的难度。 12. **泛型**:泛型提供了类型安全和效率,但误用可能导致代码复杂度增加。了解何时...
封装是面向对象编程的基本概念之一,指的是将数据(或状态)和行为(或功能)捆绑在一个单独的单元内,这个单元就是类。封装可以隐藏对象的内部实现细节,只暴露有限的接口供外部使用,从而提高系统的安全性和稳定性...
- **设计模式**:理解常见设计模式,如工厂模式、单例模式、观察者模式等,提升代码设计能力。 7. **调试与性能优化** - **调试技巧**:学习如何利用Visual Studio或VS Code进行调试,查找并修复代码错误。 - **...
如果一个BeanA依赖于另一个BeanB,同时BeanB也依赖于BeanA,那么Spring在处理依赖关系时可能会陷入循环引用,导致两个Bean都实例化两次。 3. **@PostConstruct与初始化回调**:Spring允许我们在Bean初始化后执行...
9. **设计模式的误解**:设计模式是解决常见问题的最佳实践,但误用或滥用可能导致代码结构复杂,难以维护,如单例模式的线程安全问题。 10. **反射和动态代理**:虽然这两项技术提供了强大的功能,但过度依赖或...