视频总结----java锁、分布式锁

锁集合(这个图要记住的):
在这里插入图片描述
悲观锁
悲观锁人为自己在使用数据的时候一定会有别的线程来修改数据,在获取数据的时候先加锁,确保数据不会被别的线程修改。
锁实现:关键字synchronized、接口Lock实现类。
适用场景:写操作较多、先加锁保证数据的正确性。
悲观锁执行过程:
在这里插入图片描述

乐观锁
乐观锁则是相反,认为自己使用数据的时候不会有别的线程来修改数据,所以不需要加锁,只是在更新数据的时候去半段
之前有没有别的线程更新这个数据。
锁实现:CAS算法,例如AtomicInteger类的原子自增是通过CAS自旋实现。
适用场景:读操作较多,不加锁的特点能够使其读操作的性能大幅度提升。比如秒杀系统。
乐观锁执行过程:
在这里插入图片描述

CAS(Compare and Swap): 比较于交换。保证原子性。
无锁算法:基于硬件原语实现,在不适用锁的情况下实现多线程的变量同步。
java.util.concurrent.atomic包下原子类就是通过CAS实现的。
CAS原理:需要读写内存值、进行比较的值A、要写入的新值B。
CAS锁过程:
里面有循环自选。缺点就是高并发下多次自旋。
还会导致ABA问题:比如:有人挪用了银行的100万,但是主管没发现之前,又把钱挪回去。如下ABA图
在这里插入图片描述
ABA图:多个线程下,线程1去修改值,此时有两个线程需要去这个值,在线程2去操作之前,线程3,又给改回去了。那么线程2,发现“版本"没变,直接修改成功。
在这里插入图片描述
解决ABA问题方案:
AtomicStampedReference在变量前面添加版本号,每次变量更新的时候把版本号+1。
这个东西在JDK中已经解决了。我们放心使用就行。

自旋锁
是指当一个线程在获取锁的的时候,如果已经被其他线程获取,那么该线程将循环等待,然后不断的判断锁是否能够
被成功获取,自旋直到获取到锁才会退出循环。
自旋锁存在的意义与使用场景:
1、阻塞与唤醒线程需要操作系统切换CPU状态,需要消耗一定时间。
2、同步代码块逻辑简单,执行时间很短。

自旋锁+非自旋过程:
在这里插入图片描述
进程和线程上下文切换:cpu的使用权就是一段时间片
在这里插入图片描述
这就是为什么线程上下文切换耗时间。
1、切换需要将线程相关的数据保存到寄存器。寄存器是多CPU核共用的。
2、恢复上个线程需要改回指针指向。

synchronized的用法:
1、同步实例方法,锁是当前实例对象。
2、同步类方法,锁是当前类对象。
3、同步代码块,锁是括号内的对象。

synchronized的实现方式:
synchronized是JVM内置锁,通过内部对象Monitor(监视器锁)实现,基于进入与退出Monitor对象实现方法与代码块同步,监视器锁的实现
依赖底层操作系统的Mutex lock(互斥锁)实现。
验证的话,查看反编译组成:
在这里插入图片描述
JVM内置锁机制:
在这里插入图片描述
在这里插入图片描述
如何判断一个对象是加了锁的呢?这个涉及到对象头。
对象的内存结构:
在这里插入图片描述

对象是怎么存储的(面试需从这三点来回答):
1、对象的实例存储在堆空间
2、对象的元数据存在元数据区。
3、对象的引用存在栈空间。

java并发锁之深入AQS(AbstractQueuedSynchronizer)机制
隐示锁:(synchronized)是基于jvm内置锁。jvm会自动去加锁跟解锁。
显示锁:ReentrantLock一个可重入的锁(即多次使用其加锁)。整个过程需要我们手动编写代码去控制。
这ReentrantLock就是基于AQS(抽象队列同步器)实现的。

ReentrantLock lock = new ReentrantLock(true); // true指定为公平锁
public class ReentrantLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = 7373984872572414699L;
    /** Synchronizer providing all implementation mechanics */
    private final Sync sync;

    /**
     * Base of synchronization control for this lock. Subclassed
     * into fair and nonfair versions below. Uses AQS state to
     * represent the number of holds on the lock.
     */
    abstract static class Sync extends AbstractQueuedSynchronizer {

程序举例子:
在这里插入图片描述
图形结构分析:公平锁先来先排队。
CLH队列是基于双向链表结构的队列。线程为阻塞的机制。
在这里插入图片描述
非公平锁:来了个恶霸,我来了发现位置刚好空着。恶霸就给霸占了。
在这里插入图片描述

JMM内存结构
JMM即Java内存模型(Java Memory Model)。可以理解为它是一种抽象出来的硬件存储模型的规范。

根据JMM的设计,系统存在一个主内存(Main Memory),Java中所有变量都储存在主存中,对于所有线程都是共享的。每条线程都有自己的工作内存(Working Memory),工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作都是在工作内存中进行,线程之间无法相互直接访问,变量传递均需要通过主存完成。

多核CPU多级缓存一致性协议MESI
多核CPU的情况下有多个一级缓存,如何保证缓存内部数据的一致,不让系统数据混乱。这里就引出了一个一致性的协议MESI。
四种状态: M(修改)、 E(独占) 、S(共享)、 I(无效)

变量可见性:线程间的通信。
在这里插入图片描述

分布式锁:
上面的问题可以用JVM内部锁解决。但是JVM外部的呢?分布式锁用于解决JVM外部的并发处理。
前提知识回忆:
首先redis是解决数据库压力的。
缓存穿透:去redis读取没有命中数据,就称为缓存穿透。这个无法避免,所以要想办法避免。
解决方案:
1、将第一次查询出来的null值缓存到redis。(同一id不停请求)
2、普通过滤器(带来的问题,内存紧张),将所有UUid存起来(黑客每次用uuId请求);
3、布隆过滤器(解决了内存紧张)

布隆算法:通过一定的错误率来降低内存的占用。算出不存在一定不存在,算出存在不一定存在。

redis雪崩:redis中缓存大量的热门数据,突然在莫一个时刻,集中失效。
情景:
1、redis中的数据,有效期都是一样的。解决:给每条数据设置一个随机有效期,给缓冲时间,多次发现时间去数据库取数据,则同步数据。
2、redis数据挂掉了。解决方式:分布式缓存。

redis缓存击穿:redis只存一条数据,某一刻数据失效。实质都是缓存穿透。只不过他们是缓存穿透的特殊表现形式。

上面来说:是针对大型公司,小公司而言直接请求数据库也没什么问题。超大型的项目才需要特意去处理缓存穿透等现象。

Zookeeper分布式锁:解决缓存击穿。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值