JUC——读写锁

读写锁介绍

读锁又叫共享锁,可以允许多个线程读数据;写锁又叫做独占锁,只能允许一个线程进行写操作。

读锁和写锁都可能引起死锁现象:
在这里插入图片描述
在这里插入图片描述
所以为避免死锁现象,对相应数据上什么锁,就只对相应数据做什么操作。

ReentrantReadWriteLock

读锁在同一时刻可以允许多个读线程获取,即允许多个读线程同时上锁读取数据,不允许写线程获取操作。(上多个读锁:允许多个读线程,不允许写线程)

写锁同一时刻只能有一个写线程获取成功,其他都会被阻塞。(上一个写锁:只允许一个写线程,不允许其他读、写线程)

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

class MyCache {
    private volatile Map<String, Object> map = new HashMap<>();
    private ReadWriteLock rwLock = new ReentrantReadWriteLock();

    public void put(String key, Object value) {
        rwLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t 正在写" + key);
            //暂停一会儿线程
            try {
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "\t 写完了" + key);
            System.out.println();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            rwLock.writeLock().unlock();
        }

    }

    public Object get(String key) {
        rwLock.readLock().lock();
        Object result = null;
        try {
            System.out.println(Thread.currentThread().getName() + "\t 正在读" + key);
            try {
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            result = map.get(key);
            System.out.println(Thread.currentThread().getName() + "\t 读完了" + result);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            rwLock.readLock().unlock();
        }
        return result;
    }
}

public class ReadWriteLockDemo {


    public static void main(String[] args) {
        MyCache myCache = new MyCache();

        for (int i = 1; i <= 5; i++) {
            final int num = i;
            new Thread(() -> {
                myCache.put(num + "", num + "");
            }, String.valueOf(i)).start();
        }
        for (int i = 1; i <= 5; i++) {
            final int num = i;
            new Thread(() -> {
                myCache.get(num + "");
            }, String.valueOf(i)).start();
        }
    }
}

在这里插入图片描述

COW和读写锁对比

CopyOnWrite缺点:
内存占用问题:因为CopyOnWrite的写时复制机制每次进行写操作的时候都会有两个数组对象的内存,如果这个数组对象占用的内存较大的话,如果频繁的进行写入就会造成频繁的Yong GC和Full GC,此时应该考虑其他的容器,例如 ConcurrentHashMap。

数据一致性问题:
CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。读操作的线程可能不会立即读取到新修改的数据,因为修改操作发生在副本上。但最终修改操作会完成并更新容器所以这是最终一致性。当时有说到解决这两个缺点我们可以使用Collections.synchronizedList()来替代,找个无非就是对list的增删改查方法都加了synchronized实现。我们知道synchronized其实是一个独占锁 (排他锁),但是这样的话就会存在一个性能问题,如果对于读多写少的场景,每次读也要去获取锁,读完了之后再释放锁,这样就造成了每个读的请求都要进行获取锁,但是读的话并不会引起数据不安全,这样就会造成一个性能瓶颈。为了解决这个问题,就又出现了一种新的锁,读写锁(ReadWriteLock)。

03-08
### Java并发包(JUC)使用教程 #### 线程安全集合、线程池和同步工具类 `java.util.concurrent` 包提供了多种高级实用程序类,包括线程安全集合、线程池以及各种同步辅助类。这些组件不仅适合初学者快速上手,也能满足专家级开发人员的需求[^1]。 #### CountDownLatch的应用案例 当存在一个或多个子任务需要并行处理完毕之后再启动后续流程的情况时,可以考虑采用 `CountDownLatch` 来协调各个参与方的行为。此机制允许主线程阻塞直到所有指定的工作单元都已完成为止。值得注意的是,一旦计数值达到零,则该对象不可再次利用;若需重复此类逻辑则应构建新的实例来实现相同目的[^5]。 ```java // 创建具有固定倒计数的存器 final int N = 3; CountDownLatch latch = new CountDownLatch(N); for(int i=0; i<N; ++i){ Thread t = new Thread(() -> { try{ // 执行具体业务... System.out.println(Thread.currentThread().getName() + " finished."); // 表明当前线程已经结束工作 latch.countDown(); }catch(Exception e){e.printStackTrace();} }); t.start(); } try{ // 主线程在此处等待其他N个线程完成它们的任务 latch.await(); }catch(InterruptedException ie){} System.out.println("All threads have completed their tasks"); ``` #### Semaphore用于限流控制 为了防止过多请求涌入造成系统过载,在某些情况下可能希望限制同一时刻能够进入特定区域内的最大活跃线程数目。此时可借助于信号量(`Semaphore`)来进行流量调控——它通过发放许可的方式管理着内部可用资源的数量上限,并确保任何时候都不会超出预设阈值范围之外运行更多进程[^2]。 ```java import java.util.concurrent.Semaphore; public class LimitedResourceAccess { private final static int MAX_PERMITS = 5; private static Semaphore semaphore = new Semaphore(MAX_PERMITS, true); public void accessSharedResource(){ try { // 获取许可证之前会一直被挂起直至有空闲名额释放出来 semaphore.acquire(); // 对共享数据结构或其他临界区的操作... // 完成后记得及时归还所占用的一个位置给下一位使用者 semaphore.release(); } catch (InterruptedException ex) { /* handle exception */ } } } ``` #### 原子更新器特性说明 针对那些想要高效地对基本类型变量实施原子化写操作而不必担心竞态条件影响的情形而言,JDK 提供了一系列专门设计用来支持这种需求的数据结构,比如 `AtomicIntegerFieldUpdater`, `AtomicLongFieldUpdater` 和 `AtomicReferenceFieldUpdater`。其中前两者仅作用于原始整型成员属性之上而无法直接应用于对应的封装版本;后者则是通用解决方案之一,可用于任何引用类型的字段修改动作当中[^3]。 ```java class AtomicDemo { volatile long value; static final AtomicLongFieldUpdater<AtomicDemo> updater = AtomicLongFieldUpdater.newUpdater(AtomicDemo.class,"value"); void incrementValue(){ while(true){ long current = this.value; long next = current + 1L; if(updater.compareAndSet(this,current,next)){ break; } } } } ``` #### ConcurrentNavigableMap接口解析 类似于标准库中的 `NavigableMap<K,V>` 接口定义了一套有序映射表抽象模型及其相应的方法集合作为扩展基础,`ConcurrentNavigableMap<K,V>` 则进一步增强了前者的能力范畴使之具备更好的多线程环境适应性和性能表现力。两个API之间主要区别体现在返回值得差异方面而非核心语义层面的变化[^4]。 ```java import java.util.concurrent.ConcurrentSkipListMap; public class MapExample { public static void main(String[] args) throws InterruptedException { ConcurrentSkipListMap<Integer,String> map = new ConcurrentSkipListMap<>(); Runnable task = ()->{ for(int key : Arrays.asList(7,8,9)) map.put(key,"Thread-" + Thread.currentThread().getId()); }; Thread threadA = new Thread(task),threadB=new Thread(task); threadA.start();threadB.start(); threadA.join();threadB.join(); System.out.println(map.subMap(7,true,9,false)); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

GuochaoHN

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

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

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

打赏作者

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

抵扣说明:

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

余额充值