静态域的同步化--有兴趣的进来讨论一下

时间:2021-06-15 23:36:48
有一个类
public class LogSql {
static final Set instants = new LinkedHashSet();

  static String getSql() {
    for (String sql:instants) {
      if(1==1) return sql;
    }
  }
  public LogSql() {
    synchronized (instances) {//Exception throwed
      instances.add(this);
    }
  }
}
在垃圾收集的时候我会在synchronized (instances)这个地方得到
ConcurrentModificationException

有兴趣的讨论一下为什么
没兴趣的希望不要灌水,让它沉下去就可以,而且我给得分也不多

26 个解决方案

#1


当使用 fail-fast iterator 对 Collection 或 Map 进行迭代操作过程中尝试直接修改 Collection / Map 的内容时, 即使是在单线程下运行,   java.util.ConcurrentModificationException 异常也将被抛出。  Iterator 是工作在一个独立的线程中,并且拥有一个 mutex 锁。 Iterator 被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast 原则 Iterator 会马上抛出 java.util.ConcurrentModificationException 异常。

  所以 Iterator 在工作的时候是不允许被迭代的对象被改变的。但你可以使用 Iterator 本身的方法 remove() 来删除对象, Iterator.remove() 方法会在删除当前迭代对象的同时维护索引的一致性。

  有意思的是如果你的 Collection / Map 对象实际只有一个元素的时候, ConcurrentModificationException 异常并不会被抛出。这也就是为什么在 javadoc 里面指出: it would be wrong to write a program that depended on this exception for its correctness: ConcurrentModificationException should be used only to detect bugs.

#2


PS:  转的..共同学习. 以前都没见过ConcurrentModificationException 这个异常..呵呵

#3


引用楼主 Gump09 的帖子:
在垃圾收集的时候我会在synchronized (instances)这个地方得到 
ConcurrentModificationException 


GC 的工作是不可预知的,你是怎么知道是在垃圾收集的时候产生的?

#4


up我也想知道3楼这个问题

#5


GC 的工作是不可预知的...

#6


引用 3 楼 bao110908 的回复:
GC 的工作是不可预知的,你是怎么知道是在垃圾收集的时候产生的?

火龙果来了,首先欢迎。
是不是真的垃圾收集,只能说八九不离十/
class LogSql是spring管理的。比如
这样配置<bean id="javax.sql.logSql" class="LogSql" ......
那么我在log中看到
Pre-instantiating singletons in factory [org.springframework.beans.factory.support.DefaultListableBeanFactory defining beans
javax.sql.logSql
这句话的话,基本可以确定它是在载入这个类,应用没有停止的情况下重新载入一个类
我觉得很可能就是垃圾收集

当然我说的不准确,其实是垃圾收集之后再次执行时出的错

#7


有几个基本点,希望大家发表看法
1,static Set变量会不会被垃圾收集
2,如果不会,那么它的内容会不会被收集
3,如果他的内容,比如instances中的一个对象被收集了那么它的容量就变小了吗?
4,如果他的内容被收集,索引会不会被更新
5, 类的重新载入静态域会被重置吗
6,看到了类重新载入就意味着它的所有实例都改变了吗?
7,还是说只有实例都销毁之后才能重新载入类

#8


public class LogSql {

    static final Set instants = new LinkedHashSet();

    static String getSql() { 
        for (String sql:instants) { 
          if(1==1) return sql; 
        }
    }

    public LogSql() { 
        synchronized (instances) {//Exception throwed 
            instances.add(this); 
        }
    }
}


很难想象这段代码能通过编译

#9


import java.util.LinkedHashSet;
import java.util.Set;

public class LogSql {

    static final Set<LogSql> instants = new LinkedHashSet<LogSql>();
    private static LogSql defaultLogSql;

    static LogSql getSql() { 
        for (LogSql sql:instants) { 
          if(1==1) return sql; 
        }
        return defaultLogSql; 
    }

    public LogSql() { 
        synchronized (instants) {//Exception throwed 
            instants.add(this); 
        }
    }
}

#10


上边的应该可以通过编译,它只是一段伪代码

#11


看不出这段代码这样做有什么特殊的意义。

所谓的垃圾回收是指某个对象没有被引用时,那这个对象就有可能会被回收掉,
Set 中的对象在 Set 中被引用了,不会被垃圾回收掉的。

#12


JLS允许类被卸载。
JDK1.1.2中卸载产生了一个不好的结果,就是一个类的静态量会被重新初始化。他和JLS8.3.1.中对于静态的描述(不管多少个实例被创建静态量只有一个)相冲突。类加载一般有两种途径。一种是类A中声明了类B,那么在执行到声明时就会加载类B.根据JLS卸载类时有几个限制。1,类实例能找到的话,类不能被卸载 2,类的lass对象能找到的话,类不能被卸载.如果以各类是用forname方法加载的,那么在没有对其的引用的情况下他就是不可到达的。相反,如果类是因为有一个引用链到它尔加载的话,那么那还是可到达的。就是说即使类没有了实例,也没有Class对象引用到它,它也还是可到达的。
要卸载一个类还应该需要有一个条件,如果加载类的类加载器还存在那么不要卸载含有静态量的类。


#13


多线程情况下,GC引起的Set重新初始化,会引起异常
所以如果这个类不是单态的,那么就应该考虑将其所有操作同期化
但Web*同部分的同期话会带来性能的牺牲
所以最好的方式是将其单态化

#14


引用 7 楼 Gump09 的回复:
有几个基本点,希望大家发表看法
1,static Set变量会不会被垃圾收集
2,如果不会,那么它的内容会不会被收集
3,如果他的内容,比如instances中的一个对象被收集了那么它的容量就变小了吗?
4,如果他的内容被收集,索引会不会被更新
5, 类的重新载入静态域会被重置吗
6,看到了类重新载入就意味着它的所有实例都改变了吗?
7,还是说只有实例都销毁之后才能重新载入类

回到这里,显然static量会因为Class被GC而产生状态的改变
如果其内容的引用不被收集那么内容就不应该会改变
索引的更新是单线程的这使得在多线程情况下,应对Set的索引操作也作同步但会作出性能牺牲
卸载类时有几个限制。1,类实例能找到的话,类不能被卸载 2,类的lass对象能找到的话,类不能被卸载.
不满足以上两条那么类本身也会被GC

#15


引用楼主 Gump09 的帖子:
  public LogSql() { 
    synchronized (instances) {//Exception throwed 
      instances.add(this); 
    } 
  } 

在垃圾收集的时候我会在synchronized (instances)这个地方得到 
ConcurrentModification…


既然能运行到这个代码,说明LogSql所在的对象存在,(不存在对象就报NULL了)
既然对象存在,又怎么可能会被GC

LZ好好查查自己的代码其他的位置

#16


说到这里基本上就解决问题了
改天把这套配置整理一下

#17


没遇到过

#18


遇到一个问题就是spring 的单态不起作用
我有一个web应用和几个webservice在webservice和web应用中都配置LogSql
并且singleton为true 
但是跟踪发现有多个对象

#19


去掉static并且非单态的话对效率影响太大
但是单态又不能解决
回到了如何让类不被回收这个问题上

#20


如果加一个参数可以做到类不被回收
java -Xnoclassgc xxxxxxx 

#21


由于同时在web和web service用这个类,所以跟踪了一下。
发现每次从web service中,加在spring配置文件时,类实例都加一。
查spring配置文件发现被配置为单态。也就是说类伴随着spring容器的加载而增加。
于是把web service用的spring配置文件改到web初始化时加载一次。
这样类实例就只剩下了两个。

这样推定原因为
由于每次spring加载那么实例增加,静态set的size增加。
如果一个线程访问过程中由于gc,set被重置那么就会发生异常

#22


上个周五做了一天试验。
启动web Server加载应用,然后不断去执行web service.
使得LogSql 的instances不断增加。
其间强制垃圾收集两次。但LogSql均未被收集掉。
这个事实与预期相悖

#23


本来打算将我的心得写上来,不过看来也没人看,算了,继续自言自语
由于LogSql对象一个都没被收集掉,可以确认他被某个实力引用到了。
暂时决定将LogSql做成真正的单态

#24


你虽然在往里加的时候同步了,获取的时候也应该同步,因为你往里加的时候同时获取,本来数据就会出现异常,先不说JDK那"行话".你在foreache循环中get的时候往里加一个对象,这里下面的循环还会正确吗?如果仅仅是getXXXat(i)这种非迭代操作,也就是非foreache倒是可以的.

#25


引用 24 楼 axman 的回复:
你虽然在往里加的时候同步了,获取的时候也应该同步,因为你往里加的时候同时获取,本来数据就会出现异常,先不说JDK那"行话".你在foreache循环中get的时候往里加一个对象,这里下面的循环还会正确吗?如果仅仅是getXXXat(i)这种非迭代操作,也就是非foreache倒是可以的.

这里主要考虑是效率的问题。所以虽然有时会出错但由于是log所以一直就没改过。
而且当初的考虑是它是单态的所以没有问题,没想到spring的单态是假单态
现在就是搞不清到底原因是什么,到底是gc引起出错还是add引起出错。
正在考虑将其单态化。

#26


确定是在add的时候出错。
线程一进入循环
线程二把set数加一
线程一继续循环, 出错

#1


当使用 fail-fast iterator 对 Collection 或 Map 进行迭代操作过程中尝试直接修改 Collection / Map 的内容时, 即使是在单线程下运行,   java.util.ConcurrentModificationException 异常也将被抛出。  Iterator 是工作在一个独立的线程中,并且拥有一个 mutex 锁。 Iterator 被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast 原则 Iterator 会马上抛出 java.util.ConcurrentModificationException 异常。

  所以 Iterator 在工作的时候是不允许被迭代的对象被改变的。但你可以使用 Iterator 本身的方法 remove() 来删除对象, Iterator.remove() 方法会在删除当前迭代对象的同时维护索引的一致性。

  有意思的是如果你的 Collection / Map 对象实际只有一个元素的时候, ConcurrentModificationException 异常并不会被抛出。这也就是为什么在 javadoc 里面指出: it would be wrong to write a program that depended on this exception for its correctness: ConcurrentModificationException should be used only to detect bugs.

#2


PS:  转的..共同学习. 以前都没见过ConcurrentModificationException 这个异常..呵呵

#3


引用楼主 Gump09 的帖子:
在垃圾收集的时候我会在synchronized (instances)这个地方得到 
ConcurrentModificationException 


GC 的工作是不可预知的,你是怎么知道是在垃圾收集的时候产生的?

#4


up我也想知道3楼这个问题

#5


GC 的工作是不可预知的...

#6


引用 3 楼 bao110908 的回复:
GC 的工作是不可预知的,你是怎么知道是在垃圾收集的时候产生的?

火龙果来了,首先欢迎。
是不是真的垃圾收集,只能说八九不离十/
class LogSql是spring管理的。比如
这样配置<bean id="javax.sql.logSql" class="LogSql" ......
那么我在log中看到
Pre-instantiating singletons in factory [org.springframework.beans.factory.support.DefaultListableBeanFactory defining beans
javax.sql.logSql
这句话的话,基本可以确定它是在载入这个类,应用没有停止的情况下重新载入一个类
我觉得很可能就是垃圾收集

当然我说的不准确,其实是垃圾收集之后再次执行时出的错

#7


有几个基本点,希望大家发表看法
1,static Set变量会不会被垃圾收集
2,如果不会,那么它的内容会不会被收集
3,如果他的内容,比如instances中的一个对象被收集了那么它的容量就变小了吗?
4,如果他的内容被收集,索引会不会被更新
5, 类的重新载入静态域会被重置吗
6,看到了类重新载入就意味着它的所有实例都改变了吗?
7,还是说只有实例都销毁之后才能重新载入类

#8


public class LogSql {

    static final Set instants = new LinkedHashSet();

    static String getSql() { 
        for (String sql:instants) { 
          if(1==1) return sql; 
        }
    }

    public LogSql() { 
        synchronized (instances) {//Exception throwed 
            instances.add(this); 
        }
    }
}


很难想象这段代码能通过编译

#9


import java.util.LinkedHashSet;
import java.util.Set;

public class LogSql {

    static final Set<LogSql> instants = new LinkedHashSet<LogSql>();
    private static LogSql defaultLogSql;

    static LogSql getSql() { 
        for (LogSql sql:instants) { 
          if(1==1) return sql; 
        }
        return defaultLogSql; 
    }

    public LogSql() { 
        synchronized (instants) {//Exception throwed 
            instants.add(this); 
        }
    }
}

#10


上边的应该可以通过编译,它只是一段伪代码

#11


看不出这段代码这样做有什么特殊的意义。

所谓的垃圾回收是指某个对象没有被引用时,那这个对象就有可能会被回收掉,
Set 中的对象在 Set 中被引用了,不会被垃圾回收掉的。

#12


JLS允许类被卸载。
JDK1.1.2中卸载产生了一个不好的结果,就是一个类的静态量会被重新初始化。他和JLS8.3.1.中对于静态的描述(不管多少个实例被创建静态量只有一个)相冲突。类加载一般有两种途径。一种是类A中声明了类B,那么在执行到声明时就会加载类B.根据JLS卸载类时有几个限制。1,类实例能找到的话,类不能被卸载 2,类的lass对象能找到的话,类不能被卸载.如果以各类是用forname方法加载的,那么在没有对其的引用的情况下他就是不可到达的。相反,如果类是因为有一个引用链到它尔加载的话,那么那还是可到达的。就是说即使类没有了实例,也没有Class对象引用到它,它也还是可到达的。
要卸载一个类还应该需要有一个条件,如果加载类的类加载器还存在那么不要卸载含有静态量的类。


#13


多线程情况下,GC引起的Set重新初始化,会引起异常
所以如果这个类不是单态的,那么就应该考虑将其所有操作同期化
但Web*同部分的同期话会带来性能的牺牲
所以最好的方式是将其单态化

#14


引用 7 楼 Gump09 的回复:
有几个基本点,希望大家发表看法
1,static Set变量会不会被垃圾收集
2,如果不会,那么它的内容会不会被收集
3,如果他的内容,比如instances中的一个对象被收集了那么它的容量就变小了吗?
4,如果他的内容被收集,索引会不会被更新
5, 类的重新载入静态域会被重置吗
6,看到了类重新载入就意味着它的所有实例都改变了吗?
7,还是说只有实例都销毁之后才能重新载入类

回到这里,显然static量会因为Class被GC而产生状态的改变
如果其内容的引用不被收集那么内容就不应该会改变
索引的更新是单线程的这使得在多线程情况下,应对Set的索引操作也作同步但会作出性能牺牲
卸载类时有几个限制。1,类实例能找到的话,类不能被卸载 2,类的lass对象能找到的话,类不能被卸载.
不满足以上两条那么类本身也会被GC

#15


引用楼主 Gump09 的帖子:
  public LogSql() { 
    synchronized (instances) {//Exception throwed 
      instances.add(this); 
    } 
  } 

在垃圾收集的时候我会在synchronized (instances)这个地方得到 
ConcurrentModification…


既然能运行到这个代码,说明LogSql所在的对象存在,(不存在对象就报NULL了)
既然对象存在,又怎么可能会被GC

LZ好好查查自己的代码其他的位置

#16


说到这里基本上就解决问题了
改天把这套配置整理一下

#17


没遇到过

#18


遇到一个问题就是spring 的单态不起作用
我有一个web应用和几个webservice在webservice和web应用中都配置LogSql
并且singleton为true 
但是跟踪发现有多个对象

#19


去掉static并且非单态的话对效率影响太大
但是单态又不能解决
回到了如何让类不被回收这个问题上

#20


如果加一个参数可以做到类不被回收
java -Xnoclassgc xxxxxxx 

#21


由于同时在web和web service用这个类,所以跟踪了一下。
发现每次从web service中,加在spring配置文件时,类实例都加一。
查spring配置文件发现被配置为单态。也就是说类伴随着spring容器的加载而增加。
于是把web service用的spring配置文件改到web初始化时加载一次。
这样类实例就只剩下了两个。

这样推定原因为
由于每次spring加载那么实例增加,静态set的size增加。
如果一个线程访问过程中由于gc,set被重置那么就会发生异常

#22


上个周五做了一天试验。
启动web Server加载应用,然后不断去执行web service.
使得LogSql 的instances不断增加。
其间强制垃圾收集两次。但LogSql均未被收集掉。
这个事实与预期相悖

#23


本来打算将我的心得写上来,不过看来也没人看,算了,继续自言自语
由于LogSql对象一个都没被收集掉,可以确认他被某个实力引用到了。
暂时决定将LogSql做成真正的单态

#24


你虽然在往里加的时候同步了,获取的时候也应该同步,因为你往里加的时候同时获取,本来数据就会出现异常,先不说JDK那"行话".你在foreache循环中get的时候往里加一个对象,这里下面的循环还会正确吗?如果仅仅是getXXXat(i)这种非迭代操作,也就是非foreache倒是可以的.

#25


引用 24 楼 axman 的回复:
你虽然在往里加的时候同步了,获取的时候也应该同步,因为你往里加的时候同时获取,本来数据就会出现异常,先不说JDK那"行话".你在foreache循环中get的时候往里加一个对象,这里下面的循环还会正确吗?如果仅仅是getXXXat(i)这种非迭代操作,也就是非foreache倒是可以的.

这里主要考虑是效率的问题。所以虽然有时会出错但由于是log所以一直就没改过。
而且当初的考虑是它是单态的所以没有问题,没想到spring的单态是假单态
现在就是搞不清到底原因是什么,到底是gc引起出错还是add引起出错。
正在考虑将其单态化。

#26


确定是在add的时候出错。
线程一进入循环
线程二把set数加一
线程一继续循环, 出错