Java基础-锁Lock

简介: ReentrantLock构造方法(不带参数 和带参数 true: 公平锁; false: 非公平锁)

基本实现

Lock与ReadWriteLock是两大锁根接口
(1)Lock :
Lock 接口支持那些语义不同(重入、公平等)的锁规则;
可以在非阻塞式结构的上下文(包括 hand-over-hand 和锁重排算法)中使用这些规则。
主要实现类是ReentrantLock(可重入锁)
(2)ReadWriteLock(读写锁):
以Lock类似方式定义了一些读取者可以共享而写入者独占的锁。
主要实现类是ReentrantReadWriteLock
(3)Condition:
描述了可能会与锁有关联的条件变量。
单个 Lock 可能与多个 Condition 对象关联

关键字:synchronized

synchronized是java中的一个关键字,也就是说是Java语言内置的特性。
Lock不是Java语言内置的,Lock是一个类,通过这个类可以实现同步访问。
Lock和synchronized有一点非常大的不同:
synchronized不需要用户去手动释放锁,系统会自动让线程释放对锁的占用。
而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

局限性
如果一个代码块被synchronized关键字修饰;
当一个线程获取了对应的锁,并执行该代码块时;
其他线程便只能一直等待直至占有锁的线程释放锁。

在使用synchronized关键字的情形下,可能因为某些原因,无期限阻塞下去。
当多个线程读写文件时,读操作和读操作不会发生冲突。采用synchronized关键字实现同步的话,多线程进行读操作,也只能一个进行读操作,其他线程只能等待。

Lock 实现提供了比 synchronized 关键字 更广泛的锁操作,它能以更优雅的方式处理线程同步问题。

接口:Lock

(1)接口介绍
Lock接口有6个方法:

// 获取锁  
void lock() ;  

// 如果当前线程未被中断,则获取锁,可以响应中断  
void lockInterruptibly() ; 

// 返回绑定到此 Lock 实例的新 Condition 实例  
Condition newCondition()  ;  

// 仅在调用时锁为空闲状态才获取该锁,可以响应中断  
boolean tryLock() ;

// 如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁  
boolean tryLock(long time, TimeUnit unit) ;  

// 释放锁  
void unlock();

(2)方法:lock()
lock()方法是平常使用得最多的一个方法,就是用来获取锁。
如果锁已被其他线程获取,则进行等待。
如果采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。
一般来说,使用Lock必须在try…catch…块中进行;
并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。
例如:

lock.lock();
try {
    inc++; 
} finally {
    lock.unlock();
}

(3)方法:tryLock() & tryLock(long time, TimeUnit unit)
tryLock()方法是有返回值的,它表示用来尝试获取锁;
如果获取成功,则返回true;
如果获取失败(即锁已被其他线程获取),则返回false。
这个方法无论如何都会立即返回(在拿不到锁时不会一直在那等待)。

tryLock(long time, TimeUnit unit) 类似
在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false,同时可以响应中断。
如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
例如:

if(lock.tryLock()) {
    try{
       //处理任务
    }catch(Exception ex){ }
    finally{
       lock.unlock();   //释放锁
    }
}else {
    //如果不能获取锁,则直接做其他事情
}

(4)方法:lockInterruptibly()
当通过这个方法去获取锁时,如果线程 正在等待获取锁,则这个线程能够 响应中断,即中断线程的等待状态。
lock.lockInterruptibly()必须放在try块中;
或者在调用lockInterruptibly()的方法外声明抛出 InterruptedException

当一个线程获取了锁之后,是不会被interrupt()方法中断的。
因为interrupt()方法只能中断阻塞过程中的线程而不能中断正在运行过程中的线程。
当通过lockInterruptibly()方法获取某个锁时,如果不能获取到,那么只有进行等待的情况下,才可以响应中断的。
与 synchronized 相比,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去。

实现类:ReentrantLock

ReentrantLock,即 可重入锁。
ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法。
ReentrantLock构造方法(不带参数 和带参数 true: 公平锁; false: 非公平锁)
注意:
(1)synchronized是Java语言的关键字,因此是内置特性;Lock不是Java语言内置的,Lock是一个接口,通过实现类可以实现同步访问。
(2)synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定;但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中。
(3)在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock;但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态。

如下: 公平锁会按顺序输出,非公平锁不一定按顺序输出

static Lock lock = new ReentrantLock(true);
static void lock(String name) {
    lock.lock();
    try {
        System.out.println(name + " get the lock");
    } finally {
        System.out.println(name + " release the lock");
        lock.unlock();
    }
}

public static void main(String[] args) {
    new Thread(() -> lock("0")).start();
    new Thread(() -> lock("1")).start();
    new Thread(() -> lock("2")).start();
    new Thread(() -> lock("3")).start();
    new Thread(() -> lock("4")).start();
    new Thread(() -> lock("5")).start();
    new Thread(() -> lock("6")).start();
    new Thread(() -> lock("7")).start();
    new Thread(() -> lock("8")).start();
    new Thread(() -> lock("9")).start();
}

非公平锁里
判断当前锁占用状态==0直接会进行compareAndSetState尝试获取锁
若此时有线程排队,可能争夺不过资源。
非公平锁里,不需要加入队列、等待队列头线程唤醒再获取锁这一步骤,所以效率较快。

公平锁里
判断当前锁占用状态==0后
会继续判断hasQueuedPredecessors,即当前队列是否有排队的情况,如果没有才会尝试获取锁
这样可以保证遵循FIFO的原则,每一个先来的线程都可以最先获取到锁。
但是增加了上下文切换与等待线程的状态变换时间。所以效率相较于非公平锁较慢。

相关文章
|
2月前
|
安全 Java 调度
Java编程时多线程操作单核服务器可以不加锁吗?
Java编程时多线程操作单核服务器可以不加锁吗?
43 2
|
1天前
|
缓存 Java
java中的公平锁、非公平锁、可重入锁、递归锁、自旋锁、独占锁和共享锁
本文介绍了几种常见的锁机制,包括公平锁与非公平锁、可重入锁与不可重入锁、自旋锁以及读写锁和互斥锁。公平锁按申请顺序分配锁,而非公平锁允许插队。可重入锁允许线程多次获取同一锁,避免死锁。自旋锁通过循环尝试获取锁,减少上下文切换开销。读写锁区分读锁和写锁,提高并发性能。文章还提供了相关代码示例,帮助理解这些锁的实现和使用场景。
java中的公平锁、非公平锁、可重入锁、递归锁、自旋锁、独占锁和共享锁
|
4天前
|
Java 开发者
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
19 4
|
26天前
|
存储 缓存 Java
java基础:IO流 理论与代码示例(详解、idea设置统一utf-8编码问题)
这篇文章详细介绍了Java中的IO流,包括字符与字节的概念、编码格式、File类的使用、IO流的分类和原理,以及通过代码示例展示了各种流的应用,如节点流、处理流、缓存流、转换流、对象流和随机访问文件流。同时,还探讨了IDEA中设置项目编码格式的方法,以及如何处理序列化和反序列化问题。
58 1
java基础:IO流 理论与代码示例(详解、idea设置统一utf-8编码问题)
|
23天前
|
Java
Java 中锁的主要类型
【10月更文挑战第10天】
|
28天前
|
Java 开发者
在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选
【10月更文挑战第6天】在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选。相比 `synchronized`,Lock 提供了更灵活强大的线程同步机制,包括可中断等待、超时等待、重入锁及读写锁等高级特性,极大提升了多线程应用的性能和可靠性。通过示例对比,可以看出 Lock 接口通过 `lock()` 和 `unlock()` 明确管理锁的获取和释放,避免死锁风险,并支持公平锁选择和条件变量,使其在高并发场景下更具优势。掌握 Lock 接口将助力开发者构建更高效、可靠的多线程应用。
23 2
|
2月前
|
算法 Java 关系型数据库
Java中到底有哪些锁
【9月更文挑战第24天】在Java中,锁主要分为乐观锁与悲观锁、自旋锁与自适应自旋锁、公平锁与非公平锁、可重入锁以及独享锁与共享锁。乐观锁适用于读多写少场景,通过版本号或CAS算法实现;悲观锁适用于写多读少场景,通过加锁保证数据一致性。自旋锁与自适应自旋锁通过循环等待减少线程挂起和恢复的开销,适用于锁持有时间短的场景。公平锁按请求顺序获取锁,适合等待敏感场景;非公平锁性能更高,适合频繁加解锁场景。可重入锁支持同一线程多次获取,避免死锁;独享锁与共享锁分别用于独占和并发读场景。
|
24天前
|
安全 Java 开发者
java的synchronized有几种加锁方式
Java的 `synchronized`通过上述三种加锁方式,为开发者提供了从粗粒度到细粒度的并发控制能力,满足了不同场景下的线程安全需求。合理选择加锁方式对于提升程序的并发性能和正确性至关重要,开发者应根据实际应用场景的特性和性能要求来决定使用哪种加锁策略。
13 0
|
24天前
|
Java 应用服务中间件 测试技术
Java21虚拟线程:我的锁去哪儿了?
【10月更文挑战第8天】
30 0
|
2月前
|
Java
领略Lock接口的风采,通过实战演练,让你迅速掌握这门高深武艺,成为Java多线程领域的武林盟主
领略Lock接口的风采,通过实战演练,让你迅速掌握这门高深武艺,成为Java多线程领域的武林盟主
34 7