在Java多线程编程中,多个线程同时操作共享资源时,如果读写操作没有做好同步控制,就可能出现数据不一致的问题。getter方法负责读取共享数据,run方法通常包含线程的核心执行逻辑,二者的同步配合是保障线程安全的关键。

为什么要同步getter与run方法
当多个线程同时访问一个共享变量时,如果run方法中会修改这个变量,而getter方法会读取这个变量,没有同步的情况下就可能出现问题。比如线程A正在修改变量的值,修改到一半的时候线程B调用getter读取,就会拿到不完整的数据,也就是常说的脏读。
另外,Java内存模型中,每个线程有自己的工作内存,共享变量会存在主内存中。没有同步的话,线程可能不会及时把工作内存的变量刷新到主内存,或者其他线程无法感知变量的更新,导致读取到旧值。
run方法的同步实现
run方法是线程的执行入口,通常包含对共享资源的修改逻辑,需要根据场景选择同步方式。如果整个run方法的逻辑都需要保证原子性,可以直接给run方法加同步关键字,不过这种方式会限制线程的并发能力,更适合逻辑简单、执行时间短的场景。
更常见的做法是在run方法内部对操作共享资源的代码块加同步,这样其他不需要操作共享资源的逻辑可以并发执行,提升效率。下面是一个简单的示例,run方法内部修改共享计数变量:
public class CounterTask implements Runnable {
private int count = 0;
private final Object lock = new Object();
@Override
public void run() {
// 不需要同步的耗时操作可以放在同步块外面
long start = System.currentTimeMillis();
// 同步修改共享变量的代码块
synchronized (lock) {
for (int i = 0; i < 1000; i++) {
count++;
}
}
long end = System.currentTimeMillis();
System.out.println("线程执行耗时:" + (end - start) + "ms");
}
public int getCount() {
return count;
}
}
上面的代码中,lock是专门的锁对象,避免使用this作为锁对象,防止外部代码意外持有锁导致死锁。同步块只包裹修改count的代码,其他逻辑可以并发执行。
getter方法的同步策略
getter方法的同步需要根据共享变量的特性来决定。如果共享变量是基本类型,且run方法中的修改操作是原子的,那么可能不需要同步。比如int类型的赋值操作在Java中是原子的,但是如果是long或者double类型,在32位系统中赋值可能不是原子的,就需要同步。
如果共享变量是引用类型,或者run方法中的修改不是原子操作,那么getter方法也需要同步,保证读取到的是最新、完整的数据。同步getter的常用方式有两种:
1. 给getter方法加synchronized关键字
这种方式最简单,直接保证getter的同步,和run方法中使用的锁保持一致即可。示例代码如下:
public class SafeCounter {
private int count = 0;
private final Object lock = new Object();
public void increment() {
synchronized (lock) {
count++;
}
}
// getter方法加synchronized,使用同一个锁对象
public synchronized int getCount() {
synchronized (lock) {
return count;
}
}
}
2. 使用volatile关键字修饰共享变量
如果共享变量的修改是原子操作,比如int类型的赋值,那么可以用volatile修饰变量,保证变量的可见性,这样getter方法不需要加锁也可以读取到最新值。但volatile不能保证复合操作的原子性,比如count++这种操作包含读取、修改、写入三步,volatile无法保证原子性,还是需要同步。
public class VolatileCounter {
// volatile保证可见性,但是count++不是原子操作,还是需要同步修改逻辑
private volatile int count = 0;
private final Object lock = new Object();
public void increment() {
synchronized (lock) {
count++;
}
}
// getter不需要加锁,因为count是volatile的,读取的是最新值
public int getCount() {
return count;
}
}
同步的注意事项
- 锁对象要统一:run方法和getter方法必须使用同一个锁对象,否则同步会失效,两个方法还是可以并发执行,无法保证数据一致性。
- 避免锁粒度过大:不要把不需要同步的逻辑放在同步块中,否则会降低多线程的并发效率,比如IO操作、耗时计算都不应该放在同步块里。
- 优先使用专门的锁对象:不要直接使用
this或者字符串作为锁对象,防止外部代码意外持有锁,导致死锁或者同步失效。 - 复合操作必须同步:如果操作包含多个步骤,比如先判断再修改,即使getter做了同步,run方法中的复合操作也需要同步,否则还是会出现线程安全问题。
完整示例
下面是一个完整的多线程计数示例,run方法修改计数,getter方法读取计数,保证线程安全:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class MultiThreadCounter {
private int count = 0;
private final Object lock = new Object();
// run方法对应的任务逻辑
public void runTask() {
synchronized (lock) {
for (int i = 0; i < 100; i++) {
count++;
}
}
}
// 同步的getter方法
public int getCount() {
synchronized (lock) {
return count;
}
}
public static void main(String[] args) throws InterruptedException {
MultiThreadCounter counter = new MultiThreadCounter();
ExecutorService executor = Executors.newFixedThreadPool(5);
// 提交10个任务,每个任务执行runTask
for (int i = 0; i < 10; i++) {
executor.submit(counter::runTask);
}
// 关闭线程池,等待所有任务执行完成
executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);
// 读取最终的计数结果
System.out.println("最终计数结果:" + counter.getCount());
}
}
上述代码中,所有修改count的操作都在同一个锁的同步块中,getter方法也使用同一个锁同步,因此最终读取到的计数是正确的,不会出现数据不一致的问题。
Java多线程getter同步run方法同步synchronized关键字线程安全修改时间:2026-07-02 15:33:20