目录
在Java中,synchronized
是一个用于实现线程同步的关键字,它的核心作用是确保多线程环境下对共享资源的互斥访问,避免因并发操作导致的数据不一致问题
1. 核心作用
-
互斥访问:同一时间仅允许一个线程执行被
synchronized
修饰的代码块或方法。 -
可见性保障:线程释放锁时,会将共享变量的修改同步到主内存;获取锁时,会清空本地内存中的副本,保证数据的最新性。
2. 使用方式
(1) 修饰实例方法
-
锁对象:当前实例对象(
this
)。 -
适用场景:保护非静态方法中的共享资源。
-
public synchronized void increment() { count++; // 同一实例的多个线程会互斥执行此方法 }
(2) 修饰静态方法
-
锁对象:类的
Class
对象(如MyClass.class
)。 -
适用场景:保护静态资源或类级别的操作。
public static synchronized void log() {
// 所有线程访问此静态方法时互斥
}
(3) 修饰代码块
-
锁对象:显式指定任意对象(通常是私有对象,避免外部干扰)。
-
优点:更细粒度控制锁的范围,减少性能开销。
private final Object lock = new Object();
public void doSomething() {
// 非同步代码...
synchronized(lock) {
// 需要同步的代码块
}
}
3. 底层原理
-
Monitor(监视器锁):每个 Java 对象在 JVM 内部都会关联一个
Monitor
,用于管理线程的互斥访问。-
monitorenter
:线程尝试获取锁时执行,若锁未被占用则获取成功,否则进入阻塞队列。 -
monitorexit
:线程释放锁时执行,唤醒等待队列中的其他线程。
-
-
对象头(Object Header):
-
对象在内存中的布局包含
Mark Word
(存储锁状态和对象哈希码等元数据)。 -
Mark Word
在不同锁状态下会变化,记录锁的标志位、线程 ID、锁指针等信息。
-
4. 关键特性
-
可重入性:同一线程可重复获取同一把锁(避免死锁)。
-
非公平锁:默认不保证等待线程的获取顺序,但性能较高。
-
等待不可中断:线程在等待锁时无法被中断(
ReentrantLock
提供此功能)。
5. 对比其他同步工具
特性 | synchronized | ReentrantLock |
---|---|---|
使用方式 | 关键字,自动释放锁 | 需手动 lock() /unlock() |
公平性 | 非公平(默认) | 支持公平/非公平 |
条件变量 | 无 | 支持(Condition 类) |
可中断性 | 不支持 | 支持 |
锁超时 | 不支持 | 支持 tryLock(timeout) |
6. 典型应用场景
-
单例模式(双重检查锁定)。
-
线程安全的计数器、缓存、资源池等。
-
需要简单快速实现同步的小规模并发控制。
7:代码中的应用
1. 实例方法同步
场景:多个线程操作同一个对象的实例方法,需要保证线程安全。
public class Counter {
private int count = 0;
// 修饰实例方法:锁对象是当前实例(this)
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
// 创建两个线程操作同一个 Counter 实例
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Final count: " + counter.getCount()); // 输出 2000
}
}
-
两个线程
t1
和t2
操作同一个counter
实例的increment
方法。 -
synchronized
保证了同一时间只有一个线程能执行increment
方法,最终结果为2000
(若不加同步,结果可能小于 2000)。
2. 静态方法同步
场景:多个线程操作静态共享资源。
public class Logger {
private static int logCount = 0;
// 修饰静态方法:锁对象是 Logger.class
public static synchronized void log() {
logCount++;
System.out.println("Log count: " + logCount);
}
public static void main(String[] args) {
// 两个线程操作静态方法
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
Logger.log();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
Logger.log();
}
});
t1.start();
t2.start();
}
}
3. 同步代码块
场景:需要更细粒度的锁控制(仅同步部分代码)。
public class ResourcePool {
private final List<String> resources = new ArrayList<>();
private final Object lock = new Object(); // 私有锁对象
public void addResource(String resource) {
// 非同步代码(不涉及共享资源)
System.out.println("Preparing to add resource...");
// 同步代码块:锁是 lock 对象
synchronized (lock) {
resources.add(resource);
System.out.println("Added: " + resource);
}
}
public static void main(String[] args) {
ResourcePool pool = new ResourcePool();
Thread t1 = new Thread(() -> {
pool.addResource("A");
pool.addResource("B");
});
Thread t2 = new Thread(() -> {
pool.addResource("C");
pool.addResource("D");
});
t1.start();
t2.start();
}
}
4. 可重入性验证
场景:验证同一线程可以重复获取同一把锁。
public class ReentrantDemo {
public synchronized void methodA() {
System.out.println("Method A");
methodB(); // 调用另一个同步方法
}
public synchronized void methodB() {
System.out.println("Method B");
}
public static void main(String[] args) {
ReentrantDemo demo = new ReentrantDemo();
new Thread(demo::methodA).start();
}
}
线程进入 methodA
后,可以继续进入 methodB
,因为锁是同一个对象(this
),且支持可重入。