• 新知识点查漏补缺


    java值传递还是引用传递?(值传递)

    对于基本数据来说,在进行传递的时候, 将数据的值复制了一份进行的传递,所以我们也比较好理解的这种值传递;而对于对象数据类型,因为该对象本身指向的是它在内存中的地址,所以方法调用的时候,实际上是创建的地址的副本,所以在方法中对其值进行改变的时候,他的地址没有变,值也就跟着改变了;而当你重新创建一个对象的时候,它指向的便是另一个对象的地址了。这样看来跟值传递的定义便不冲突了。

    总之,Java中其实还是值传递的,只不过对于对象参数,值的内容是对象的引用。

    布隆过滤器

    BloomFilter 是由一个固定大小的二进制向量或者位图(bitmap)和一系列映射函数组成的。

    在初始状态时,对于长度为 m 的位数组,它的所有位都被置为0,如下图所示:

    image

    当有变量被加入集合时,通过 K 个映射函数将这个变量映射成位图中的 K 个点,把它们置为 1(假定有两个变量都通过 3 个映射函数)。

    image

    查询某个变量的时候我们只要看看这些点是不是都是 1 就可以大概率知道集合中有没有它了

    • 如果这些点有任何一个 0,则被查询变量一定不在;
    • 如果都是 1,则被查询变量很可能存在

    为什么说是可能存在,而不是一定存在呢?那是因为映射函数本身就是散列函数,散列函数是会有碰撞的。

    特性

    • 一个元素如果判断结果为存在的时候元素不一定存在,但是判断结果为不存在的时候则一定不存在
    • 布隆过滤器可以添加元素,但是不能删除元素。因为删掉元素会导致误判率增加。

    优点

    相比于其它的数据结构,布隆过滤器在空间和时间方面都有巨大的优势。布隆过滤器存储空间和插入/查询时间都是常数 $O(K)$,另外,散列函数相互之间没有关系,方便由硬件并行实现。布隆过滤器不需要存储元素本身,在某些对保密要求非常严格的场合有优势。

    布隆过滤器可以表示全集,其它任何数据结构都不能;

    布隆过滤器的典型应用有:

    • 数据库防止穿库。 Google Bigtable,HBase 和 Cassandra 以及 Postgresql 使用BloomFilter来减少不存在的行或列的磁盘查找。避免代价高昂的磁盘查找会大大提高数据库查询操作的性能。
    • 业务场景中判断用户是否阅读过某视频或文章,比如抖音或头条,当然会导致一定的误判,但不会让用户看到重复的内容。
    • 缓存宕机、缓存击穿场景,一般判断用户是否在缓存中,如果在则直接返回结果,不在则查询db,如果来一波冷数据,会导致缓存大量击穿,造成雪崩效应,这时候可以用布隆过滤器当缓存的索引,只有在布隆过滤器中,才去查询缓存,如果没查询到,则穿透到db。如果不在布隆器中,则直接返回。
    • WEB拦截器,如果相同请求则拦截,防止重复被攻击。用户第一次请求,将请求参数放入布隆过滤器中,当第二次请求时,先判断请求参数是否被布隆过滤器命中。可以提高缓存命中率。Squid 网页代理缓存服务器在 cache digests 中就使用了布隆过滤器。Google Chrome浏览器使用了布隆过滤器加速安全浏览服务

    乐观锁和悲观锁

    乐观锁和悲观锁是两种思想,用于解决并发场景下的数据竞争问题。

    • 乐观锁:乐观锁在操作数据时非常乐观,认为别人不会同时修改数据。因此乐观锁不会上锁,只是在执行更新的时候判断一下在此期间别人是否修改了数据:如果别人修改了数据则放弃操作,否则执行操作。
    • 悲观锁:悲观锁在操作数据时比较悲观,认为别人会同时修改数据。因此操作数据时直接把数据锁住,直到操作完成后才会释放锁;上锁期间其他人不能修改数据。

    悲观锁的实现方式是加锁,加锁既可以是对代码块加锁(如Java的synchronized关键字),也可以是对数据加锁(如MySQL中的排它锁)。

    乐观锁的实现方式主要有两种:CAS机制和版本号机制

    • CAS操作逻辑如下:如果内存位置V的值等于预期的A值,则将该位置更新为新值B,否则不进行任何操作。许多CAS的操作是自旋的:如果操作不成功,会一直重试,直到操作成功为止。CAS是由CPU支持的原子操作,其原子性是在硬件层面进行保证的。

    • 版本号机制也可以用来实现乐观锁。版本号机制的基本思路是在数据中增加一个字段version,表示该数据的版本号,每当数据被修改,版本号加1。当某个线程查询数据时,将该数据的版本号一起查出来;当该线程更新数据时,判断当前版本号与之前读取的版本号是否一致,如果一致才进行操作。

    需要注意的是,这里使用了版本号作为判断数据变化的标记,实际上可以根据实际情况选用其他能够标记数据版本的字段,如时间戳等。

    为什么要代码指令重排序

    重排序的好处:提高处理速度

    image

    图中左侧是 3 行 Java 代码,右侧是这 3 行代码可能被转化成的指令。
    可以看出 a = 100 对应的是 Load a、Set to 100、Store a,意味着从主存中读取 a 的值,然后把值设置为 100,并存储回去,同理, b = 5 对应的是下面三行 Load b、Set to 5、Store b,最后的 a = a + 10,对应的是 Load a、Set to 110、Store a。
    如果你仔细观察,会发现这里有两次“Load a”和两次“Store a”,说明存在一定的重排序的优化空间。

    经过重排序之后,情况如下图所示:
    image

    重排序后, a 的两次操作被放到一起,指令执行情况变为 Load a、Set to 100、Set to 110、 Store a。
    下面和 b 相关的指令不变,仍对应 Load b、 Set to 5、Store b。
    可以看出,重排序后 a 的相关指令发生了变化,节省了一次 Load a 和一次 Store a。
    重排序通过减少执行指令,从而提高整体的运行速度,这就是重排序带来的优化和好处。

    重排序的 3 种情况

    (1)编译器优化

    编译器(包括 JVM、JIT 编译器等)出于优化的目的,例如当前有了数据 a,把对 a 的操作放到一起效率会更高,避免读取 b 后又返回来重新读取 a 的时间开销,此时在编译的过程中会进行一定程度的重排。不过重排序并不意味着可以任意排序,它需要需要保证重排序后,不改变单线程内的语义,否则如果能任意排序的话,程序早就逻辑混乱了。
    (2)CPU 重排序

    CPU 同样会有优化行为,这里的优化和编译器优化类似,都是通过乱序执行的技术来提高整体的执行效率。
    所以即使之前编译器不发生重排,CPU 也可能进行重排,我们在开发中,一定要考虑到重排序带来的后果。
    (3) 内存的“重排序”

    内存系统内不存在真正的重排序,但是内存会带来看上去和重排序一样的效果,所以这里的“重排序”打了双引号。
    由于内存有缓存的存在,在 JMM 里表现为主存和本地内存,而主存和本地内存的内容可能不一致,所以这也会导致程序表现出乱序的行为。
    举个例子,线程 1 修改了 a 的值,但是修改后没有来得及把新结果写回主存或者线程 2 没来得及读到最新的值,所以线程 2 看不到刚才线程 1 对 a 的修改,此时线程 2 看到的 a 还是等于初始值。但是线程 2 却可能看到线程 1 修改 a 之后的代码执行效果,表面上看起来像是发生了重顺序。

    mysql的乐观锁,悲观锁,共享锁,排他锁

    共享锁:

    名词解释:共享锁又叫做读锁,所有的事务只能对其进行读操作不能写操作,加上共享锁后在事务结束之前其他事务只能再加共享锁,除此之外其他任何类型的锁都不能再加了。

    排他锁:

    名词解释:若某个事物对某一行加上了排他锁,只能这个事务对其进行读写,在此事务结束之前,其他事务不能对其进行加任何锁,其他进程可以读取,不能进行写操作,需等待其释放。

    乐观锁

    用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加1。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。

    悲观锁

    与乐观锁相对应的就是悲观锁了。悲观锁就是在操作数据时,认为此操作会出现数据冲突,所以在进行每次操作时都要通过获取锁才能进行对相同数据的操作,这点跟java中的synchronized很相似,所以悲观锁需要耗费较多的时间。另外与乐观锁相对应的,悲观锁是由数据库自己实现了的,要用的时候,我们直接调用数据库的相关语句就可以了。

    共享锁和排它锁是悲观锁的不同的实现,它俩都属于悲观锁的范畴。

    Innodb默认是行锁,行锁是基于索引实现的,如果不使用索引访问数据,会使用表锁

    Innodb与MyISAM的区别是Innodb是行锁,并且支持事务

    关于mysql索引

    https://mp.weixin.qq.com/s?__biz=MzI4Njg5MDA5NA==&mid=2247484721&idx=1&sn=410dea1863ba823bec802769e1e6fe8a&chksm=ebd74430dca0cd265a9a91dcb2059e368f43a25f3de578c9dbb105e1fba0947e1fd0b9c2f4ef&token=1676899695&lang=zh_CN###rd

    关于数据库

    https://www.zhihu.com/column/c_1104074839660294144

    undolog redolog binlog

    mysql(InnoDB)事务隔离级别(READ UNCOMMITTED) 与 锁

    并发编程

    对象的wait方法,notify方法

    当线程调用共享对象的 wait()方法时,当前线程只会释放当前共享对象的锁,并且会阻塞,当前线程持有的其他共享的监视器锁并不会被释放

    一个线程调用共享对象的 notify ()方法后,会唤醒一个在该共享变量上调用 wait 系列方法后被挂起的线程。 一个共享变量上可能会有多个线程在等待,具体唤醒哪个等待的线程是随机的。

    被唤醒的线程不能马上从 wait 方法返回并继续执行,它必须在获取了共享对象的监视器锁后才可以返回也就是唤醒它的线程释放了共享变量上的监视器锁后,被唤醒的线程也不一定会获取到共享对象的监视器锁,这是因为该线程还需要和其他线程一起竞争该锁,只有该线程竞争到了共享变量的监视器锁后才可 继续执行。

    类似 wait 列方法,只有当前线程获取到了共享变量的监视器锁后,才可以调用共享变量的 notify () 方法,否则会抛出 Illega!MonitorStateE ception 异常。

    线程的join方法,sleep方法

    在主线程里面启动了两个子线程,然后分别调用了它 join () 方法,那么主线程首先会在调用 threadOne.join() 方法后被阻塞,等待 threadOne 执行完毕后返回,threadOne 执行完毕后 threadOne.join () 就会返回 ,然后主线程调用 threadTwo.join() 方法后再次被阻塞 等待 threadTwo 执行完毕后返 回。

    一个执行中的线程调用了Threadleep法后,调用线程会暂时让出指定时间的执行权,也就是在这期间不参与 CPU 的调度,但是该线程所拥有的监视器 源,比如锁还是持有不让出的, 指定的睡眠时间到了后该函数,会正常返回,线程就处于就绪状态,然后参与 CPU 的调度,获取到 CPU 资源后就可以继续运行了

    让出 CPU 执行权的 yield 方法

    操作系统是为每个线程分配一个时 片来占有 PU 的, 正常情况下当一个线程把分配给自己的时间片使用完后,线程调度器才会进行下一轮的线程调度

    当一 线程调用 ield 方法时, 当前线程会让出 PU 使用权,然后处于就绪状态,线程调度器会从线程就绪队列里面获取一个线程优先级最高的线程,当然也有可能会调度到刚刚让出 PU 的那个线程来获取 PU 行权。

    sleep方法和yield方法的区别:

    当线程调用 sleep 方法时调用线程会被阻塞挂起指定的时间,在这期间线程调度器不会去调度该线程。而调用 yield方法时,线程只是让出自己剩余的时间片,并没有被阻塞挂起,而是处于就绪状态,线程调度器下一次调度时就有可能调度到当前线程执行。

    破坏死锁

    资源的有序性破坏 资源的请求并持有条件和环路等待条件 因此避免了死锁

    让在线程B中获取资源的顺序和在线程A中获取资源的顺序保持一致,其实资源分配有序性就 指,假如线程A和线程B都需要资源 1, 2, 3, .. . , n,对资源

    进行排序,线程A 和线程B 有在获取了资源 n-1 时才能去获取资源n

    ThreadLocal

    创建 ThreadLocal 变量后,每个线程都会复制 到自己的本地内存

    ThreadLocal 就是 个工具壳,它通过 set 方法把 value 值放入调用线程的 threadLocals 里面并存放起来, 当调用 线程调用它的 get方法时,再从当前线程的 threadLocals 变量里面将其拿出来使用

    在每个线程内部都有一个名为threadLocals 的成员变量,该变量类型为 HashMap ,其中key 为我们定义 的ThreadLocal 变量的this引用, value 则为

    我们使用set方法设置的值,默认情况下,该成员变量为null,只有当前线程第一次调用ThreadLocal的set或者get方法时才会创建它,每个线程的本地变量存放在线程自己的内存变量 threadLocals 中,如果当前线程一直不消亡 那么这些本地变量会一直存在,所以可能会造成内存溢出,使用完毕后要记得调用 ThreadLocal 的remove 方法删除对应线程 threadLocals 中的本地变量。

    InheritableThreadLocal

    InheritableThreadLocal继承自 ThreadLocal 其提供了一个特性,就是让子线程可 以访问在父线程中设置的本地变量;

    当父线程创建子线程时,构造函数会把父线程中 inheritableThreadLocal 变量里面的本地变量复制一份保存到子线程的 inheritableThreadLocals 变量里面。

  • 相关阅读:
    插入排序的算法分析
    SQL的UNION操作
    二分查找的思路
    怎么看吉他简谱
    一句CSS代码杜绝网站iframe挂马
    关于NewFolder.文件夹无法删除的办法
    C#监听USB接入
    C# 系统服务添加安装
    .NET 实现ISAPI过滤器,指定类型文件防下载
    ASP 简单的异或加密方法
  • 原文地址:https://www.cnblogs.com/RealGang/p/15397001.html
Copyright © 2020-2023  润新知