Java 线程同步的原理主要基于两个核心概念:互斥(Mutual Exclusion)和可见性(Visibility)。互斥保证在同一时刻只有一个线程可以访问代码的临界区(critical section),而可见性保证一个线程对共享变量的修改能够及时地被其他线程看到。Java 提供了多种机制来实现线程同步,如 synchronized
关键字、volatile
关键字、锁(Locks)等。
1. synchronized
当一个线程访问某对象的 synchronized
方法或同步代码块时,这个线程获得了该对象的锁。在该线程持有锁期间,其他任何线程都无法访问这个对象的任意 synchronized
方法或代码块。一旦持锁线程释放了锁(即离开了同步方法或代码块),其他线程就可以获得锁并访问同步的代码。
原理
- 锁的概念:Java 中的每个对象都有一个内部锁(monitor)。当通过
synchronized
关键字来同步方法或代码块时,就是通过请求这个内部锁来提供互斥访问的。 - 锁的获取和释放:
- 当进入一个同步方法或代码块时,线程尝试获取对象的锁。如果锁没有被其他线程持有,那么该线程获得锁并进入方法或代码块。
- 当线程离开同步方法或代码块时,它会释放锁,允许其他线程获取锁。
2. volatile
volatile
关键字用于声明 Java 变量,可以保证该变量对所有线程的可见性。当一个变量被声明为 volatile
后,线程对这个变量的读写都会直接操作在主内存中,而不是线程本地内存。
原理
- 内存可见性:
volatile
保证了不同线程对共享变量操作的可见性。使用volatile
关键字标记的变量,会强制所有线程都从主内存中读取该变量的值,而不是从线程工作内存中读取。 - 禁止指令重排序:在
volatile
变量前后的代码不会进行重排序。这个特性可以用于保证在某些情况下的有序性。
3. 锁(Locks)
Java 从 JDK 1.5 开始,提供了 java.util.concurrent.locks
包,其中包含了一系列的锁实现,如 ReentrantLock
。这些锁提供比 synchronized
更丰富的功能,例如尝试非阻塞获取锁(tryLock)、可中断的锁获取等。
原理
- 显式锁:与
synchronized
自动管理锁不同,Lock
接口允许更灵活的锁操作。它需要手动地获取和释放锁,给予了开发者更大的控制空间。 - 可中断:锁等待(如
lockInterruptibly()
)可以响应中断,这一点synchronized
块无法做到。 - 尝试锁定:
tryLock()
方法尝试获取锁,如果锁不可用,该方法会立即返回,不会使线程阻塞。
总结
Java 线程同步的核心在于控制对共享资源的并发访问,以保证线程安全。通过使用 synchronized
、volatile
和 Locks
等机制,Java 提供了多种方式来实现线程之间的同步。每种机制都有其适用场景,理解它们的工作原理有助于更好地选择合适的同步策略。