Synchronized和lock的区别
- 本质和类型
- Synchronized:是Java中的一个关键字,它属于JVM层面,由内置语言实现。
- Lock:是Java中的一个接口(java.util.concurrent.locks.Lock),需要通过实现它的类(如ReentrantLock)来使用,属于JDK层面,通过代码实现。
- 锁的获取和释放
- Synchronized:隐式地获取和释放锁。当线程进入synchronized代码块或方法时,会自动获取锁;当线程执行完毕或抛出异常时,会自动释放锁。
- Lock:显式地获取和释放锁。需要通过调用lock()方法来获取锁,并在finally块中调用unlock()方法来释放锁,以确保锁的正确释放。
- 锁的灵活性
- Synchronized:提供的锁是非中断的、不可判断的(无法判断锁是否被持有)、非公平的(线程获取锁的顺序是不确定的)。
- Lock:提供了更高的灵活性。Lock是可中断的(可以通过lockInterruptibly()方法中断等待锁的线程)、可判断的(可以通过tryLock()等方法判断锁是否可用)、公平的(可以通过构造ReentrantLock时传入true参数来创建公平锁,确保等待时间最长的线程先获取锁)。
- 锁的性能
- Synchronized:在JVM层面实现,优化较好,但在大量同步代码块时可能会有性能问题。
- Lock:由于可以手动控制锁的获取和释放,以及支持多种锁的特性(如可中断、可判断、公平锁等),因此在某些情况下可以提供更好的性能。但这也需要程序员更加小心地管理锁,以避免死锁等问题。
- 使用场景
- Synchronized:适用于简单的同步场景,如少量的同步代码块或方法。
- Lock:适用于需要更灵活控制锁的场景,如需要手动中断等待锁的线程、需要判断锁是否可用、需要实现公平锁等。
- 底层实现
- Synchronized:基于JVM的内置锁机制,具体实现依赖于JVM的实现。
Lock:基于AQS(AbstractQueuedSynchronizer)框架实现,这是一个用于构建锁和其他同步类的框架。
综上所述,Synchronized和Lock各有优缺点,选择哪个取决于具体的应用场景和需求。在大多数情况下,Synchronized已经足够满足需求,因为它简单且易于使用。但在需要更精细控制锁的场景下,Lock可能是一个更好的选择。
Synchronized
Synchronized详解
- Synchronized是Java中的一个关键字,用于控制多线程的访问,确保同一时刻只有一个线程可以进入临界区(即被synchronized保护的代码块或方法)。它主要用于保护多线程环境下对共享资源访问的安全性
底层原理
- Synchronized的底层实现主要依赖于JVM中的monitor对象(也称为管程或监视器)。每个Java对象都有一个与之关联的monitor对象,该对象由JVM的C++代码实现,具体表现为ObjectMonitor。当一个线程进入synchronized代码块或方法时,它会尝试获取与对象关联的monitor对象的锁。如果获取成功,则线程进入临界区执行代码;如果获取失败(即锁已被其他线程持有),则线程会被阻塞,直到锁被释放。
- monitor对象内部包含了一些关键的字段,如:
- _owner:指向当前持有锁的线程。
- _EntryList:关联没有抢到锁的线程,这些线程处于阻塞状态。
- _WaitSet:关联调用了wait方法的线程,这些线程处于等待状态。
synchronized的锁释放是自动的,当线程退出synchronized代码块或方法时,JVM会自动释放锁。
锁升级过程和原理
在Java中,锁的升级过程主要是为了优化性能,减少不必要的锁竞争和上下文切换。synchronized的锁升级过程主要包括以下几个阶段:
- 无锁状态:
在没有线程竞争的情况下,对象处于无锁状态,线程可以自由访问共享资源。 - 偏向锁状态:
当一个线程访问同步代码块并获取锁时,JVM会在对象头和栈帧中的锁记录里存储锁偏向的线程ID。以后该线程再次进入同步代码块时,只需比较当前线程ID和对象头中的线程ID是否一致,如果一致则无需进行CAS操作即可直接获取锁。偏向锁的主要目的是减少在单线程环境下的锁竞争和CAS操作。 - 轻量级锁状态:
当有其他线程来竞争锁时,偏向锁会升级为轻量级锁。轻量级锁通过CAS操作来尝试获取锁,如果成功则线程获得锁;如果失败,则根据具体情况进行锁膨胀或重入处理。轻量级锁的主要目的是减少多线程环境下的锁竞争和上下文切换。 - 重量级锁状态:
当轻量级锁无法满足需求时(如多个线程频繁竞争锁),锁会进一步升级为重量级锁。重量级锁使用操作系统的互斥量来实现,当线程请求锁时,如果锁被其他线程占用,则线程会被挂起并等待被唤醒。重量级锁的主要目的是保证数据的正确性和一致性,但性能较低。
锁升级的过程是JVM自动进行的,无需程序员手动干预。通过锁升级机制,JVM可以根据实际情况选择最合适的锁策略,以优化程序的性能。
综上所述,Synchronized是Java中用于控制多线程访问共享资源的关键字,其底层实现依赖于JVM中的monitor对象。通过锁升级机制,JVM可以根据实际情况自动选择合适的锁策略以优化性能。
提示:这里可以添加技术名词解释
例如:
- Bert
- GPT 初代
- GPT-2
- GPT-3
- ChatGPT
Lock
Lock是Java并发编程中的一个重要接口,位于java.util.concurrent.locks包下。它提供了比synchronized关键字更广泛的锁操作,能以更优雅的方式处理线程同步问题。Lock接口支持多种锁规则,如重入、公平性等,可以在非阻塞式结构的上下文中使用。Lock接口的实现类主要有ReentrantLock(可重入锁)等。
ReentrantLock 介绍
ReentrantLock是Lock接口的一个实现类,是一个独占式可重入锁。相比synchronized,ReentrantLock提供了更高的灵活性和控制能力,如支持公平锁、非公平锁、可中断、超时等特性。ReentrantLock的底层实现依赖于AbstractQueuedSynchronizer(AQS)同步器。
原理:
-
ReentrantLock的实现原理主要涉及到AQS(AbstractQueuedSynchronizer)同步器和Condition条件队列。AQS是一个用于构建锁和其他同步类的框架,它内部维护了一个状态变量和一个等待队列。对于ReentrantLock来说,状态变量表示当前锁被持有的次数,等待队列用于存放等待获取锁的线程。
-
ReentrantLock通过AQS提供的队列和状态管理机制,实现了可重入锁的机制。当一个线程尝试获取锁时,如果锁的状态为0(表示锁未被持有),则线程可以直接获取锁,并将状态设置为1。如果锁的状态不为0(表示锁已被其他线程持有),则线程将被加入等待队列,并被挂起,直到锁被释放。当持有锁的线程再次尝试获取锁时,如果当前线程已经持有锁,则直接增加状态值;如果不是,则将该线程加入等待队列。当一个线程释放锁时,AQS会将状态值减1,如果状态值为0,则唤醒等待队列中的下一个线程。
公平锁与非公平锁实现原理
ReentrantLock支持公平锁和非公平锁两种模式。
- 非公平锁(NonfairSync):在尝试获取锁时,不考虑其他线程是否在等待队列中等待锁,直接尝试获取。如果获取成功,则当前线程持有锁;如果失败,则根据AQS的等待队列机制进行等待。非公平锁的优点在于吞吐量比公平锁大,但可能会造成线程饥饿现象.
- 公平锁(FairSync):在尝试获取锁时,会先检查等待队列是否为空。如果为空或者当前线程是等待队列的第一个,则尝试获取锁;否则,将当前线程加入等待队列,并按照FIFO(先进先出)的规则等待获取锁。公平锁确保了线程按照申请锁的顺序来获取锁,避免了线程饥饿现象,但可能会降低吞吐量。
AQS 的使用原理
AQS(AbstractQueuedSynchronizer)是Java并发包中的一个抽象基类,为实现锁和其他同步器提供了一种统一的框架。AQS内部维护了一个状态变量和一个等待队列。状态变量用于表示被保护资源的状态,等待队列用于存放等待获取资源的线程。
AQS的主要方法包括:
- acquire(int arg):尝试获取锁。如果获取成功,则返回;如果失败,则将当前线程加入等待队列并阻塞。
- release(int arg):释放锁。将状态变量减1,如果状态变量变为0,则唤醒等待队列中的下一个线程。
- tryAcquire(int acquires):尝试获取锁,但不阻塞当前线程。如果获取成功,则返回true;如果失败,则返回false。
- tryRelease(int releases):尝试释放锁,但不唤醒等待线程。如果释放成功,则返回true;如果失败,则返回false。