代码清单1:
public class StuffIntoPublic {
public Holder holder;
public void initialize() {
holder = new Holder(42);
}
}
代码清单2:
public class Holder {
private int n;
public Holder(int n) {
this.n = n;
}
public void assertSanity() {
if (n != n)
throw new AssertionError("This statement is false.");
}
}
你无法信赖局部创建对象,一个监视着处于不一致状态对象的线程,会看到尽管该对象自发布之后从未修改过,但是它的状态还是会发生突变。代码清单2的Holder使用代码清单1的不安全发布方式发布,那么除了发布线程,其他线程调用assertSanity时都可能抛出AssertionError异常。(此问题并非出在Holder类自身,而是Holder没有被正确的发布,但是将域n声明为final类型,使Holder成为不可变的,可以避免出现不正确发布的问题)
原因分析如下:
因为没有同步来确保Holder对其他线程可见,所以我们称Holder是“非正确发布的”,没有正确发布的对象会导致两种错误。首先,发布线程以外的任何线程都可以看到Holder域的过期值,因而看到的是一个null引用或者旧值,即使此刻Holder已经被赋与新值。其次,更坏的情况是,线程看到的Holder引用是最新的,然而Holder状态却是过期的。
(可以看出,在构造函数中设置的域值,应该是向这些域写入的第一个值,因此,没有“更旧”的值可以作为所谓的过期值。但是Object的构造函数会先于子类的构造函数运行,并首先向所有的域写入默认值。因此这些默认值可能成为域的过期值)
这使程序执行变得更加不可预测:线程首次读取某个域可能会看到过期值,再次读取该域会得到一个更新值,这正是assertSanity抛出AssertionError异常的原因。
不可变对象与初始化安全性
出于不可变对象的重要性,java存储模型为共享不可变对象提供了特殊的初始化安全性的保证。
另一方面,即使发布对象引用时没有使用同步,不可变对象仍然可以被安全地访问。为了获得这种初始化安全性的保证,应该满足所有不可变性的条件:不可修改的状态,所有域都是final类型的以及正确的构造。
不可变类的例子:
public class Holder {
private final int n;
public Holder(int n) {
this.n = n;
}
public void assertSanity() {
if (n != n)
throw new AssertionError("This statement is false.");
}
}
注意:不可变对象可以在没有额外同步的情况下,安全地用于任意线程;甚至发布它们时也不需要同步。
这个保证还会延伸到一个正确创建的对象中的所有final类型域的值,没有额外的同步,final域也可以被安全地访问。然而,如果final域指向可变对象,那么访问这些对象的状态时仍然需要同步。
分享到:
相关推荐
8. String与StringBuffer的区别:String是不可变对象,每次修改都会生成新对象,而StringBuffer是可变的,适合在需要多次修改字符串的场合使用,避免了不必要的内存开销。 9. Excel公式合法性:B和C选项是合法的...
例如,当你在一个地方修改了一个功能时,必须确保所有重复的地方都被正确更新。 2. **子程序太长**:长方法通常是代码难以阅读和理解的主要原因。它们往往包含了多种逻辑,使得理解其整体功能变得困难。 3. **循环...
- 解析:关系模型中,同一列的值通常是不可重复的,即列是不可变的,但这并不意味着任意两列的值不能相同。 14、在数据库设计中,表示用户业务流程的常用方法是 _________。 - 正确答案:A. 数据流图 DFD - 解析...
这是因为`String`类是不可变的,这意味着每次进行拼接操作都会创建一个新的`String`对象。为了提高性能,可以使用`StringBuilder`(非线程安全)或`StringBuffer`(线程安全)类来进行字符串的拼接,这两个类都提供...
- **正确解析**:当大写锁定键(Caps Lock)被激活时,按下 SHIFT 键确实可以输入小写字母。这是键盘设计的一个基本功能,有助于用户在需要混合大小写字母的情况下更加方便地进行输入。 ### 9. 查询设计器中的字段...
- 错误描述:不能将常量对象作为变参传递。 - 解决方法:确保参数类型不是常量。 38. **Constant or type identifier expected** - 错误描述:期望常量或类型标识符。 - 解决方法:确保提供的值是常量或类型...
- **知识点**: 对象中的某些值可以被设计为不可变的,这意味着一旦创建了对象,其状态就不能改变。 - **解析**: 题目中指出对象中的值是不可变的是正确的。这种特性常见于一些编程语言如Java中的`String`类或Python...
题目中的答案为 **正确**,这可能是指特定上下文中对象的值被认为是不可变的。 ### 面向对象编程 14. **基类成员的访问限制**:在面向对象编程中,如果派生类的成员函数不能直接访问基类中继承来的某个成员,则该...
3. **不可变对象**:如果对象在创建后其状态不能被修改,那么这个对象就是不可变的,如`String`类。不可变对象天然线程安全。 4. **线程局部变量**:`ThreadLocal`类提供了一种线程内的局部变量,每个线程都拥有...
在C++中,构造函数不能声明为虚函数,这是因为构造函数在对象创建时调用,而虚函数机制是在运行时通过对象的虚函数表来决定调用哪个函数的。 #### 8. 冒泡排序的时间复杂度 冒泡排序的时间复杂度为O(n^2),在最坏...
3. 排序算法中,最坏情况下比较次数最少的通常是堆排序,因为它在最坏情况下的时间复杂度为O(n log n),而冒泡排序、简单选择排序和直接插入排序在最坏情况下比较次数更多,故选项D是正确的。 4. 在计算机网络中,...
- **限制变量的作用域**:当`static`用于局部变量时,它使变量成为静态局部变量,意味着变量在整个程序运行期间都存在,即使函数调用结束,其值也不会丢失。而在全局变量中使用`static`,则限制了变量的作用域,使其...
- **接口隔离原则**:不应该强迫客户程序依赖它们不用的方法。 ### 多态的实现 - 通过虚函数实现:基类定义虚函数,派生类重写该虚函数。 - 通过模板实现:使用模板实现多态行为。 ### 深拷贝和浅拷贝的区别 - *...
- **错误说明**:不能将常量对象作为变量参数传递。 - **解决方案**:确保所有变量参数都是可变的。 38. **Constant or type identifier expected** - **错误说明**:期望找到常量或类型标识符。 - **解决方案*...
- **静态成员函数**:不与任何对象实例关联,无法通过虚函数机制实现多态。 - **内联函数**:内联函数在编译时会被替换为相应的代码,因此无法通过虚函数表进行动态绑定。 **8. 冒泡排序算法的时间复杂度** - ...
当一个进程调用`fork()`时,会创建一个新的进程(子进程),这个子进程是父进程的一个副本,拥有与父进程相同的程序计数器、堆栈和其他系统资源。 ##### Linux Linux操作系统是基于POSIX标准的开源操作系统,广泛...
在C++中,不能将构造函数声明为虚函数,因为这会导致对象的创建过程变得不确定。 ### 8. 排序算法的时间复杂度 冒泡排序算法的时间复杂度为O(n^2),在最坏的情况下,需要比较和交换的次数随数据量的平方增长。 ##...