Java内存模型定义了多线程环境下变量的访问规则,是保证并发程序正确性的底层规范。正确同步是Java内存模型中提到的保证程序执行结果符合预期的重要手段,很多开发者会思考是否可以将正确同步的范围缩小到局部组件,减少全局同步带来的性能损耗。

Java内存模型核心规则回顾
Java内存模型规定了所有变量都存储在主内存中,每个线程有自己的工作内存,工作内存保存了线程使用变量的副本。线程对变量的操作都必须在工作内存中进行,不能直接读写主内存的变量。线程间变量值的传递需要通过主内存来完成,这就带来了可见性、有序性、原子性的问题。
Java内存模型通过happens-before规则来保证多线程操作的有序性和可见性,常见的happens-before规则包括:
- 程序顺序规则:同一个线程内,按照代码顺序,前面的操作happens-before后面的操作
- volatile变量规则:对一个volatile变量的写操作happens-before后续对这个变量的读操作
- 锁规则:对一个锁的解锁操作happens-before后续对这个锁的加锁操作
- 线程启动规则:Thread对象的start()方法调用happens-before线程内的所有操作
- 线程终止规则:线程内的所有操作happens-before其他线程检测到该线程已经终止
正确同步的定义与判定
Java内存模型中提到的正确同步,指的是程序的所有执行结果都符合顺序一致性的预期,也就是程序的执行结果和所有操作按照某种顺序依次执行的结果一致。要满足正确同步,需要保证所有存在数据竞争的操作都通过happens-before规则建立了顺序关系。
数据竞争指的是两个或者多个线程同时访问同一个变量,且至少有一个是写操作,同时这些操作之间没有happens-before关系。如果存在数据竞争,程序的执行结果就可能出现不符合预期的情况。
局部组件的边界与同步范围
局部组件指的是程序中相对独立的功能模块,比如一个工具类、一个业务处理单元,或者是一个封装了内部状态的对象。局部组件如果只在单线程环境下被访问,那么不需要任何同步措施,自然也不存在正确同步的应用问题。
如果局部组件会被多个线程访问,那么就需要判断组件内部是否存在共享的可变状态。如果组件内部没有共享可变状态,比如所有状态都是线程私有的,或者状态是不可变的,那么也不需要同步。只有当组件内部存在多个线程共享的可变状态时,才需要考虑同步问题。
将正确同步应用于局部组件的可行性分析
可以将正确同步的概念应用于局部组件,只要局部组件内部的共享可变状态的所有访问操作都建立了正确的happens-before关系,那么这个局部组件本身就是正确同步的,不需要依赖外部组件的同步措施。
下面通过一个计数器组件的例子来说明:
// 局部计数器组件,内部维护一个共享计数变量
public class LocalCounter {
// 使用volatile保证可见性
private volatile int count = 0;
// 递增方法,通过volatile写保证happens-before关系
public void increment() {
count++;
}
// 获取当前计数值,通过volatile读保证happens-before关系
public int getCount() {
return count;
}
}
上面的LocalCounter组件内部只有一个volatile修饰的count变量,increment方法的写操作和getCount方法的读操作之间通过volatile变量规则建立了happens-before关系,所以这个局部组件本身是正确同步的,不管外部如何调用,只要调用方式符合组件的约定,就不会出现数据竞争问题。
如果局部组件内部的状态访问没有建立正确的happens-before关系,那么即使外部做了同步,也不能保证组件内部的正确性。比如下面的错误示例:
// 错误的局部计数器组件,没有正确同步
public class WrongLocalCounter {
private int count = 0;
// 没有同步措施,多线程调用会出现数据竞争
public void increment() {
count++;
}
public int getCount() {
return count;
}
}
这个WrongLocalCounter组件内部没有任何同步措施,即使外部调用时对组件实例加了锁,也不能保证组件内部的正确性,因为组件内部的count++操作本身不是原子操作,而且没有可见性保证。
注意事项
将正确同步应用于局部组件时,需要注意以下几点:
- 明确局部组件的边界,确定组件内部的共享可变状态范围,不要遗漏任何共享状态
- 局部组件内部的同步措施需要自洽,不能依赖外部调用者的同步行为,否则组件的复用性会变差
- 如果局部组件和外部组件存在共享状态的交互,那么需要保证交互的操作也建立了正确的happens-before关系,不能只保证局部组件内部的同步
- 正确同步的局部组件不代表没有性能开销,比如volatile变量的访问比普通变量开销更大,需要根据实际场景选择同步方式
总的来说,正确同步的概念完全可以应用于局部组件,只要局部组件内部的共享可变状态的所有访问操作都符合Java内存模型的happens-before规则,就能保证局部组件的正确性。这种方式可以让同步的范围更精准,减少不必要的全局同步开销,提升程序的性能。