ReentrantLock和 synchronized

Lock 接口和synchronized的区别

synchronized 没有超时机制 非公平锁
ReentrantLock 显示的获得、释放锁,synchronized 隐式获得释放锁
ReentrantLock 可响应中断、可轮回,synchronized 是不可以响应中断的,为处理锁的不可用性提供了更高的灵活性
ReentrantLock 是 API 级别的,synchronized 是 JVM 级别的
ReentrantLock 可以实现公平锁
ReentrantLock 通过 Condition 可以绑定多个条件
底层实现不一样, synchronized 是同步阻塞,使用的是悲观并发策略,lock 是同步非阻
塞,采用的是乐观并发策略
Lock 是一个接口,而 synchronized 是 Java 中的关键字,synchronized 是内置的语言实现。
synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;
而 Lock 在发生异常时,如果没有主动通过 unLock()去释放锁,则很可能造成死锁现象,
因此使用 Lock 时需要在 finally 块中释放锁。
Lock 可以让等待锁的线程响应中断,而 synchronized 却不行,使用 synchronized 时,
等待的线程会一直等待下去,不能够响应中断。
通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
Lock 可以提高多个线程进行读操作的效率,既就是实现读写锁等
ReentrantLock 使用起来比较灵活,但是必须有释放锁的配合动作;
ReentrantLock 必须手动获取与释放锁,而 synchronized 不需要手动释放和开启锁;
ReentrantLock 只适用于代码块锁,而 synchronized 可以修饰类、方法、变量等。
二者的锁机制其实也是不一样的。ReentrantLock 底层调用的是 Unsafe 的park 方法加锁,
synchronized 操作的应该是对象头中 mark word
Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:
普通同步方法,锁是当前实例对象
静态同步方法,锁是当前类的class对象
同步方法块,锁是括号里面的对象
从特性来看,Synchronized 是 Java 中的同步关键字,Lock 是 J.U.C 包中提供的接口,这个接口有很多实现类,其中就包括 ReentrantLock 重入锁,
Synchronized 可以通过两种方式来控制锁的粒度,一种是把synchronized 关键字修饰在方法层面,另一种是修饰在代码块上,并且我们可以通过 Synchronized 加锁对象的声明周期来控制锁的作用范围,比如锁对象是静态对象或者类对象,那么这个锁就是全局锁。如果锁对象是普通实例对象,那这个锁的范围取决于这个实例的声明周期。
Lock 锁的粒度是通过它里面提供的 lock()和 unlock()方法决定的,包裹在这两个方法之间的代码能够保证线程安全性。而锁的作用域取决于 Lock 实例的生命周期。Lock 比 Synchronized 的灵活性更高,Lock 可以自主决定什么时候加锁,什么时候释放锁,只需要调用 lock()和 unlock()这两个方法就行,同时 Lock 还提供了非阻塞的竞争锁方法 tryLock()方法,这个方法通过返回 true/false 来告诉当前线程是否已经有其他线程正在使用锁。
Synchronized 由于是关键字,所以它无法实现非阻塞竞争锁的方法,另外,Synchronized 锁的释放是被动的,就是当 Synchronized 同步代码块执行完以后或者代码出现异常时才会释放。
Lock 提供了公平锁和非公平锁的机制,公平锁是指线程竞争锁资源时,如果已经有其他线程正在排队等待锁释放,那么当前竞争锁资源的线程无法插队。而非公平锁,就是不管是否有线程在排队等待锁,它都会尝试去竞争一次锁。Synchronized 只提供了一种非公平锁的实现。
从性能方面来看,Synchronized 和 Lock 在性能方面相差不大,在实现上会有一些区别,Synchronized 引入了偏向锁、轻量级锁、重量级锁以及锁升级的方式来优化加锁的性能,而 Lock 中则用到了自旋锁的方式来实现性能优化。

Lock 接口和synchronized 对比它有什么优势

Lock 接口优势有:
(1)可以使锁更公平
(2)可以使线程在等待锁的时候响应中断
(3)可以让线程尝试获取锁,并在无法获取锁的时候立即返回或者等待一段时间
(4)可以在不同的范围,以不同的顺序获取和释放锁
(1)ReentrantLock 可以对获取锁的等待时间进行设置,这样就避免了死锁
(2)ReentrantLock 可以获取各种锁的信息
(3)ReentrantLock 可以灵活地实现多路通知
Lock 接口比同步方法和同步块提供了更具扩展性的锁操作。他们允许更灵活的结构,可以具有完
全不同的性质,并且可以支持多个相关类的条件对象。
它的优势有:
1)可以使锁更公平
2)可以使线程在等待锁的时候响应中断
3)可以让线程尝试获取锁,并在无法获取锁的时候立即返回或者等待一段时间
4)可以在不同的范围,以不同的顺序获取和释放锁
整体上来说 Lock 是 synchronized 的扩展版,Lock 提供了无条件的、可轮询的(tryLock 方法)、定
时的(tryLock 带参方法)、可中断的(lockInterruptibly)、可多条件队列的(newCondition 方法)锁操
作。另外 Lock 的实现类基本都支持非公平锁(默认)和公平锁,synchronized 只支持非公平锁,当
然,在大部分情况下,非公平锁是高效的选择。

Synchronized

synchronized 是 Java 中一个非常重要的关键字,用于控制对共享资源的并发访问,以防止多个线程同时修改数据而导致的数据不一致。当一个方法或代码块被声明为 synchronized 时,同一时间只有一个线程可以执行该方法或代码块。
以下是 synchronized 的几种用法:

  1. 同步方法:
    在方法声明中使用 synchronized 关键字,这样整个方法就变为同步的。
public synchronized void myMethod() {  
    // 方法体  
}

当一个线程正在执行一个对象的同步方法时,其他任何线程都无法执行该对象的任何其他同步方法。但不同对象之间的同步方法是互不影响的。
2. 同步代码块:
可以在方法中的特定代码块上使用 synchronized 关键字,而不是整个方法。这样可以更细粒度地控制并发访问。

public void myMethod() {  
    synchronized(this) {  
        // 同步代码块  
    }  
}

在上面的例子中,this 作为同步对象。你也可以使用类的任何对象或类的 Class 对象作为同步对象。
3. 静态同步方法:
如果 synchronized 关键字用于静态方法,那么该方法将属于类而不是实例,并且锁定的将是该类的 Class 对象。

public static synchronized void myStaticMethod() {  
    // 方法体  
}

当一个线程正在执行某个类的静态同步方法时,其他任何线程都无法执行该类的任何其他静态同步方法。
4. 同步块的灵活使用:
除了 this 和类的 Class 对象,你还可以使用任何对象作为锁对象。这提供了更大的灵活性,因为你可以根据需要锁定不同的对象。

private final Object lock = new Object();  
 
public void myMethod() {  
    synchronized(lock) {  
        // 同步代码块  
    }  
}

使用自定义对象作为锁的好处是你可以控制哪些代码块需要相互排斥,以及哪些不需要。
注意:synchronized 关键字虽然简单易用,但它在高并发场景下可能会导致性能问题,因为线程在获取不到锁时会被阻塞,导致上下文切换等开销。因此,在设计并发系统时,应仔细考虑是否真的需要使用 synchronized,以及是否有更高效的并发控制手段,如 ReentrantLock、Semaphore、CountDownLatch 等。

synchronized 底层实现原理

synchronized原理
在绝大多数情况下,都只会有一个线程去访问synchronized修饰的代码块,所以synchronized在jdk1.6之后为了提升效率,优化了synchronized的机制,就是所谓的锁升级。通过对象头及ObjectMonitor对象将锁划分了几个类型,其升级顺序为:无锁->偏向锁->轻量级锁->重量级锁,要了解它的原理,则必须要了解对象头。

Synchronized的语义底层是通过一个monitor(监视器锁)的对象来完成,
每个对象有一个监视器锁(monitor)。每个Synchronized修饰过的代码当它的monitor被占用时就
会处于锁定状态并且尝试获取monitor的所有权 ,过程:
1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为 monitor的所有者。
2、如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.
3、如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再 重新尝试获取monitor的所有权。
synchronized是可以通过 反汇编指令 javap命令,查看相应的字节码文件
synchronized可重入的原理
重入锁是指一个线程获取到该锁之后,该线程可以继续获得该锁。底层原理维护一个计数器,当线 程获取该锁时,计数器加一,再次获得该锁时继续加一,释放锁时,计数器减一,当计数器值为0 时,表明该锁未被任何线程所持有,其它线程可以竞争获取锁

synchronized 是 Java 中最基本的同步机制之一,它用于保证多线程并发访问共享资源时的线程安全。
在 Java 中,每一个对象都有一个关联的监视器锁(monitor lock),也称为内部锁(intrinsic lock)或对象锁(object lock)。当一个线程进入一个被 synchronized 修饰的方法或代码块时,它会尝试获取该对象的监视器锁,如果该锁没有被其他线程占用,那么该线程将持有该锁并执行 synchronized 代码块中的操作,同时其他需要该锁的线程将进入阻塞状态。当线程离开 synchronized 代码块时,它会释放持有的锁,此时其他线程可以继续竞争该锁。synchronized 的实现基于底层的 JVM 指令,如 monitorenter 和 monitorexit 指令,用来获取和释放对象的监视器锁。在 Java 5 以后,Java 引入了基于锁的并发库(Lock-based concurrency),包括 ReentrantLock 和 ReadWriteLock 等类,提供了比 synchronized 更灵活的锁定机制,并支持更多的并发控制选项。但是在实际应用中,由于 synchronized 代码块简单易用,而且在大多数情况下已经能够满足需求,因此它仍然是 Java 中最常用的同步机制。

sychronized 的自旋锁、偏向锁、轻量级锁、重量级锁

锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁。
Synchronized在jdk1.6版本之前,是通过重量级锁的方式来实现线程之间锁的竞争。
之所以称它为重量级锁,是因为它的底层底层依赖操作系统的Mutex Lock来实现互斥功能。
Mutex是系统方法,由于权限隔离的关系,应用程序调用系统方法时需要切换到内核态来执行。
这里涉及到用户态向内核态的切换,这个切换会带来性能的损耗。
在jdk1.6版本中,synchronized增加了锁升级的机制,来平衡数据安全性和性能。简单来说,就是
线程去访问synchronized同步代码块的时候,synchronized根据
线程竞争情况,会先尝试在不加重量级锁的情况下去保证线程安全性。所以引入了偏向锁和轻量级锁
的机制。
偏向锁,就是直接把当前锁偏向于某个线程,简单来说就是通过CAS修改偏向锁标记,这种锁适合同
一个线程多次去申请同一个锁资源并且没有其他线程竞争的场景。轻量级锁也可以称为自旋锁,基于自适应自旋的机制,通过多次自旋重试去竞争锁。自旋锁优点在于它避免避免了用户态到内核态的切换带来的性能开销。

Synchronized引入了锁升级的机制之后,如果有线程去竞争锁:
首先,synchronized会尝试使用偏向锁的方式去竞争锁资源,如果能够竞争到偏向锁,表示加锁成
功直接返回。如果竞争锁失败,说明当前锁已经偏向了其他线程。
需要将锁升级到轻量级锁,在轻量级锁状态下,竞争锁的线程根据自适应自旋次数去尝试抢占锁资源,
如果在轻量级锁状态下还是没有竞争到锁,
就只能升级到重量级锁,在重量级锁状态下,没有竞争到锁的线程就会被阻塞,线程状态是Blocked。
处于锁等待状态的线程需要等待获得锁的线程来触发唤醒。

总的来说,Synchronized的锁升级的设计思想,在我看来本质上是一种性能和安全性的平衡,也就
是如何在不加锁的情况下能够保证线程安全性。
这种思想在编程领域比较常见,比如Mysql里面的MVCC使用版本链的方式来解决多个并行事务的竞
争问题。

重量级锁
重量级锁:我们知道,我们要进入一个同步、线程安全的方法时,是需要先获得这个方法的锁的,退出这个方法时,则会释放锁。如果获取不到这个锁的话,意味着有别的线程在执行这个方法,这时我们就会马上进入阻塞的状态,等待那个持有锁的线程释放锁,然后再把我们从阻塞的状态唤醒,我们再去获取这个方法的锁。这种获取不到锁就马上进入阻塞状态的锁,我们称之为重量级锁。
Synchronized 是通过对象内部的一个叫做监视器锁(monitor)来实现的。但是监视器锁本质又是依赖于底层的操作系统的 Mutex Lock 来实现的。而操作系统实现线程之间的切换这就需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized 效率低的原因。因此,这种依赖于操作系统 Mutex Lock 所实现的锁我们称之为重量级锁”。JDK 中对 Synchronized 做的种种优化,其核心都是为了减少这种重量级锁的使用。JDK1.6 以后,为了减少获得锁和释放锁所带来的性能消耗,提高性能,引入了“轻量级锁”和“偏向锁”

轻量级锁
轻量级锁:轻量级锁是由偏向所升级来的,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁;
锁升级随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁(但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级)。“轻量级”是相对于使用操作系统互斥量来实现的传统锁而言的。但是,首先需要强调一点的是,轻量级锁并不是用来代替重量级锁的,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用产生的性能消耗。在解释轻量级锁的执行过程之前,先明白一点,轻量级锁所适应的场景是线程交替执行同步块的情况,如果存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁。

偏向锁
偏向锁:顾名思义,它会偏向于第一个访问锁的线程,如果在运行过程中,同步锁只有一个线程访问,不存在多线程争用的情况,则线程是不需要触发同步的,减少加锁/解锁的一些 CAS 操作(比如等待队列的一些 CAS 操作),这种情况下,就会给线程加一个偏向锁。 如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM 会消除它身上的偏向锁,将锁恢复到标准的轻量级锁。
Hotspot 的作者经过以往的研究发现大多数情况下锁不仅不存在多线程竞争,而且总是由同一线程多次获得。偏向锁的目的是在某个线程获得锁之后,消除这个线程锁重入(CAS)的开销,看起来让这个线程得到了偏护。引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次 CAS 原子指令,而偏向锁只需要在置换ThreadID 的时候依赖一次 CAS 原子指令(由于一旦出现多线程竞争的情况就必须撤销偏向锁,所以偏向锁的撤销操作的性能损耗必须小于节省下来的 CAS 原子指令的性能消耗)。上面说过,轻量级锁是为了在线程交替执行同步块时提高性能,而偏向锁则是在只有一个线程执行同步块时进
一步提高性能
自旋锁:
果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。

synchronized 锁升级

在java中,经常会用到synchronized关键字来保证线程安全,那么什么时候会存在线程安全呢?
共享数据的修改
临界资源访问
无锁->偏向锁->轻量级锁->重量级锁
synchronized 锁升级原理:在锁对象的对象头里面有一个 threadid 字段,在第一次访问的时候 threadid 为空,jvm 让其持有偏向锁,并将 threadid 设置为其线程 id,再次进入的时候会先判断 threadid 是否与其线程 id 一致,如果一致则可以直接使用此对象,如果不一致,则升级偏向锁为 轻量级锁,通过自旋循环一定次数来获取锁,执行一定次数之后,如果还没有正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁,此过程就构成了 synchronized 锁的升级。
锁的升级的目的:锁升级是为了减低了锁带来的性能消耗。在 Java 6 之后优化 synchronized 的实现方
式,使用了偏向锁升级为轻量级锁再升级到重量级锁的方式,从而减低了锁带来的性能消耗
偏向锁,顾名思义,它会偏向于第一个访问锁的线程,如果在运行过程中,同步锁只有一个线程访问,不存在多线程争用的情况,则线程是不需要触发同步的,减少加锁/解锁的一些CAS操作(比如等待队列的一些CAS操作),这种情况下,就会给线程加一个偏向锁。 如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会消除它身上的偏向锁,将锁恢复到标准的轻量级锁。
轻量级锁是由偏向所升级来的,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,轻量级锁就会升级为重量级锁;
重量级锁是synchronized ,是 Java 虚拟机中最为基础的锁实现。在这种状态下,Java 虚拟机会阻 塞加锁失败的线程,并且在目标锁被释放的时候,唤醒这些线程

应用场景
修饰普通同步方法:锁当前实例对象;
修饰静态同步方法:锁当前的类Class对象;
修饰同步代码块:锁Synchronized后面括号里配置的对象,这个对象可以是任意对象;

对象头
java对象保存在内存中,由3个部分组成:
● 对象头
● 实例数据
● 对齐填充字节
这里,我们只对对象头加以说明
对象头的存在形式
JVM中的对象头有两种形式,它由三部分组成:
● Mark Word
● Klass Pointer(指向类的指针)
● 数组长度(只有数组对象才有)

锁升级的过程
JVM一般是这样使用锁和Mark Word的:
1、当没有被当做锁的时候,这就是个普通对象,锁标志位为01,是否偏向锁为0
2、当对象被当做同步锁时,一个线程A抢到锁时,锁标志位依然是01,是否偏向锁为1,前23位记录A线程的线程ID,此时锁升级为偏向锁
3、当线程A再次试图来获得锁时,JVM发现同步锁对象的标志位是01,是否偏向锁是1,也就是偏向状态,Mark Word中记录的线程id就是线程A自己的id,表示线程A已经获得了这个偏向锁,可以执行同步锁的代码,这也是偏向锁的意义
4、当一个线程B尝试获取锁,JVM发现当前的锁处于偏向状态,并且现场ID不是B线程的ID,那么线程B会先用CAS将线程id改为自己的,这里是有可能成功的,因为A线程一般不会释放偏向锁。如果失败,则执行5
5、偏向锁抢锁失败,则说明当前锁存在一定的竞争,偏向锁就升级为轻量级锁。JVM会在当前线程的现场栈中开辟一块单独的空间,里面保存指向对象锁Mark Word的指针,同时在对象锁MarkWord中保存指向这片空间的指针。上面的保存都是CAS操作,如果竞争成功,代表线程B抢到了锁,可以执行同步代码。如果抢锁失败,则继续执行6
6、轻量级锁抢锁失败,则JVM会使用自旋锁,自旋锁并非是一个锁,则是一个循环操作,不断的尝试获取锁。从JDK1.7开始,自旋锁默认开启,自旋次数由JVM决定。如果抢锁成功,则执行同步代码;如果抢锁失败,则执行7
7、自旋锁重试之后仍然未抢到锁,同步锁会升级至重量级锁,锁标志位改为10,在这个状态下,未抢到锁的线程都会被阻塞,由Monitor来管理,并会有线程的park与unpark,因为这个存在用户态和内核态的转换,比较消耗资源,故名重量级锁
锁升级原因
锁升级是为了减低了synchronized(初始设计就是重量级锁)带来的性能消耗。没有优化以前,synchronized是重量级锁-悲观锁
使用 wait 和 notify、notifyAll 来切换线程状态非常消耗系统资源。
线程运行到synchronized代码块时
程序的运行级别从用户态切换到内核态,
把所有的线程挂起
cpu通过操作系统指令,去调度多线程之间,谁执行代码块,谁进入阻塞状态。
这样会频繁出现程序运行状态的切换,线程的挂起和唤醒,这样就会大量消耗资源,程序运行的效率低下。
为了提高效率
将锁分为 无锁、偏向锁、轻量级锁、重量级锁 状态
尽量避免多线程访问公共资源的时候,进行运行状态的切换(用户态切换到内核态)所造成的的性能损耗。
在Java中,synchronized关键字的升级过程包括多个阶段,其中会涉及到自旋操作。具体自旋的次数取决于JVM的实现和运行环境的配置。下面是一般情况下synchronized升级过程中可能涉及到的自旋次数:

  1. 初始自旋:线程在获取锁时,如果发现锁已经被其他线程占用,会进行一定次数的自旋尝试获取锁,以避免线程切换的开销。
  2. 自适应自旋:在初始自旋之后,如果发现在同一个锁上频繁进行自旋但都无法获得锁,JVM会根据历史信息动态调整自旋次数,也就是自适应自旋。
  3. 锁膨胀:如果自旋一定次数后仍然无法获取到锁,那么线程将进入重量级锁状态,此时会挂起线程并释放CPU资源,等待被唤醒。
    需要注意的是,以上的自旋次数仅是一般情况下的参考值,具体的实现可能因为JVM版本、运行环境以及锁竞争的情况而有所不同。在高并发场景下,自旋次数可能会增加或减少,以尽量减少线程切换的开销和提高锁竞争的效率。
    另外,为了进一步提高并发性能,在Java 9及更高版本中引入了偏向锁、轻量级锁和重量级锁的概念,这些锁的升级过程也可能涉及到不同阶段的自旋。

ReentrantLock

Lock 接口比同步方法和同步块提供了更具扩展性的锁操作。 他们允许更灵活的结构,可以具有完全不同的性质,并且可以支持多个相关类 的条件对象。
它的优势有: 可以使锁更公平 可以使线程在等待锁的时候响应中断 可以让线程尝试获取锁,并在无法获取锁的时候立即返回或者等待一段时间 可以在不同的范围,以不同的顺序获取和释放锁
整体上来说 Lock 是 synchronized 的扩展版,Lock 提供了无条件的、可轮询 的(tryLock 方法)、定时的(tryLock 带参方法)、可中断的 (lockInterruptibly)、可多条件队列的(newCondition 方法)锁操作。另外 Lock 的实现类基本都支持非公平锁(默认)和公平锁,synchronized 只支持非 公平锁,当然,在大部分情况下,非公平锁是高效的选择。

ReentrantLock
ReentrantLock 是一种可重入的排它锁,主要用来解决多线程对共享资源竞争的问题。
ReentantLock 继承接口 Lock 并实现了接口中定义的方法,他是一种可重入锁,除了能完成 synchronized 所能完成的所有工作外,还提供了诸如可响应中断锁、可轮询锁请求、定时锁等避免多线程死锁的方法。
核心的特性
第1,它支持可重入,也就是获得锁的线程在释放锁之前再次去竞争同一把锁的时候,不需要加锁就可以直接访问
第2,它支持公平和非公平特性
第3,它提供了阻塞竞争锁和非阻塞竞争锁的两种方法,分别是 lock()和 tryLock()。
ReentrantLock 的实现原理
第1个,锁的竞争,ReentrantLock 是通过互斥变量,使用 CAS 机制来实现的。没有竞争到锁的线程,使用了 AbstractQueuedSynchronizer 这样一个队列同步器来存储,底层是通过双向链表来实现的。当锁被释放之后,会从 AQS 队列里面的头部唤醒下一个等待锁的线程。
第2个,公平和非公平的特性,主要是体现在竞争锁的时候,是否需要判断 AQS 队列存在等待中的线程。
第3个,锁的重入特性,在 AQS 里面有一个成员变量来保存当前获得锁的线程,当同一个线程下次再来竞争锁的时候,就不会去走锁竞争的逻辑,而是直接增加重入次数。
源码解读
reentantLock是基于aqs实现的一个可重入锁。它的实现主要有2种不同方式,一种是公平锁、一种
是非公平锁。他通过aqs中的state字段来维护是否获得锁或者重入了几次,并且这个字段需要用volatile来修饰,
保证线程之间的可见性。
并且去抢占这个标记必须是安全的,所以会通过CAS来保证抢占过程的安全性。
如果是不同的线程,通过cas判断是否能拿到锁,如果是同一个线程重复抢占锁,那么state+1,这样就实现了可重入。如果拿到锁的线程,去执行相关业务代码,如果拿不到锁,会加入一个等待队列,是一个双向链表,
这个队列也属于aqs维护,加入队列后park,这个时候线程就等待拿到锁的线程释放锁去唤醒。
当拿锁的线程unlock后,会去唤醒等待队列的第一个等待的线程。自旋去获取抢占锁。

Lock 接口的主要方法

  1. void lock(): 执行此方法时, 如果锁处于空闲状态, 当前线程将获取到锁. 相反, 如果锁已经
    被其他线程持有, 将禁用当前线程, 直到当前线程获取到锁.
  2. boolean tryLock():如果锁可用, 则获取锁, 并立即返回 true, 否则返回 false. 该方法和
    lock()的区别在于, tryLock()只是"试图"获取锁, 如果锁不可用, 不会导致当前线程被禁用,
    当前线程仍然继续往下执行代码. 而 lock()方法则是一定要获取到锁, 如果锁不可用, 就一
    直等待, 在未获得锁之前,当前线程并不继续向下执行.
  3. void unlock():执行此方法时, 当前线程将释放持有的锁. 锁只能由持有者释放, 如果线程
    并不持有锁, 却执行该方法, 可能导致异常的发生.
  4. Condition newCondition():条件对象,获取等待通知组件。该组件和当前的锁绑定,
    当前线程只有获取了锁,才能调用该组件的 await()方法,而调用后,当前线程将缩放锁。
  5. getHoldCount() :查询当前线程保持此锁的次数,也就是执行此线程执行 lock 方法的次
    数。
  6. getQueueLength():返回正等待获取此锁的线程估计数,比如启动 10 个线程,1 个
    线程获得锁,此时返回的是 9
  7. getWaitQueueLength:(Condition condition)返回等待与此锁相关的给定条件的线
    程估计数。比如 10 个线程,用同一个 condition 对象,并且此时这 10 个线程都执行了
    condition 对象的 await 方法,那么此时执行此方法返回 10
  8. hasWaiters(Condition condition):查询是否有线程等待与此锁有关的给定条件
    (condition),对于指定 contidion 对象,有多少线程执行了 condition.await 方法
  9. hasQueuedThread(Thread thread):查询给定线程是否等待获取此锁
  10. hasQueuedThreads():是否有线程等待此锁
  11. isFair():该锁是否公平锁
  12. isHeldByCurrentThread(): 当前线程是否保持锁锁定,线程的执行 lock 方法的前后分
    别是 false 和 true
  13. isLock():此锁是否有任意线程占用
  14. lockInterruptibly():如果当前线程未被中断,获取锁
  15. tryLock():尝试获得锁,仅在调用时锁未被线程占用,获得锁
  16. tryLock(long timeout TimeUnit unit):如果锁在给定等待时间内没有被另一个线程保持,
    则获取该锁。

非公平锁
JVM 按随机、就近原则分配锁的机制则称为不公平锁,ReentrantLock 在构造函数中提供了是否公平锁的初始化方式,默认为非公平锁。非公平锁实际执行的效率要远远超出公平锁,除非程序有特殊需要,否则最常用非公平锁的分配机制。
公平锁
公平锁指的是锁的分配机制是公平的,通常先对锁提出获取请求的线程会先被分配到锁,ReentrantLock 在构造函数中提供了是否公平锁的初始化方式来定义公平锁。

public class MyService {
    private Lock lock = new ReentrantLock();
    //Lock lock=new ReentrantLock(true);//公平锁
    //Lock lock=new ReentrantLock(false);//非公平锁
    private Condition condition=lock.newCondition();//创建 Condition
    public void testMethod() {
        try {
            lock.lock();//lock 加锁
            //1:wait 方法等待:
            //System.out.println("开始 wait");
            condition.await();
            //通过创建 Condition 对象来使线程 wait,必须先执行 lock.lock 方法获得锁
            //:2:signal 方法唤醒
            condition.signal();//condition 对象的 signal 方法可以唤醒 wait 线程
            for (int i = 0; i < 5; i++) {
                System.out.println("ThreadName=" + Thread.currentThread().getName()+ (" " + (i + 1)));
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        finally
            {
                lock.unlock();
            }
    }}

Condition 类和 Object 类锁方法区别区别

  1. Condition 类的 awiat 方法和 Object 类的 wait 方法等效
  2. Condition 类的 signal 方法和 Object 类的 notify 方法等效
  3. Condition 类的 signalAll 方法和 Object 类的 notifyAll 方法等效
  4. ReentrantLock 类可以唤醒指定条件的线程,而 object 的唤醒是随机的

tryLock 和 lock 和 lockInterruptibly 的区别

  1. tryLock 能获得锁就返回 true,不能就立即返回 false,tryLock(long timeout,TimeUnit
    unit),可以增加时间限制,如果超过该时间段还没获得锁,返回 false
  2. lock 能获得锁就返回 true,不能的话一直等待获得锁
  3. lock 和 lockInterruptibly,如果两个线程分别执行这两个方法,但此时中断这两个线程,
    lock 不会抛出异常,而 lockInterruptibly 会抛出异常。

ReadWriteLock 读写锁

读写锁是用来提升并发程序性能的锁分离技术的成果
首先明确一下,不是说 ReentrantLock 不好,只是 ReentrantLock 某些时候有局限。如果使用ReentrantLock,可能本身是为了防止线程 A 在写数据、线程 B 在读数据造成的数据不一致,但这样,如果线程 C 在读数据、线程 D 也在读数据,读数据是不会改变数据的,没有必要加锁,但是还是加锁了,降低了程序的性能。因为这个,才诞生了读写锁ReadWriteLock 。 ReadWriteLock 是 一 个 读 写 锁 接 口 , ReentrantReadWriteLock 是
ReadWriteLock 接口的一个具体实现,实现了读写的分离,读锁是共享的,写锁是独占的,读和读之间不会互斥,读和写、写和读、写和写之间才会互斥,提升了读写的性能。
为了提高性能,Java 提供了读写锁,在读的地方使用读锁,在写的地方使用写锁,灵活控制,如果没有写锁的情况下,读是无阻塞的,在一定程度上提高了程序的执行效率。读写锁分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,这是由 jvm 自己控制的,你只要上好相应的锁即可。读锁 如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁 写锁如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁。总之,读的时候上读锁,写的时候上写锁
Java 中 读 写 锁 有 个 接 口 java.util.concurrent.locks.ReadWriteLock , 也 有 具 体 的 实 现
ReentrantReadWriteLock

  • 24
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: synchronizedreentrantlock都是Java中用于实现线程同步的机制。 synchronized是Java中最基本的同步机制,它可以用于方法或代码块级别的同步。当一个线程进入synchronized代码块时,它会尝试获取锁,如果锁已经被其他线程持有,则该线程会被阻塞,直到锁被释放。synchronized机制是Java中最常用的同步机制之一,但它有一些限制,例如无法中断正在等待锁的线程,也无法尝试获取锁而不阻塞。 reentrantlock是Java中另一种同步机制,它提供了更多的灵活性和控制。与synchronized不同,reentrantlock可以中断正在等待锁的线程,也可以尝试获取锁而不阻塞。此外,reentrantlock还提供了一些高级功能,例如公平锁和可重入锁。但是,reentrantlock的使用比synchronized更复杂,需要手动获取和释放锁,并且需要注意避免死锁等问题。 总的来说,synchronized是Java中最基本的同步机制,适用于大多数情况。而reentrantlock则提供了更多的灵活性和控制,适用于一些特殊的场景。 ### 回答2: synchronizedReentrantLock是Java中用于实现线程同步的两种机制。 synchronized是Java中最基本的线程同步机制,它使用了内置的监视器锁(也称为对象锁或互斥锁)来控制线程的访问。synchronized关键字可以用于方法或代码块,并且是隐式锁。当线程执行到synchronized关键字时,会自动获取锁,执行完后会自动释放锁。synchronized关键字确保了在同一时间只有一个线程可以访问被锁定的代码或方法,从而保证了线程的安全性。然而,synchronized关键字也有一些缺点,比如无法获得锁时会一直阻塞等待和释放锁。 ReentrantLock是Java中的另一种线程同步机制,它是通过显示锁来实现的。ReentrantLock提供了更灵活的锁定操作,相比于synchronized,它具有更多的特性。ReentrantLock可以使用lock()和unlock()方法来手动加锁和解锁,这意味着我们可以更加精确地控制代码的同步性。ReentrantLock还支持可重入性,即线程可以多次获得同一个锁,提供了更高的灵活性。它还支持公平锁和非公平锁两种模式,可以按照先进先出的规则来选择获取锁的线程。此外,ReentrantLock还提供了一些高级功能,比如可中断锁、超时锁和条件变量等。 总的来说,synchronized是Java中最基本的线程同步机制,使用简单但灵活性较低。而ReentrantLock则提供了更多的功能和灵活性,但使用相对复杂一些。在实际应用中,我们可以根据具体需求来选择使用哪种线程同步机制。 ### 回答3: synchronizedReentrantLock都是Java中用于实现线程同步的工具。 synchronized是Java中最常见的同步关键字,它可以应用在方法或代码块上。当一个线程获取到对象的synchronized锁时,其他线程就无法同时访问这个对象。当该线程执行完代码块或者方法,会释放锁,其他线程才能获得锁并执行。synchronized是隐式获取和释放锁的过程,非常方便,但也存在一些限制,例如无法中断一个正在等待获取锁的线程。 ReentrantLock是Java中提供的一种显示锁,可以实现与synchronized类似的功能。与synchronized不同的是,ReentrantLock提供了更灵活和更强大的功能。它允许更细粒度的控制锁定的获取和释放,例如通过lock()和unlock()方法手动获取和释放锁,也可以通过tryLock()方法尝试获取锁而不需要一直等待。此外,ReentrantLock还支持公平性,即按照线程请求的顺序获取锁,避免产生线程饥饿现象。而synchronized则不保证公平性。 另外,ReentrantLock提供了Condition接口,可以使用newCondition()方法创建多个条件变量。条件变量可以用于线程间的通信,可以让某个线程等待特定条件满足后再继续执行,或者通知其他等待线程某个条件已经满足。 总的来说,synchronized是Java中隐式锁机制的实现,使用方便但功能有限;而ReentrantLock是显示锁机制的实现,提供了更多灵活和强大的功能,但使用起来需要更细致的控制。根据具体情况需要,我们可以选择合适的同步机制来保证线程安全。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

思静语

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值