Java synchronized用法详解:从原理到实战,一篇搞定线程安全
** 还在为Java线程安全头疼?本文带你彻底搞懂synchronized关键字的用法、原理及最佳实践,让你的代码告别并发Bug!

引言:为什么Java并发编程离不开synchronized?
在Java的世界里,多线程就像一把双刃剑,它能充分利用CPU资源,提升程序性能,但也带来了无数令人头疼的线程安全问题,当多个线程同时共享和修改同一个数据时,如果不加以控制,就可能导致数据不一致、程序崩溃等严重后果。
synchronized,作为Java并发编程的元老级关键字,为我们提供了一种简单、可靠的机制来保证代码块的原子性、可见性和有序性,从而解决线程安全问题,无论是初学者还是资深开发者,深入理解synchronized的用法都是一项必备技能。
本文将带你从零开始,全面、系统地剖析Java synchronized的用法,并结合实例代码,让你彻底掌握它。
synchronized是什么?核心作用揭秘
synchronized是Java中的一个关键字,中文意为“同步”,它可以用来修饰以下几种结构:

- 实例方法
- 静态方法
- 代码块
其核心作用是确保在同一时刻,只有一个线程可以执行被synchronized修饰的代码,这就像给一段代码加了一把“锁”,任何想要执行这段代码的线程,都必须先获取到这把锁。
synchronized的三种用法详解(附代码示例)
synchronized的用法灵活多样,我们分别来看它在不同场景下的应用。
修饰实例方法(对象锁)
当synchronized修饰一个非静态的实例方法时,它锁定的是当前对象实例(this)。
代码示例:

public class SynchronizedMethodExample {
private int count = 0;
// synchronized修饰实例方法
public synchronized void increment() {
count++;
System.out.println(Thread.currentThread().getName() + " - Count: " + count);
}
public int getCount() {
return count;
}
public static void main(String[] args) {
SynchronizedMethodExample example = new SynchronizedMethodExample();
// 创建两个线程,同时调用increment方法
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
example.increment();
}
}, "Thread-A");
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
example.increment();
}
}, "Thread-B");
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final Count: " + example.getCount()); // 输出应为10
}
}
原理解析:
在上面的例子中,thread1和thread2共享同一个example对象,当thread1调用increment()方法时,它会获取example对象的锁,在thread1执行完increment()方法并释放锁之前,thread2无法进入该方法,这就保证了count++操作的原子性,最终结果正确。
关键点:
- 锁的是对象本身(this)。
- 不同对象实例的
synchronized方法互不干扰,因为它们锁的是不同的对象。
修饰静态方法(类锁)
当synchronized修饰一个静态方法时,它锁定的是当前类的Class对象。
代码示例:
public class SynchronizedStaticMethodExample {
private static int staticCount = 0;
// synchronized修饰静态方法
public static synchronized void staticIncrement() {
staticCount++;
System.out.println(Thread.currentThread().getName() + " - Static Count: " + staticCount);
}
public static int getStaticCount() {
return staticCount;
}
public static void main(String[] args) {
// 创建两个线程,调用静态方法
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
SynchronizedStaticMethodExample.staticIncrement();
}
}, "Thread-C");
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
SynchronizedStaticMethodExample.staticIncrement();
}
}, "Thread-D");
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final Static Count: " + SynchronizedStaticMethodExample.getStaticCount()); // 输出应为10
}
}
原理解析:
无论创建多少个SynchronizedStaticMethodExample的实例,它们的staticIncrement()方法都共享同一个Class对象,当一个线程(如thread1)调用staticIncrement()时,它会锁定SynchronizedStaticMethodExample.class,其他所有线程(包括其他实例的线程)都无法再调用该类的任何synchronized静态方法,直到锁被释放。
关键点:
- 锁的是类的Class对象。
- 所有实例共享这把“类锁”,不同实例的
synchronized静态方法会相互排斥。
修饰代码块(更灵活的锁)
这是synchronized最灵活、也是最推荐的用法之一,它可以指定任意对象作为锁,从而精确控制锁的粒度。
语法:
synchronized (锁对象) { // 需要同步的代码 }
代码示例:
public class SynchronizedBlockExample {
private int count = 0;
// 定义一个锁对象
private final Object lock = new Object();
public void incrementWithBlock() {
// synchronized代码块,锁定lock对象
synchronized (lock) {
count++;
System.out.println(Thread.currentThread().getName() + " - Block Count: " + count);
}
}
public int getCount() {
return count;
}
public static void main(String[] args) {
SynchronizedBlockExample example = new SynchronizedBlockExample();
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
example.incrementWithBlock();
}
}, "Thread-E");
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
example.incrementWithBlock();
}
}, "Thread-F");
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final Block Count: " + example.getCount()); // 输出应为10
}
}
原理解析:
在这个例子中,我们创建了一个普通的Object实例lock作为锁,只有当线程进入synchronized (lock)代码块时,才会尝试获取lock对象的锁,这种方式的优点是:
- 锁粒度更细:可以只对关键代码段加锁,而不是整个方法,减少锁的持有时间,提高并发性能。
- 锁对象可定制:可以灵活选择锁定哪个对象,例如锁定方法参数、类的私有成员等。
关键点:
- 锁的是括号中指定的对象。
- 必须保证多个线程使用的是同一个锁对象,否则锁机制失效。
- 通常推荐使用
private final Object作为锁对象,避免外部误修改。
synchronized的底层原理:JVM是如何实现锁的?
了解底层原理能帮助我们更深刻地理解synchronized的行为,在Java 6之后,JVM对synchronized进行了大量优化,其实现主要涉及三种锁状态:
-
偏向锁(Biased Locking)
- 思想:假设锁总是由同一个线程获取,当一个线程第一次获取锁时,JVM会将这个线程的ID记录在锁对象头中,该线程之后再次获取锁时,无需进行CAS(Compare-And-Swap)操作,直接进入,开销极小。
- 场景:适用于单线程几乎总是持有锁的情况。
-
轻量级锁(Lightweight Locking)
- 思想:当有另一个线程尝试获取锁时,偏向锁会升级为轻量级锁,竞争线程会通过自旋(忙等待)的方式尝试获取锁,而不是立即阻塞。
- 场景:适用于线程竞争不激烈的情况,自旋避免了线程上下文切换的开销。
-
重量级锁(Heavyweight Locking)
- 思想:当自
