首页 > 编程语言 >Java中的锁

Java中的锁

时间:2022-11-04 14:00:52浏览次数:63  
标签:Java int 获取 读锁 线程 return true


锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁能够防止多个线程同时 访问共享资源(但是有些锁可以允许多个线程并发的访问共享资源,比如读写锁)。

Lock和synchronized的主要区别:

  • synchronized它是隐式的获取和释放锁,使用起来更方便,但是不灵活。
  • Lock接口获取和释放锁是由我们自己控制,更加灵活
  • Lock接口支持超时获取锁和可中断获取锁

Lock的使用的方式

Lock lock = new ReentrantLock(); lock.lock(); try
{
// TODO
} finally {
lock.unlock();
}

如果在没有特殊需求的话建议尽量使用synchronized,不容易出错。

Lock的API

Java中的锁_ReentrantLock

公平锁和非公平锁。

如果在绝对时间上,先对锁进行获取的请求一定先被满足(FIFO),那么这个锁是公平锁,反之,是非公平锁。

公平的锁机制往往没有非公平的效率高,公平性锁保证了锁的获取按照FIFO原则,而代价是进行大量的线程切换。非公平性锁虽然可能造成线程“饥饿”,但极少的线程切换,保证了其更大的吞吐量。

ReentrantLock支持获取锁时的公平和非公平性选择;synchronized是非公平锁。

重入锁

能够支持一个线程对 资源的重复加锁 的锁叫做可重入锁。ReentrantLock和synchronized都是可重入锁。

实现重进入

重进入是指任意线程在获取到锁之后能够再次获取该锁而不会被锁所阻塞,该特性的实现需要解决以下两个问题。

  1. 线程再次获取锁。锁需要去识别获取锁的线程是否为当前占据锁的线程,如果是,则再 次成功获取。
  2. 锁的最终释放。线程重复n次获取了锁,随后在第n次释放该锁后,其他线程能够获取到该锁。锁的最终释放要求锁对于获取进行计数自增,计数表示当前锁被重复获取的次数,而锁被释放时,计数自减,当计数等于0时表示锁已经成功释放。

ReentrantLock

ReentrantLock是通过组合自定义同步器来实现锁的获取与释放。
非公平性获取锁:

final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}

该方法增加了再次获取同步状态的处理逻辑:通过判断当前线程是否为获取锁的线程来 决定获取操作是否成功,如果是获取锁的线程再次请求,则将同步状态值进行增加并返回 true,表示获取同步状态成功。

非公平性释放锁:

protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}

如果该锁被获取了n次,那么前(n-1)次tryRelease(int releases)方法必须返回false,而只有同 步状态完全释放了,才能返回true。可以看到,该方法将同步状态是否为0作为最终释放的条 件,当同步状态为0时,将占有线程设置为null,并返回true,表示释放成功。

公平性获取锁:

protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}

public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}

该方法与nonfairTryAcquire(int acquires)比较,唯一不同的位置为判断条件多了 hasQueuedPredecessors()方法,即加入了同步队列中当前节点是否有前驱节点的判断,如果该 方法返回true,则表示有线程比当前线程更早地请求获取锁,因此需要等待前驱线程获取并释 放锁之后才能继续获取锁。

Java中的锁_Java中的锁_02

读写锁

之前提到锁(如ReentrantLock)基本都是排他锁,这些锁在同一时刻只允许一个线程进行访问,而读写锁在同一时刻可以允许多个读线程访问,但是在写线程访问时,所有的读 线程和其他写线程均被阻塞。读写锁维护了一对锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性相比一般的排他锁有了很大提升。大多应用于读多写少的情况。

ReentrantReadWriteLock

  • 公平性选择
  • 重入:获取读锁后能再次获取读锁,获取写锁后能再次获取读锁和写锁
  • 锁降级:遵循先获取写锁然后获取读锁,再释放写锁,写锁就降级成了读锁了

读写状态的设计

读写锁将锁状态变量切分成了两个部分,高16位表示读,低16位表示写。

Java中的锁_Java中的锁_03


当前同步状态表示一个线程已经获取了写锁,且重进入了两次,同时也连续获取了两次读锁。读写锁是如何迅速确定读和写各自的状态呢?答案是通过位运算。假设当前同步状态 值为S,写状态等于S&0x0000FFFF(将高16位全部抹去),读状态等于S>>>16(无符号补0右移 16位)。当写状态增加1时,等于S+1,当读状态增加1时,等于S+(1<<16),也就是 S+0x00010000。

参考

《java并发编程的艺术》

源码

​https://github.com/wyh-spring-ecosystem-student/spring-boot-student/tree/releases​

spring-boot-student-concurrent 工程

layering-cache

为监控而生的多级缓存框架 layering-cache这是我开源的一个多级缓存框架的实现,如果有兴趣可以看一下


标签:Java,int,获取,读锁,线程,return,true
From: https://blog.51cto.com/u_15861563/5823727

相关文章

  • Elasticsearch 同时使用should和must 只有must生效,java代码解决方案
    ES中同时使用should和must导致只有must生效解决方案失效的原因就是must和should在一起使用会不生效,如果全部都是must是不影响的.加入一个字段需要有类似url=aor......
  • java 压缩图片
    FilesourceFile=newFile("F:\\1.png");FiletargetFile=newFile("F:\\2.png");Thumbnails.of(sourceFile).scale(0.3f).toFile(targetFile)......
  • JAVA CST时间 转换成Date
    格式化CST时间SimpleDateFormatsdf=newSimpleDateFormat("EEEMMMddHH:mm:sszzzyyyy",Locale.US);CST时间转换成字符串,实体中为date类型的toString()转换即......
  • Java线程状态详解
    Java的每个线程都具有自己的状态,Thread类中成员变量threadStatus存储了线程的状态: privatevolatileintthreadStatus=0; 在Thread类中也定义了状态的枚举,共六......
  • 【java技术总结】Java-9中List.of()和Arrays.asList()的区别及原因分析
    1.List.of()和Arrays.asList()的区别?List.of()不可以插入null,Arrays.asList()可以。List.of()生成的List不可以修改,Arrays.asList()可以。List.of()原数组修改不会影响......
  • 13. Java 面向对象编程
    Java面向对象编程Java的核心思想就是OOP1.初识面向对象面向过程&面向对象面向过程思想步骤清晰简单,第一步做什么,第二步做什么.....面对过程适合处理一些......
  • java如何实现原子操作CAS
    在Java中可以通过锁和循环CAS的方式来实现原子操作。使用循环CAS实现原子操作JVM中的CAS操作正是利用了处理器提供的CMPXCHG指令实现的。自旋CAS实现的基本思路就是循环进行......
  • java api 视频面试准备
    Java api知识点总结1.单例设计模式:通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问懒汉式:先对对象进行初始化恶汉式(延时加载用到的是恶汉式):先在堆......
  • 前端100题」包含算法、Vue、Reac、Javascript、浏览器等真题和答案
    目录(https://github.com/Advanced-Frontend/Daily-Interview-Question)第1题:写React/Vue项目时为什么要在列表组件中写key,其作用是什么?.6第2题:['1','2','3'......
  • 常用的前端JavaScript方法封装
    1、输入一个值,返回其数据类型**functiontype(para){returnObject.prototype.toString.call(para)}2、数组去重functionunique1(arr){return[...newS......