当前位置: 首页 > news >正文

并发编程之线程安全lock

并发编程之线程安全lock

为什么需要lock接口锁

提供了编程级别的锁接口,锁实现,可以灵活使用、扩展

lock类层次结构

自jdk1.5后,增加的JUC包下的locks包包含的类如下:

image-20220124125015818

image-20220127143919642

image-20210413182850908

AQS

AbstractOwnableSynchronizer:提供独占模式同步的线程所有者;

AbstractQueuedSynchronizer:抽象的队列同步器;

AbstractQueuedLongSynchronizer:将AbstractQueuedSynchronizer的状态int改为long类型的抽象的队列同步器;

ReentrantLock

特性:独占锁;支持公平锁、非公平锁两种模式;可重入锁

ReentrantReadWriteLock

维护一对关联锁,一个用于读操作,一个用于写操作;读锁可以有多个线程同时持有,写锁时排他锁

适用场景:适合读取线程比写入线程多的场景,改进互斥锁的性能,比如:集合的并发线程安全性改造缓存组件

锁降级指的是写锁降级成为读锁。把持住当前拥有的写锁的同时,再获取到读锁,随后释放写锁的过程。写锁是线程独占,读锁是共享,所以写- >读是降级。(读->写,是不能实现的)

lock类方法

lock接口方法

  1. Acquires the lock 获取锁
void lock();
  1. Acquires the lock unless the current thread is interrupted 获取锁,除非当前线程被打断
void lockInterruptibly() throws InterruptedException;
  1. Acquires the lock only if it is free at the time of invocation 仅在调用时锁是空闲的情况下才获取锁(尝试获取锁)
boolean tryLock();
//带时间限制的尝试获取锁,超过指定时间无法获取锁,则继续执行
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
  1. Releases the lock 释放锁
void unlock();

lock锁使用

lock锁使用

JUC包中的ReentrantLock类中给我们提供了使用lock锁的实例,如下:

 class X {private final ReentrantLock lock = new ReentrantLock();// ...public void m() {lock.lock();  // block until condition holdstry {// ... method body} finally {lock.unlock()}}}}

建议:正如上边的实例,在使用lock锁时,正确的姿势:配合try{}finally{}一块使用,保证获取锁后,在不使用锁时将锁能够及时的释放:lock.lock lock.unlock

    public static void main(String[] args) {
//        test1();test2();}/****/private static void test1() {lock.lock();System.out.println("当前线程"+Thread.currentThread().getName()+"获的锁的次数为:"+lock.getHoldCount());;if( lock.tryLock()){System.out.println("尝试获得锁成功");}System.out.println("当前线程"+Thread.currentThread().getName()+"获的锁的次数为:"+lock.getHoldCount());;try{System.out.println("获取了锁");}finally {lock.unlock();System.out.println("当前线程"+Thread.currentThread().getName()+"释放一次锁,目前获的锁的次数为:"+lock.getHoldCount());lock.unlock();System.out.println("当前线程"+Thread.currentThread().getName()+"释放一次锁,目前获的锁的次数为:"+lock.getHoldCount());}}public static void test2(){lock.lock();try {final Thread thread = new Thread(new Runnable() {@Overridepublic void run() {try {System.out.println("当前线程" + Thread.currentThread().getName() + "开始获得锁");lock.lockInterruptibly();//子线程可以被打断,执行finallylock.lock();//子线程不可被打断,一直阻塞等待System.out.println("当前线程" + Thread.currentThread().getName() + "结束获得锁");} catch (Exception e) {e.printStackTrace();} finally {System.out.println("当前线程" + Thread.currentThread().getName() + "释放锁");lock.unlock();}}});thread.start();System.out.println("当前线程" + Thread.currentThread().getName() + " 开始打断interrupt子线程"+thread.getName());thread.interrupt();} finally {
//            lock.unlock();}}
}

自定义锁

自定义实现Lock
public class MyLock implements Lock {//当前锁的拥有者private AtomicReference<Thread> owner = new AtomicReference<>();//等待队列private LinkedBlockingQueue<Thread> waiters = new LinkedBlockingQueue<>();@Overridepublic boolean tryLock() {return owner.compareAndSet(null,Thread.currentThread());}@Overridepublic void lock() {while (!tryLock()){waiters.offer(Thread.currentThread());LockSupport.park();waiters.remove(Thread.currentThread());}}public void unlock() {if(owner.compareAndSet(Thread.currentThread(),null)){waiters.forEach(thead -> {LockSupport.unpark(thead);});}}@Overridepublic void lockInterruptibly() throws InterruptedException {}@Overridepublic boolean tryLock(long time, TimeUnit unit) throws InterruptedException {return false;}@Overridepublic Condition newCondition() {return null;}
}
使用自定义实现的lock锁
public class MyLockDemo {MyLock lock = new MyLock();public  int i =0 ;public static void main(String[] args) {for (int i=0;i<200;i++){test2(i+1);}}public static void test2(int n){MyLockDemo demo = new MyLockDemo();List<Thread> list = new ArrayList<>();for (int i=0;i<200;i++){final Thread thread = new Thread(new Runnable() {@Overridepublic void run() {demo.add();}});thread.start();list.add(thread);}for (Thread thread:list){try {thread.join();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("第 "+n+" 次:"+demo.i);}public void add(){lock.lock();try{i++;}finally {lock.unlock();}}
}

ReentrantReadWritLock使用

Demo5_ReadWrite01的实例,是一个读和写都用一把锁,也就是共同争抢一把锁,这样效率太低了,代码如下:

/*** 既有读,又有写时, 读也需要加锁。* 一个写(独占),多个读(共享),多个读不能相互影响,读写之间互斥。*/
public class Demo5_ReadWrite01 {long i = 0;Lock lock = new ReentrantLock();//为什么读也要加锁???public void read() {lock.lock();try {long a = i;System.out.println(Thread.currentThread().getName()+" 读到了值"+a);}finally {lock.unlock();}}public void write() {lock.lock();try {i++;System.out.println(Thread.currentThread().getName()+" 修改值:"+i);}finally {lock.unlock();}}public static void main(String[] args) throws InterruptedException {final Demo5_ReadWrite01 demo = new Demo5_ReadWrite01();List<Thread> list = new ArrayList<Thread>();for (int i=0; i<=10; i++){int n = i;String threadName = n == 0 ? "writeThread" : "readThread"+n;Thread th = new Thread(()->{long starttime = System.currentTimeMillis();while (System.currentTimeMillis() - starttime < 1000) {if (n==0) {demo.write();}else{demo.read();}}}, threadName);list.add(th);th.start();}for(Thread th : list) {th.join();}}}

由于读和写都通争抢一把锁,效率太低了,因此使用读写锁ReentrantReadWriteLock改进,读一把锁,写一把锁,代码Demo6_ReadWrite02,如下:


public class Demo6_ReadWrite02 {volatile long i = 0;ReadWriteLock rwLock = new ReentrantReadWriteLock();//为什么读也要加锁???public void read() {// 获得读锁,读锁共享rwLock.readLock().lock();try {long a = i;System.out.println(Thread.currentThread().getName()+" 读到了值"+a);}finally {// 释放读锁rwLock.readLock().unlock();}}public void write() {// 获取写锁,写锁排他rwLock.writeLock().lock();try {i++;System.out.println(Thread.currentThread().getName()+" 修改值:"+i);}finally {// 释放写锁rwLock.writeLock().unlock();}}public static void main(String[] args) throws InterruptedException {final Demo5_ReadWrite01 demo = new Demo5_ReadWrite01();List<Thread> list = new ArrayList<Thread>();for (int i=0;i<=10; i++){int n = i;String threadName = n == 0 ? "writeThread" : "readThread"+n;Thread th = new Thread(()->{long starttime = System.currentTimeMillis();while (System.currentTimeMillis() - starttime < 1000) {if (n==0) {demo.write();}else{demo.read();}}}, threadName);list.add(th);th.start();}for(Thread th : list) {th.join();}}}

ReentrantReadWritLock锁降级

ReentrantReadWritLock:读写锁包含:读锁ReadLock、写锁WriteLock

/*** 如果缓存中存在的,则从缓存中获取;* 如果缓存中不存在的,则从数据库中获取*/
public class CacheDemo {public static void main(String[] args) {System.out.println(new CacheDemo().getData("admin"));System.out.println(new CacheDemo().getData("admin"));}static ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();private static volatile boolean isCache ;private String getData(String key) {String date = "";//获取读锁rwLock.readLock().lock();try {//缓存中有if(isCache){date =  Redis.map.get(key);}else{//缓存中没有,从数据库获取//..........................//从数据库获取//释放读锁rwLock.readLock().unlock();//获取写锁rwLock.writeLock().lock();try {//判断缓存中是否有if(isCache){date = Redis.map.get(key);}else {date = DBService.getData();Redis.map.put(key,date);isCache = true;}rwLock.readLock().lock();//锁降级、由写锁变为读锁}finally {rwLock.writeLock().unlock();}}return date;}finally {//释放读锁rwLock.readLock().unlock();}}
}
class DBService{static String getData(){System.out.println("开始查询数据库。。。");return "{name:张三,age:18}";}
}
class Redis{public static Map<String,String> map = new HashMap<>();
}

Condition

用于替代wait/notify。

Object中的wait(),notify(),notifyAll()方法是和synchronized配合使用的,可以唤醒一个或者全部(单个等待集);

Condition是需要与Lock配合使用的,提供多个等待集合,更精确的控制(底层是park/unpark机制);

image-20231228070118732

同步锁的本质-排队

同步的方式:独享锁 – 单个队列窗口, 共享锁 – 多个队列窗口

抢锁的方式: 插队抢(不公平锁)、先来后到抢锁(公平锁)

没抢到锁的处理方式: 快速尝试多次(CAS自旋锁)、阻塞等待

唤醒阻塞线程的方式(叫号器):全部通知、通知下一个

AQS抽象队列同步器

提供了对资源占用、释放,线程的等待、唤醒等等接口和具体实现。

可以用在各种需要控制资源争用的场景中。(ReentrantLock/CountDownLatch/Semphore)

image-20231228065907203

acquire、 acquireShared : 定义了资源争用的逻辑,如果没拿到,则等待。

tryAcquire、 tryAcquireShared : 实际执行占用资源的操作,如何判定一个由使用者具体去实现。

release、 releaseShared : 定义释放资源的逻辑,释放之后,通知后续节点进行争抢。

tryRelease、 tryReleaseShared: 实际执行资源释放的操作,具体的AQS使用者去实现。

资源占用流程

image-20231228070017624

线程封闭

线程封闭概念

多线程中访问共享可变数据时,涉及到线程间数据同步的问题。

并不是所有的时候,都要用到共享数据,若数据都被封闭在各自的线程中,就不需要同步,这种通过将数据封闭在线程中而避免使用同步的技术称为线程封闭

线程封闭实现

ThreadLocal实现了线程封闭,它是java中一种特殊的变量;它是一个线程级别变量,每个线程都拥有一个ThreaLocal,就是每个线程都拥有自己独立的一个变量,不存在竞争,在并发模式下是绝对安全的变量。

用法:ThreadLocal var = new ThreadLocal();

会自动在每一个线程上创建一个T的副本,副本之间彼此独立,互不影响。可以用ThreadLocal存储一些参数,以便在线程中多个方法中使用,用来代替方法传参的做法

栈封闭

局部变量的固有属性之一就是封闭在线程中。它们位于执行线程的栈中,其他线程无法访问这个栈。

流程

[外链图片转存中…(img-YNyxQVpp-1747910139323)]

线程封闭

线程封闭概念

多线程中访问共享可变数据时,涉及到线程间数据同步的问题。

并不是所有的时候,都要用到共享数据,若数据都被封闭在各自的线程中,就不需要同步,这种通过将数据封闭在线程中而避免使用同步的技术称为线程封闭

线程封闭实现

ThreadLocal实现了线程封闭,它是java中一种特殊的变量;它是一个线程级别变量,每个线程都拥有一个ThreaLocal,就是每个线程都拥有自己独立的一个变量,不存在竞争,在并发模式下是绝对安全的变量。

用法:ThreadLocal var = new ThreadLocal();

会自动在每一个线程上创建一个T的副本,副本之间彼此独立,互不影响。可以用ThreadLocal存储一些参数,以便在线程中多个方法中使用,用来代替方法传参的做法

栈封闭

局部变量的固有属性之一就是封闭在线程中。它们位于执行线程的栈中,其他线程无法访问这个栈。

相关文章:

  • 739. 每日温度
  • 西交交互增强与细节引导的具身导航!OIKG:基于观察图交互与关键细节融合框架下的视觉语言导航
  • 在MPI中将全局通信子划分为节点本地通信子
  • 前端JavaScript-对象-同Python及C++对比
  • 结合人工智能的应用
  • 5.22本日总结
  • leetcode每日一题 -- 3362. 零数组变换 III
  • PyQt学习系列01-框架概述与基础环境搭建
  • memcpy 函数的使用 (C语言)
  • 【SpringBoot实战指南】使用 Spring Cache
  • 通义灵码 2.5 版深度评测:智能编程的边界在哪里?
  • C# 项目
  • 【工具】Quicker/VBA|PPT 在指定位置添加参考线
  • Elasticsearch 分页查询的 from+size 有什么缺陷?如何优化深度分页?比较scroll API与search_after的差异
  • session、cookie或者jwt 解释一下
  • docker 启动一个python环境的项目dockerfile版本
  • HarmonyOS 鸿蒙应用开发基础:@Watch装饰器详解及与@Monitor装饰器对比分析
  • Android 添加系统服务的完整流程
  • 第十三章 watchdog组件配置
  • 广东省省考备考(第十七天5.22)—申论认识
  • 做外贸需关注的网站/百度搜索怎么优化
  • 公司做网站提供资料/优化推广网站怎么做
  • 阿克苏网站建设价格/网络营销的功能有哪些?
  • 个人网页设计首页/优化seo可以从以下几个方面进行
  • 公司名高端大气不重名/优化设计答案
  • wordpress url参数/seo 是什么