背景
在并发编程学习中程,认识到了volatile实现的可见性和有序性,同时在也知道了synchronized也可以保证有序性和可见性,但是原子性的保证我们之前知道是通过加锁的方式实现的,那可见性和有序性是怎么保证的那?我们往下接着看。
初步认识
在Java中,synchronized关键字用于保证线程安全,它通过提供互斥锁(Mutex Lock)来确保一次只有一个线程可以访问被同步的代码块或方法。当一个线程进入同步代码块时,它会获得锁;当线程退出同步代码块时,它会释放锁。这种方式不仅防止了多个线程同时执行同一段代码导致的数据不一致问题,还提供了两个重要的特性:可见性和有序性。
实现可见性详解
可见性指的是当一个线程修改了共享变量的值,其他线程能够立即看到这个修改。在没有同步机制的情况下,JVM为了优化性能可能会将变量缓存到寄存器或者CPU缓存中,而不是直接读写主内存。这会导致一个线程对共享变量的更新不能及时反映给其他线程。
synchronized通过使用监视器锁(Monitor Lock)来实现可见性。当一个线程获取到锁并进入同步代码块时,它会首先刷新所有共享变量的最新值到工作内存中,从而确保该线程能看到之前所有线程对该变量所做的任何修改。同样,当线程退出同步代码块并释放锁时,它会把本地工作内存中的变更刷新回主内存,使得其他线程可以获得最新的数据。
实现有序性详解
有序性指的是程序按照代码编写的顺序来执行。由于现代处理器和编译器都可能进行指令重排序以优化性能,所以如果没有适当的同步机制,某些操作可能会看似乱序执行。这对多线程编程来说尤其危险,因为它可能导致难以预测的行为。
synchronized通过其内置的happens-before规则来保证有序性。具体来说:
- 每个解锁操作
unlock()happens-before 后续对同一个锁的加锁操作lock()。 - 这意味着如果一个线程A释放了一个锁,然后线程B获得了相同的锁,那么线程A在释放锁之前执行的所有操作对于线程B都是可见的,并且这些操作看起来是按顺序发生的。
因此,synchronized不仅防止了并发冲突,还确保了跨线程的操作具有正确的可见性和顺序,这对于编写正确、可靠的并发程序至关重要。然而,值得注意的是,虽然synchronized提供了强大的同步功能,但它也可能带来性能开销,特别是在高并发环境下。因此,在设计并发程序时,应该权衡使用synchronized和其他更轻量级的同步工具如volatile、原子类(如AtomicInteger)以及显式的锁机制(如ReentrantLock)。