/**
* Copyright (C), 2020
* FileName: Java后端面试总结
* Author: Marlon
* Email: [email protected]
* Date: 2020/2/18
* Description:
*/
JVM基于进入和退出Monitor对象来显示方法同步和代码块同步,但两者的实现细节不一样。代码块同步是使用monitorenter和monitorexit指令实现的,而方法同步是使用另外一种方式实现的。monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法的结束处和异常处,JVM保证每个monitorenter必须有对应的monitorexit与之配对。
Synchronize是可重入锁,一个获得锁的线程可以再次进入一个同步块方法
CAS并发原语提现在Java语言中就是sun.miscUnSafe类中的各个方法。调用UnSafe类中的CAS方法,JVM会帮我实现CAS汇编指令.这是一种完全依赖于硬件 功能,通过它实现了原子操作。再次强调,由于CAS是一种系统原语,原语属于操作系统用于范畴,是由若干条指令组成,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许中断,也即是说CAS是一条原子指令,不会造成所谓的数据不一致的问题。
CAS问题:
解决ABA:版本号,JDK
的Atomic
包里提供了一个类Atomic
包里提供了一个AtomicStampedReference
来解决ABA问题
####4. HashMap数据结构线程安全吗,举个例子HashMap怎么线程不安全
不安全,在resize的时候,两个线程同时操作会出现链表成环问题,导致查找一个不存在的key的时候,CPU利用率达到100%,解决方:使用ConcurrentHashMap
####5. java的基本数据类型和字节数
整型:byte(1字节)、short(2字节)、int(4字节)、long(8字节)
浮点型:float(4字节)、double(8字节)
字符:char(2字节)
布尔:boolean(1位)
####6. Java,volatile关键字
volatile 是 java 提供的最轻量级的同步机制,当变量定义为 volatile 后,可以保证此变量对多线程的可见性。多个线程可以读到内存中最新的值。volatile可以保证可见性和有序性,不能保证原子性。根据总线上的嗅探机制修改共享变量会使其他线程中的缓存变量失效,但不是立即从主内存中更新值,而是等到下一次use的时候,发现值是无效的,才会从主内存中读取新的值。
StringBuilder不是线程安全的,StringBuffer是线程安全的。
Java 中字符串属于对象,String的值是不可变的,这就导致每次对String的操作都会生成新的String对象。
当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)。
由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。
Java的CAS会使用现代处理器上提供的高效机器级别原子指令,这些原子指令以原子方式对内存执行读-改-写操作,这是在多处理器中实现同步的关键。同时,volatile变量的读/写和CAS可以实现线程之间的通信。把这些特性整合在一起,就形成了整个concurrent包得以实现的基石。如果我们仔细分析concurrent包的源代码实现,会发现一个通用化的实现模式:
AQS,非阻塞数据结构和原子变量类(java.util.concurrent.atomic包中的类),这些concurrent包中的基础类都是使用这种模式来实现的,而concurrent包中的高层类又是依赖于这些基础类来实现的。从整体来看,concurrent包的实现示意图如下:
所有异常都是由trowable类继承而来,但在下一层立即分解为两个分支:Error和Exception
https://blog.csdn.net/qq_30264689/article/details/81903031
9个
clone、getClass、toString、finalize、equals、hashCode、wait、notify、notifyAll
https://blog.csdn.net/qpzkobe/article/details/81007463
clone方法执行的是浅拷贝
对于对象中的引用类型,浅拷贝复制引用,深拷贝复制值
数组中的clone():
一维数组:深克隆;(重新分配空间,并将元素复制过去)
二维数组:浅克隆。(只传递引用)
如何实现二维数组的深克隆呢?对每一维数组调用clone方法。
B、B+树:https://my.oschina.net/u/4116286/blog/3107389
动态查找树主要包括:二叉查找树,平衡二叉树,红黑树,B树,B-树,查找的时间复杂度就为O(log2N),通过对数就可以发现降低树的深度就会提高查找效率。在大数据存储过程,大量的数据会存储到外存磁盘,外存磁盘中读取与写入某数据的时候,首先定位到磁盘中的某一块,这就有个问题:如何才能有效的查找磁盘中的数据呢,这就需要一种高效的外存数据结构,也就引出了下面的课题。
辅助索引把一个复杂度大概为log2(N)的二次搜索问题变成了logb(N)磁盘读,b是数据块的因素(树的分叉数)(也就是一个数据块容纳的条目数量)
红黑树的复杂度:包含n个内部节点的红黑树的高度是 O(log(n))。
B树也称B-树,它是一颗多路平衡查找树。二叉树我想大家都不陌生,其实,B树和后面讲到的B+树也是从最简单的二叉树变换而来的,并没有什么神秘的地方,下面我们来看看B树的定义。
总结:5阶B树,一个节点最多4个关键字,最少2个关键字,关键字顺序排列,每个节点既包含索引也有数据,叶子结点都在同一层,典型的B树:
B+树其实和B树是非常相似的,我们首先看看相同点。
不同点。
B+树相对于B树的优势:
b+ 树为什么左右节点用指针连接:
B+树只有叶结点存储数据。叶结点连起来正好是所有数据的有序序列,可以用来做全表顺序扫描或者范围查询。
红黑树是一个平衡的二叉树,但不是一个完美的平衡二叉树。虽然我们希望所有查找都能在~lgN次比较内结束,但是这样在动态插入中保持树的完美平衡代价太高,所以,我们稍微放松一下限制,希望找到一个能在对数时间内完成查找的数据结构。这个时候,红黑树站了出来。
红黑树的复杂度:包含n个内部节点的红黑树的高度是 O(log(n))。
红黑树需要满足的五条性质:
性质一:节点是红色或者是黑色
在树里面的节点不是红色的就是黑色的,没有其他颜色,要不怎么叫红黑树呢,是吧。
性质二:根节点是黑色
根节点总是黑色的。它不能为红。
性质三:每个叶节点(NIL是空节点)是黑色
这个可能有点理解困难,可以看图:
这个图片就是一个红黑树,NIL节点是个空节点,并且是黑色的。
性质四:每个红色节点的两个子节点都是黑色的(也就是说不存在两个连续的红色节点)
就是连续的两个节点不能是连续的红色,连续的两个节点的意思就是父节点与子节点不能是连续的红色。
性质五:从任一节点到其每个叶节点的所有路径都包含相同数目的黑色节点;
当我们进行插入或者删除操作时所作的一切操作都是为了调整树使之符合这五条性质。
最多只有两个孩子结点的树结构,二叉树结点的最大度数为2,是一种用的很多的数据结构,二叉树类型:
(1)完全二叉树——若设二叉树的高度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第h层有叶子结点,并且叶子结点都是从左到右依次排布,这就是完全二叉树。
(2)满二叉树——除了叶结点外每一个结点都有左右子叶且叶子结点都处在最底层的二叉树。
(3)平衡二叉树——平衡二叉树又被称为AVL树(区别于AVL算法),它是一棵二叉排序树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
索引是帮助Mysql高效获取数据的排好序的数据结构
索引的数据结构:
B树:Innodb默认一个结点16KB
,大约可以存下1170
个关键字,3层的红黑树大概可以存下2000w
个索引元素
重点(Mysql存储引擎与数据库索引):
注意:Mysql的存储引擎都是形容数据库表
的
MylSAM不支持事务和外键,只有表锁。InnoDB支持事务和外键,支持行锁
MylSAM索引文件和数据文件是分离的(非聚集),在文件夹下每一个数据表都对应两个文件:
InnoDB引擎索引实现(聚簇)(用的最多的存储引擎)
两个文件:frm、ibd,一个是表结构,一个是索引和数据一体文件
表数据文件本身就是一个按照B+树组织的一个索引文件
聚集索引:叶子结点包含了全部数据
为什么InnoDB表必须要有主键,且推荐使用整形的自增主键?
需要主键:引擎就是这么设计的,需要有一个主键用于构建B+树。
使用整形:查找过程中需要大量比较,整形比较的话较快,占用空间相对UUID也小
自增:自增可以保证插入数据只在B+树的后面插入,很少情况出现插入数据导致B+树分裂等平衡操作,可以节省时间
为什么非主键索引结构叶子结点存储的是主键值(一致性和节省存储空间)
事务的隔离级别:(默认的事务隔离级别是可重复读)
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交 read-uncommitted | 是 | 是 | 是 |
读已提交 read-committed | 否 | 是 | 是 |
可重复读 repeatable-read | 否 | 否 | 是 |
串行化 serializable | 否 | 否 | 否 |
读未提交,顾名思义,就是一个事务可以读取另一个未提交事务的数据。(脏读)
读提交,顾名思义,就是一个事务要等另一个事务提交后才能读取数据。(不可重复读)
重复读,就是在开始读取数据(事务开启)时,不再允许修改操作。(解决不可重复读)
Serializable 是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。
不可重复读和幻读的区别(个人理解):
不可重复读:针对于修改同一条数据,会出现前后不一致的情况。****解决方式为添加行锁****
幻读:针对于一批数据,主要体现在新增和删除操作。****解决幻读需要锁整张表****
mysql中,默认的事务隔离级别是可重复读(repeatable-read),为了解决不可重复读,innodb采用了**mvcc(多版本并发控制)**来解决这一问题。mvcc是利用在每条数据后面加了隐藏的两列(创建版本号和删除版本号),类似于Java的CAS乐观锁机制
事务的四大特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)
原子性:
一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么
全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作,这就是事务的原子性
一致性:
数据库总是从一个一致性的状态转换到另一个一致性的状态。(在前面的例子中,一致性确保了,即
使在转账过程中系统崩溃,支票账户中也不会损失200美元,因为事务最终没有提交,所以事务中所做
的修改也不会保存到数据库中。)
隔离性:
通常来说,一个事务所做的修改操作在提交事务之前,对于其他事务来说是不可见的。(在前面的例
子中,当执行完第三条语句、第四条语句还未开始时,此时有另外的一个账户汇总程序开始运行,则
其看到支票帐户的余额并没有被减去200美元。)
持久性:
一旦事务提交,则其所做的修改会永久保存到数据库。
二级索引:叶子节点中存储主键值,每次查找数据时,根据索引找到叶子节点中的主键值,根据主键值再到聚簇索引中得到完整的一行记录。
InnoDB在移动行时,无需维护二级索引,因为叶子节点中存储的是主键值,而不是指针。
那么InnoDB有了聚簇索引,为什么还要有二级索引呢?
聚簇索引的叶子节点存储了一行完整的数据,而二级索引只存储了主键值,相比于聚簇索引,占用的空间要少。当我们需要为表建立多个索引时,如果都是聚簇索引,那将占用大量内存空间,所以InnoDB中主键所建立的是聚簇索引,而唯一索引、普通索引、前缀索引等都是二级索引。
为什么一般情况下,我们建表的时候都会使用一个自增的id来作为我们的主键?
InnoDB中表中的数据是直接存储在主键聚簇索引的叶子节点中的,每插入一条记录,其实都是增加一个叶子节点,如果主键是顺序的,只需要把新增的一条记录存储在上一条记录的后面,当页达到最大填充因子的时候,下一跳记录就会写入新的页中,这种情况下,主键页就会近似于被顺序的记录填满。
若表的主键不是顺序的id,而是无规律数据,比如字符串,InnoDB无法加单的把一行记录插入到索引的最后,而是需要找一个合适的位置(已有数据的中间位置),甚至产生大量的页分裂并且移动大量数据,在寻找合适位置进行插入时,目标页可能不在内存中,这就导致了大量的随机IO操作,影响插入效率。除此之外,大量的页分裂会导致大量的内存碎片。
https://www.cnblogs.com/wupeixuan/p/11626024.html
大体上,MySQL 分为 Server 层和存储引擎层两部分。
Server 层包括连接器、查询缓存、分析器、执行器等,以及所有的内置函数(如日期、时间、数学和加密函数等)和跨存储引擎的功能(如存储过程、触发器、视图)。
存储引擎层负责数据的存储和提取,支持 InnoDB、MyISAM、Memory 等多个存储引擎。MySQL 5.5.5 版本后默认存储存储引擎是 InnoDB。
连接器
在查询 SQL 语句前,肯定要先建立与 MySQL 的连接,这就是由连接器来完成的。连接器负责跟客户端建立连接、获取权限、维持和管理连接
查询缓存
MySQL 拿到查询请求后,会先查询缓存,看是不是执行过这条语句。执行过的语句及其结果会以 key-value 对的形式保存在一定的内存区域中。
分析器
如果查询缓存未命中,就要开始执行语句了。首先,MySQL 需要对 SQL 语句进行解析。
分析器先会做词法分析。SQL 语句是由多个字符串和空格组成的,MySQL 需要识别出里面的字符串分别是什么,代表什么。MySQL 从你输入的 select 这个关键字识别出来,这是查询语句。它也要把字符串 user_info 识别成表名,把字符串 id 识别成列名。之后就要做语法分析。根据词法分析的结果,语法分析器会根据语法规则,判断输入的 SQL 语句是否满足 MySQL 语法。如果你 SQL 语句不对,就会收到 You have an error in your SQL syntax
的错误提醒,比如下面这个语句 from 写成了 form。
优化器
经过分析器的词法分析和语法分析后,还要经过优化器的处理。优化器是在表里面有多个索引的时候,决定使用哪个索引;或者在一个语句有多表关联(join)的时候,决定各个表的连接顺序
执行器
MySQL 通过分析器知道了要做什么,通过优化器知道了该怎么做,于是就进入了执行器阶段,开始执行语句。
总结:主要通过对一个 SQL 语句完整执行过程进行讲解,介绍 MySQL 的逻辑架构,MySQL 主要包括连接器、查询缓存、分析器、优化器、执行器这几个模块。
https://www.cnblogs.com/AbnerLc/p/11923242.html
只需要在一棵索引树上就能获取SQL所需的所有列数据,无需回表,速度更快。
https://www.iteye.com/blog/lixiaohui-2352449
https://blog.csdn.net/djrm11/article/details/96499817
这个要从两个存储引擎的索引结构上面答:
MySQL锁大体分三种:
MySQL中不同的存储引擎之间的锁机制不一定相同,例如MyISAM和MEMORY采用的是表锁,BDB采用的是页面锁,但也支持表锁,InnoDB默认是行锁,但也支持表锁。
(由索引机制决定的)InnoDB之所以可以锁行,是因为Innodb的主索引结构上,既存储了主键值,又直接存储了行数据,可以方便的锁住行数据,而MyIsam索引指向另一片数据文件,没有办法精确锁住数据段
https://zhuanlan.zhihu.com/p/95170837
函数、隐式转换、order by、or、like
EXPLAIN select * from t where month(update_time)=12;
select * from t where id + 1 = 10
select “10” > 9
其实是用到了函数隐式转换
select CAST("10" AS signed int) > 9
原子性:操作这些指令时,要么全部执行成功,要么全部不执行
一致性:事务开始和结束之间的中间状态不会被其他事务看到
隔离性:事务之间不相互干扰,一个事物不知道其他事务的存在
持久性:每一次的事务提交后就会保证不会丢失
refresh()的12个步骤,9处使用到后置处理器的地方,obtainBeanFactory和finshBeanFactoryInitialization是比较重要的方法。
https://www.cnblogs.com/javazhiyin/p/10905294.html
方案:顺序读文件中,对于每个词x,取hash(x)%5000,然后按照该值存到5000个小文件(记为x0,x1,…x4999)中。这样每个文件大概是200M左右。如果其中的有的文件超过了2G大小,还可以按照类似的方法继续往下分,直到分解得到的小文件的大小都不超过2G。 对每个小文件,统计每个文件中出现的词以及相应的频率(可以采用trie树/hash_map等),并取出出现频率最大的100个词(可以用含100个结点的最小堆),并把100个词及相应的频率存入文件,这样又得到了5000个文件。下一步就是把这5000个文件进行归并(类似与归并排序)的过程了。
使用堆排序
首先是这一天,并且是访问百度的日志中的IP取出来,逐个写入到一个大文件中。注意到IP是32位的,最多有个2^32个 IP。同样可以采用映射的方法,比如模1000,把整个大文件映射为1000个小文件,再找出每个小文中出现频率最大的IP(可以采用hash_map进行频率统计,然后再找出频率最大的几个)及相应的频率。然后再在这1000个最大的IP中,找出那个频率最大的IP,即为所求。
**方案1:**采用2-Bitmap(每个数分配2bit,00表示不存在,01表示出现一次,10表示多次,11无意义)进行,共需内存内存,还可以接受。然后扫描这2.5亿个整数,查看Bitmap中相对应位,如果是00变01,01变10,10保持不变。所描完事后,查看bitmap,把对应位是01的整数输出即可。
**方案2:**也可采用与第1题类似的方法,进行划分小文件的方法。然后在小文件中找出不重复的整数,并排序。然后再进行归并,注意去除重复的元素。
与上第6题类似,第一反应时快速排序+二分查找。以下是其它更好的方法:
方案1:申请512M的内存,一个bit位代表一个unsigned int值。读入40亿个数,设置相应的bit位,读入要查询的数,查看相应bit位是否为1,为1表示存在,为0表示不存在。
方案2:这个问题在《编程珠玑》里有很好的描述,大家可以参考下面的思路,探讨一下:又因为2^32为40亿多,所以给定一个数可能在,也可能不在其中;这里我们把40亿个数中的每一个用32位的二进制来表示假设这40亿个数开始放在一个文件中。然后将这40亿个数分成两类: 1.最高位为0 2.最高位为1 并将这两类分别写入到两个文件中,其中一个文件中数的个数<=20亿,而另一个>=20亿(这相当于折半了);与要查找的数的最高位比较并接着进入相应的文件再查找
方案1:这题是考虑时间效率。用trie树统计每个词出现的次数,时间复杂度是O(nle)(le表示单词的平准长度)。然后是找出出现最频繁的前10个词,可以用堆来实现,前面的题中已经讲到了,时间复杂度是O(nlg10)。所以总的时间复杂度,是O(nle)与O(nlg10)中较大的哪一个。
##(六)操作系统、计算机网络
进程:操作系统资源分配和管理的基本单位。
线程:处理机调度的基本单位。进程至少要有一条线程
进程与线程的区别:
地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。
资源拥有:同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的资源是独立的。
一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。
进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程
线程是处理器调度的基本单位,但是进程不是。
**效率问题:**io操作密集型的代码多线程效率更高,因为线程创建要比进程创建开销少。但是计算密集型的代码多 那么进程操作更快,因为多进可以应用多核技术
必要条件:
互斥:资源不共享
占有并等待:—个进程应占有至少一个资源,并等待另一个资源,而该资源为其他进程所占有。
非抢占:资源不能被抢占,即资源只能被进程在完成任务后自愿释放。
循环等待
死锁预防:
死锁避免:银行家算法
死锁检测与解除:在检测到运行系统进入死锁,进行恢复。
PV操作。用于进程间传递信号的一个整数值。在信号量上只有三种操作可以进行:初始化,P操作和V操作,这三种操作都是原子操作。
管程。一种数据结构。一个进程通过调用管程的一个过程进入管程。
消息传递。消息传递的实际功能以一对原语的形式提供:send(destination,message)
receive(source,message)
https://www.cnblogs.com/an-wen/p/11180076.html
各版本差异:https://blog.51cto.com/13932385/2393194
超文本传输协议,是一种应用层协议。HTTP假定其下层协议提供可靠的传输。
HTTP请求方法(8种动作):
Http与Https的区别:
《看书》
1、新建(new):线程对象被创建后就进入了新建状态。如:Thread thread = new Thread();
2、就绪状态(Runnable):也被称为“可执行状态”。线程对象被创建后,其他线程调用了该对象的start()方法,从而启动该线程。如:thread.start(); 处于就绪状态的线程随时可能被CPU调度执行。
3、运行状态(Running):线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权限,暂时停止运行。直到线程进入就绪状态,才有机会进入运行状态。阻塞的三种情况:
1)等待阻塞:通过调用线程的wait()方法,让线程等待某工作的完成。
2)同步阻塞:线程在获取synchronized同步锁失败(因为锁被其他线程占用),它会进入同步阻塞状态。
3)其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或超时、或者I/O处理完毕时,线程重新转入就绪状态。
5、死亡状态(Dead):线程执行完了或因异常退出了run()方法,该线程结束生命周期。
悲观锁总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized
和ReentrantLock
等独占锁就是悲观锁思想的实现。
乐观锁总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。在Java中java.util.concurrent.atomic
包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
https://blog.csdn.net/xlgen157387/article/details/79036337
在分析分布式锁的三种实现方式之前,先了解一下分布式锁应该具备哪些条件:
1、在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行;
2、高可用的获取锁与释放锁;
3、高性能的获取锁与释放锁;
4、具备可重入特性;
5、具备锁失效机制,防止死锁;
6、具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败。
分布式的CAP理论告诉我们“任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。“所以,很多系统在设计之初就要对这三者做出取舍。在互联网领域的绝大多数的场景中,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证“最终一致性”,只要这个最终时间是在用户可以接受的范围内即可。
基于数据库实现分布式锁;
基于缓存(Redis等)实现分布式锁;
基于Zookeeper实现分布式锁;
基于数据库的实现方式
基于数据库的实现方式的核心思想是:在数据库中创建一个表,表中包含方法名等字段,并在方法名字段上创建唯一索引,想要执行某个方法,就使用这个方法名向表中插入数据,成功插入则获取锁,执行完成后删除对应的行数据释放锁。
注意:这只是使用基于数据库的一种方法,使用数据库实现分布式锁还有很多其他的玩法!
基于Redis的实现方式
(1)获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。
(2)获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
(3)释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。
基于ZooKeeper的实现方式
ZooKeeper是一个为分布式应用提供一致性服务的开源组件,它内部是一个分层的文件系统目录树结构,规定同一个目录下只能有一个唯一文件名。基于ZooKeeper实现分布式锁的步骤如下:
https://blog.csdn.net/Dinosaur_1117/article/details/89394219
创建临时顺序结点,避免死锁和惊群效应
①可重入锁:在执行对象中所有同步方法不用再次获得锁
②可中断锁:在等待获取锁过程中可中断
③公平锁:按等待获取锁的线程的等待时间进行获取,等待时间长的具有优先获取锁的权力
④读写锁:对资源读取和写入的时候拆分为2部分处理,读的时候可以多线程一起读,写的时候必须同步的写
java中每个对象都可作为锁,锁有四种级别,按照量级从轻到重分为:无锁,偏向锁,轻量级锁,重量级锁。每个对象一开始都是无锁的,随着线程间争夺锁,越激烈,锁的级别越高,并且锁只能升级不能降级。
锁的实现机制与java对象头息息相关,锁的所有信息都记录在java的对象头中,用2字(32位JVM中1字=32bit)存储对象头,如果是数组类型使用3字存储(还需要存储数组长度),对象头中记录了hash值,GC年龄,锁的状态,线程拥有者,类元数据的指针
https://www.jianshu.com/p/dd09194b120b
synchronized是一种悲观锁,每次都把自己关起来做事,怕被抢而lock底层是CAS乐观锁的体现,无所谓外界,如果被抢了,就重新去拿,很乐观,底层主要靠volatile和CAS实现的
https://www.jianshu.com/p/7e0abbce929c
(集合类失效引用没有及时清除,数据库、IO操作连接等未及时关闭等)
老年代内存无法分配的时候
https://blog.csdn.net/baidu_38083619/article/details/82527461
Synchronized常用三种使用方式
1、修饰普通方法:锁对象即为当前对象
2、修饰静态方法:锁对象为当前Class对象
3、修饰代码块:锁对象为synchronized紧接着的小括号内的对象
递归
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if (l1 == null) {
return l2;
}
else if (l2 == null) {
return l1;
}
else if (l1.val < l2.val) {
l1.next = mergeTwoLists(l1.next, l2);
return l1;
}
else {
l2.next = mergeTwoLists(l1, l2.next);
return l2;
}
}
}
时间复杂度:O(n + m)
空间复杂度:O(n + m)
迭代:
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
// maintain an unchanging reference to node ahead of the return node.
ListNode prehead = new ListNode(-1);
ListNode prev = prehead;
while (l1 != null && l2 != null) {
if (l1.val <= l2.val) {
prev.next = l1;
l1 = l1.next;
} else {
prev.next = l2;
l2 = l2.next;
}
prev = prev.next;
}
// exactly one of l1 and l2 can be non-null at this point, so connect
// the non-null list to the end of the merged list.
prev.next = l1 == null ? l2 : l1;
return prehead.next;
}
}
时间复杂度:O(n + m)
空间复杂度:O(1)