Java中如何访问TimerTask内修改的类变量并实现双向同步
问题核心原因
首先纠正你的认知错误:匿名内部类访问外部类的成员变量时,持有的是外部类实例的引用,并不是复制变量副本,你遇到的不同步问题主要来自三个原因:
- 原示例代码存在语法错误:
main是静态方法,不能直接访问非静态的count、fooTimer成员和startTimerTask实例方法,你必须先实例化Foo类对象才能操作对应成员,这是你运行结果不符合预期的首要原因 - 线程可见性问题:Timer启动的是独立后台线程,和主线程属于不同线程,没有可见性保证的前提下,线程会缓存变量值,感知不到其他线程对变量的修改
- 线程安全问题:
count的自增、赋值操作不是原子性的,多线程并发修改时会出现值覆盖的问题
你之前用单元素数组测试仍然有问题,也是因为没有处理可见性和同步逻辑导致的。
实现方案
要实现主线程和TimerTask对count的修改双向同步,推荐以下两种成熟方案:
方案1:使用AtomicInteger原子类(推荐)
java.util.concurrent.atomic.AtomicInteger自带原子操作保证和变量可见性,无需手动加锁,代码简洁性能好,修改后的可运行代码如下:
import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.atomic.AtomicInteger; public class Foo { // 用AtomicInteger替代基础类型int,线程安全自带可见性 private final AtomicInteger count = new AtomicInteger(0); private Timer fooTimer; public void startTimerTask() { TimerTask fooTimerTask = new TimerTask() { @Override public void run() { // 原子自增,直接操作共享的count实例 int currentVal = count.incrementAndGet(); System.out.println("TimerTask输出:" + currentVal); } }; fooTimer.scheduleAtFixedRate(fooTimerTask, 0, 2000); } public static void main(String args[]) throws InterruptedException { // 先实例化Foo对象,所有操作基于同一个实例的成员 Foo fooInstance = new Foo(); fooInstance.fooTimer = new Timer(); fooInstance.startTimerTask(); // 延迟1秒模拟任务运行一段时间后主线程修改count Thread.sleep(1000); // 主线程修改count,修改结果会自动同步到TimerTask int afterModify = fooInstance.count.addAndGet(5); System.out.println("主线程修改后输出:" + afterModify); } }
运行后你会看到主线程加5之后,TimerTask后续的自增会基于修改后的值累加,双向修改完全同步。
方案2:volatile+同步锁(基础类型实现方案)
如果你坚持要用基础int类型实现,可以通过加volatile关键字保证可见性,加同步锁保证操作原子性,核心代码片段如下:
public class Foo { // volatile保证变量可见性,禁止线程缓存count值 private volatile int count = 0; // 全局锁对象,所有修改count的操作都要持有这把锁 private final Object countLock = new Object(); private Timer fooTimer; public void startTimerTask() { TimerTask fooTimerTask = new TimerTask() { @Override public void run() { synchronized (countLock) { count++; System.out.println("TimerTask输出:" + count); } } }; fooTimer.scheduleAtFixedRate(fooTimerTask, 0, 2000); } public static void main(String args[]) throws InterruptedException { Foo fooInstance = new Foo(); fooInstance.fooTimer = new Timer(); fooInstance.startTimerTask(); Thread.sleep(1000); // 主线程修改也要加同一把锁 synchronized (fooInstance.countLock) { fooInstance.count +=5; System.out.println("主线程修改后输出:" + fooInstance.count); } } }
内容的提问来源于stack exchange,提问作者Aakash Patel
相关产品推荐
相关产品推荐

