Java Concurrency Bugs #5: inconsistent synchronization
A classic Java concurrency bug that you still see pop up frequently is inconsistent synchronization. The Java Memory Model defines the semantics that a Java programmer can expect in a *properly synchronized program*. The JMM specifies that a write made under synchronization will be visible to a subsequent read under synchronization on the same monitor. A subsequent read NOT under synchronization has no such guarantees.
Typically this shows up in code where an attribute is written under synchronized but read without it:
public class Broken {
private int value;
public synchronized void setValue(int value) {
this.value = value;
}
public int getValue() {
return this.value;
}
}
public class Broken {
private int value;
public synchronized void setValue(int value) {
this.value = value;
}
public int getValue() {
return this.value;
}
}
If one thread calls setValue() there is no guarantee that subsequent reads in other threads via getValue() will ever see that value. Probably the most common version of this I’ve run across (and ok, written myself) is with a HashMap:
public class SomeData {
private final Map data = new HashMap();
public void set(String key, String value) {
synchronized(data) {
data.put(key, value);
}
}
public String get(String key) {
return data.get(key);
}
}
public class SomeData {
private final Map data = new HashMap();
public void set(String key, String value) {
synchronized(data) {
data.put(key, value);
}
}
public String get(String key) {
return data.get(key);
}
}
This is exactly the same problem - set() is synchronized, get() is not. However, HashMap has a far more insidious problem than the simple integer above. Hash map gets are only ~O(1) when the number of internal bucket collisions in the map is constrained (worst case, it devolves into a linked list at O(n)). To address this problem, HashMap uses the size and load factor to determine when it needs to rehash. A rehash causes new buckets to be created and all entries to be assigned to a new bucket by re-evaluating the hash function. And this is where the problem comes in with HashMap.get():
public V get(Object key) {
(key == null)
return getForNullKey();
int hash = hash(key.hashCode());
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
You’ll notice that get() contains a loop that walks through the collision list in a bucket. It’s entirely possible if you are reading this list while a rehash occurs that you can hit the termination condition for the loop incorrectly and miss the value. Older versions of this code contained a while(true) {} loop that could actually trigger an infinite loop. I’m not sure that’s possible in this newer version but in any case you can certainly get incorrect results from get().
One exception case for unsynchronized gets on a HashMap is with a read-only Map created during construction. In this case, you are subject to final field freeze semantics. These guarantee that if you declare the HashMap field as final, fill the map during construction, never modify that map or those objects after that, and safely publish the object, then all threads that subsequently read from the map are guaranteed to see the values in the map, even without synchronization.
An example:
public class Stooges {
private final Map stooges = new HashMap();
public Stooges() {
stooges.put("Larry", "Fine");
stooges.put("Curly", "Howard");
stooges.put("Moe", "Howard");
}
// This is safe (as long as stooges can't be modified elsewhere)
public String getStoogeLastName(String name) {
return stooges.get(name);
}
}
public class Stooges {
private final Map stooges = new HashMap();
public Stooges() {
stooges.put("Larry", "Fine");
stooges.put("Curly", "Howard");
stooges.put("Moe", "Howard");
}
// This is safe (as long as stooges can't be modified elsewhere)
public String getStoogeLastName(String name) {
return stooges.get(name);
}
}
Previous concurrency bug articles: Poll - #1 - #2 - #3 - #4
PS. I should make a blatant plug here for two Java concurrency things. First, I’ve written a Java concurrency refcard for Dzone that is currently in editing and should be out in June (free as with all refcards). I spent a lot of time on it and I’m pretty proud with how the content turned out (many many thanks to those who sent ideas and suggestions on Twitter).
Second, I’ll be doing a talk at JavaOne on Java concurrency bugs like the one above. If you happen to be going to JavaOne, check it out!
link : http://tech.puredanger.com/2009/05/13/java-concurrency-bugs-5-inconsistent-synchronization/
分享到:
相关推荐
在Eclipse这款强大的Java开发工具中,为类的属性生成setter和getter方法是常见的操作,这些方法用于封装类的私有变量,确保数据的安全性。然而,为了代码的可读性和维护性,开发者通常会在setter和getter方法上添加...
与setter和getter方法有关的案例
在JavaScript中,setter和getter是两种特殊的方法,用于对象属性的访问控制。它们提供了一种方式来监视或修改对象属性的读取和赋值行为,从而增强了数据的安全性和灵活性。在JavaScript的ECMAScript规范中,setter和...
在Objective-C(简称OC)编程中,Setter和Getter方法是面向对象编程中的重要概念,用于访问和修改对象的私有属性。私有属性是类的内部数据,通常不希望外部直接访问,以确保数据的安全性和封装性。接下来,我们将...
总之,Objective-C中的setter和getter以及@property的引入,是为了解决数据封装和代码冗余的问题,提供了一种更加高效、灵活的方式来管理和访问类的实例变量。通过使用@property,我们可以专注于业务逻辑,而不需要...
在Java编程语言中,getter和setter方法是面向对象设计的一部分,它们主要用于封装对象的属性,以保护数据并提供访问控制。Eclipse是一款流行的集成开发环境(IDE),它提供了丰富的自动代码生成功能,包括生成getter...
这里提到的`GetterSetterUtil.class`和`GetterSetterUtil.java`文件可能是一个工具类,用于批量处理getter和setter的生成及注释同步。在实际项目中,我们可能需要编写这样的工具类来自动化这个过程,尤其是在处理...
在JFinal中,Model是连接数据库的主要对象,通常需要手动编写对应数据库表结构的实体类,包括属性、setter和getter方法。然而,这个自动生成工具则可以自动化这个过程,大大节省开发者的时间。 描述中提到的“自带...
JavaScript中的setter和getter是对象的存取器属性(Accessor properties),它们允许开发者定义当尝试获取或设置对象属性时触发的方法。这种机制能够帮助实现对属性的封装,防止外界直接访问对象的内部属性,同时...
jQuery 的 setter/getter 共用一个函数,通过是否传参来表明它是何种意义。简单说传参它是 setter,不传它是 getter。 一个函数具有多种意义在编程语言中并不罕见,比如函数重载:一组具有相同函数名,不同参数列表...
eclipse修改setter/getter配置,实现自动生成方法时自动抽取属性上的注释:https://blog.csdn.net/yyaijunji/article/details/84639609
c++的成员变量的get和set生成器,简单易用。
在深入学习Vue框架的过程中,理解和掌握getter和setter机制是非常重要的。Vue通过响应式原理,将普通的JavaScript对象属性转换成getter和setter,以此来实现数据的双向绑定和状态管理。下面我们将详细地探讨Vue中的...
在iOS开发中,getter和setter方法是Objective-C和Swift中对象属性访问的重要组成部分。它们用于获取(get)和设置(set)对象的属性值。本文将深入探讨getter和setter的概念、作用以及如何在代码中使用它们。 首先...
传统的做法是为每个字段手动编写getter和setter方法以实现数据的读取和设置。然而,这种方式繁琐且容易出错。Lombok库的引入解决了这一问题。Lombok提供了一种注解方式,通过在实体类上添加`@Data`注解,可以自动为...
在Java编程中,getter和setter方法是用于封装对象属性的重要工具。Eclipse作为一个强大的集成开发环境(IDE),提供了丰富的代码生成功能,包括自动为getter和setter添加注释。本篇文章将详细探讨如何在Eclipse中...
在Java编程中,getter和setter方法是面向对象设计原则中的封装特性的重要体现。它们用于访问和修改类的私有成员变量,确保数据的安全性。Eclipse是一款广泛使用的集成开发环境(IDE),它提供了丰富的代码自动补全和...
属性变量可以自动地生成 setter 和 getter 方法,这些方法是用于修改和访问内部属性值的接口。 属性变量的声明方式有多种,例如: ```objectivec @interface ViewController () @property (nonatomic, strong) ...