为什么我的synchronized方法未生效?同步与无锁线程执行差异验证
嘿,我来帮你理清楚synchronized关键字和无锁情况下线程执行的差异~
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.stream.IntStream; public class mainClass { static int count = 0; public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(2); Runnable r = new Runnable() { public synchronized void run() { count = count + 1; // 推测你这里应该还有后续逻辑,比如循环提交任务、关闭线程池等 } }; // 假设你是循环提交任务来验证,比如执行1000次自增 IntStream.range(0, 1000).forEach(i -> executor.submit(r)); executor.shutdown(); // 等待线程池执行完毕后打印结果 while (!executor.isTerminated()) {} System.out.println("最终count值: " + count); } }
1. 你的当前代码(带synchronized)的效果
因为你只创建了一个Runnable实例r,所以线程池里的两个线程调用synchronized run()时,会争抢这个r对象的内置锁(monitor lock)。同一时间只有一个线程能进入run方法执行count +=1,这就保证了自增操作的原子性,最终count的结果会是准确的1000。
2. 去掉synchronized的情况
如果把run方法的synchronized关键字去掉,代码变成:
Runnable r = new Runnable() { public void run() { count = count + 1; } };
这时候count +=1不是原子操作,它拆分成了三步:
- 读取当前
count的数值 - 对数值执行加1操作
- 将新值写回
count
当两个线程同时执行这三步时,就会出现竞态条件:比如线程A读取count=5,线程B也读取count=5,两者都加1得到6,再写回count,最终count只增加了1,但实际上两个线程都执行了自增。最终运行结果会小于1000,而且每次运行的结果都可能不一样(比如985、993等)。
3. 容易踩的坑:锁错对象
如果你创建多个Runnable实例,比如:
IntStream.range(0, 1000).forEach(i -> executor.submit(new Runnable() { public synchronized void run() { count +=1; } }));
这时候每个Runnable实例都有自己的锁,线程之间不会互相阻塞,同样会出现竞态条件,count结果还是不准确。这种情况应该锁共享的对象,比如类对象或者专门定义的锁:
// 方式1:锁类对象 private static void increment() { synchronized (mainClass.class) { count +=1; } } // 方式2:用专门的共享锁对象 private static final Object lock = new Object(); private static void increment() { synchronized (lock) { count +=1; } }
为了更明显看到差异,你可以运行下面两个版本的代码:
版本1:无锁版本
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.stream.IntStream; public class NoLockDemo { static int count = 0; public static void main(String[] args) throws InterruptedException { ExecutorService executor = Executors.newFixedThreadPool(10); // 用更多线程放大差异 IntStream.range(0, 10000).forEach(i -> executor.submit(() -> count++)); executor.shutdown(); while (!executor.isTerminated()) {} System.out.println("无锁情况下的count: " + count); } }
运行结果通常会小于10000,比如9940左右,每次结果都不固定。
版本2:正确加锁的版本
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.stream.IntStream; public class SynchronizedDemo { static int count = 0; private static final Object lock = new Object(); public static void main(String[] args) throws InterruptedException { ExecutorService executor = Executors.newFixedThreadPool(10); IntStream.range(0, 10000).forEach(i -> executor.submit(() -> { synchronized (lock) { count++; } })); executor.shutdown(); while (!executor.isTerminated()) {} System.out.println("带锁情况下的count: " + count); } }
运行结果每次都是准确的10000,因为锁保证了同一时间只有一个线程能操作count,完全避免了竞态条件。
简单来说:synchronized的核心作用是给共享资源的操作加“互斥锁”,保证线程安全;无锁情况下,多个线程同时操作共享变量会互相干扰,导致结果不可预期。
内容的提问来源于stack exchange,提问作者Or251

