重载:同一个类里,方法名相同,return类型,参数列表可以不同
重写:主要发生在继承里,将父类的方法进行扩展。
String底层被final修饰,所以不可变长,每次改变它,都是创建一个新对象去赋值。StringBuilder和StringBuffer都是可变长的。
String是被final修饰的,不可变,所以安全。
StringBuffer,底层有同步锁修饰方法,线程安全。
StringBuilder,比StringBuffer性能快一点,但是线程不安全。
装箱:将基本类型用它们对应的引用类型包装起来。int->Integer
拆箱:将包装类型转换为基本数据类型。Integer->int
==这个符号,在基本数据类型里比的是值,引用数据类型比的是地址。
equals这个符号,单纯比较内容。
(1)接口默认是public修饰,接口里不能写任何方法的实现,抽象类可以
(2)接口的实例变量的默认final修饰,抽象类不一定
(3)接口可以多实现,抽象类只能单继承
(4)interface一个接口,必须实现所有里面的方法,extends一个抽象类,可以选择性重写
(5)接口不能new,但是可以声明,抽象类声明都不行
(1)二者都是线程不安全
(2)ArrayList底层是Object数组,LinkedList底层是双向链表
(3)动态数组查找快且方便list.get(int index),增删插慢,链表查找慢,但是增删插快
(4)动态数组整体消耗内存大,链表是每一个node占用大
Vector类的所有方法都是同步的,可以由两个线程安全地访问一个Vector对象,但是一个线程访问Vector的话代码要在同步操作上耗费大量的时间。
ArrayList不是同步的,所以不需要保证线程安全时建议使用ArrayList
JDK1.8之前,结构是数组+链表,默认长度是16,16指的是数组的长度,每一个区域存储链表的头结点。HashMap通过key的hashCode经过扰动函数处理得到hash值,通过hash算法得到value。
JDK1.8开始,结构是数组+链表+红黑树,默认长度是16,16指的是数组的长度,每一个区域存储链表的头结点。在某一条链表长度超过8的时候,自动由链表转换成红黑树,减少IO次数,提升性能与效率,当链表长度小于6,再从红黑树转回链表。
HashMap不是线程安全的,因为他的方法不是同步的,所以性能也很高,并且允许在put的时候存放null值。默认容量是16,且在jdk1.8之后有转换成红黑树的机制。HashTable是线程安全的,因为他的方法都是同步的,所以性能很低,但是不允许在put的时候存放null,会引发空指针,默认容量是 11,扩容变2n+1,没有红黑树机制。
hash值范围有40亿个数,存放在内存数量太多,所以进行了取模运算(n-1)&hash
这个问题只发生在jdk1.8之前。我们知道了resize操作,那么在多线程的时候会有可能导致哈希一致性,成为哈希环,且扩容之后,原node不一定还在原位置。
实际上,HashSet是根据HashMap设计的。只不过一个是根据key算hashcode,一个是根据成员对象算hashcode,其他只有set都会有的性能问题的区别了。
从结构上,ConcurrentHashMap在jdk1.8开始有了红黑树的结构,HashTable没有。
从线程安全上,首先这俩都是保证线程安全的,但是保证线程安全的途径不一样。
ConcurrentHashMap在jdk1.4诞生,当时采用的是Segment分段锁,在HashMap的基础上加上锁,由于默认容量为16,所以性能大约是HashTable的16倍。在jdk1.8开始摒弃Segment的概念,使用CAS原子操作保证线程安全(在JDK1.8的时候synchronized已经优化的很好了),而HashTable就是无脑加锁去锁。
synchronized主要是为了我们能够保证进行对某项共享资源,多线程的互斥操作,synchronized分为三类,this锁,synchronized同步块,和用synchronized直接修饰方法。
但是如果我们在循环的场景,就不能用this锁了,因为重复加锁去锁,很浪费性能,用synchronized修饰方法最好。
一般我们是直接用this锁的,因为锁的面积越小,出错的概率就越小。
这个synchronized在刚有的时候,是一个很有重量级的锁,因为它的底层是依赖于操作系统的Mutex Lock,而操作系统在内核态到用户态的互相切换,很浪费性能。在之后的优化,将其性能进行了一定的提升,也正是在这个时候,有了偏斜锁,轻量级锁,重量级锁,锁膨胀的概念,让它在不同的环境有了不同的发挥。
synchronized是一个关键字,在编译的时候会受到操作系统,JVM所影响,而ReentrantLock是.lock包下的一个类。
synchronized的性能一开始不如ReentrantLock,后来因为优化的比较好,出现了无锁,偏斜锁,轻量级锁,重量级锁,和锁膨胀机制之后就比较牛了,超过了。
此外,synchronized有几个功能不如ReentrantLock:
①synchronized不是公平锁,但是ReentrantLock是公平锁,公平锁的意思就是,先来的线程先获得锁,先释放锁。
②synchronized不能实现中断等待,ReentrantLock可以,中断等待,就是让自旋锁这类的,放弃请求。
③当wait的时候,synchronized唤醒锁是靠notify或者notifyAll而且还是单个释放或者全局释放,ReentrantLock靠的是condition进行选择性释放。
降低资源消耗,方便管理线程,提升响应速度
Runnable接口实现之后无法进行返回值,但是Callable实现之后可以返回值
(1)使用构造方法创建
(2)使用Executors实现以下三种ThreadPoolExecutor:
Atomic也是JUC包下的,具备一定的原子操作,原子操作,用白话来说,就是对于一个线程的操作全程,只要是开始了就不会被其他线程干扰,反过来说,就是保证线程安全。
首先这个类也是在JUC的原子类包下的,所以具有着CAS的操作。我们之所以使用CAS就是避免synchronized的高额开销,其原理就是使用CAS+volatile+native。
具体来说,我们的CAS操作,从本质上说,就是使用的Unsafe下的一个native方法获取到原值的地址,去和volatile所修饰的新值(因为volatile保证全局可见直接注入内存)去对比,然后替换旧值,这样就能保证无论怎么样都能获取到最新的值了。
AQS是比较复杂的东西,我仅仅说说我自己的理解,如果有错误欢迎指出。
AQS是JUC包下一个核心的机制,它是这样设计的:我们有一个共享资源池,如果这个池是空闲的,没有被其他线程获取锁,AQS就可以让一个准备获取锁的线程转换为工作线程去执行,然后将其锁定,不被外来线程所干扰;如果这个池已经被占用了,那么就会进行线程的阻塞或者锁的释放,来让请求的线程获得到锁。
而以上AQS的特征是由CLH队列提供的。
CLH队列是一个双向FIFO队列构成的,说简单点,这个队列如同我们想象的那样,肯定里面是有node 的,node也有pre节点和next节点的,我们最终的目的是让AQS通过CLH获取到线程的同步状态,所以这里就把线程设计成了一个node,加入到队列,让线程之间能够排队去获取共享资源。
并且,为了添加线程到队列是安全的,它还使用到了CAS操作,保证开销小,且安全。再通过内置的一个state方法能监视到线程的实际的状态。
此外ReentrantLock,读写锁,信号量,FutureTask都是基于这种机制。
上面已经说了,我们会使用CLH的队列线程,去访问可能空闲的资源然后绑定,而AQS对于资源的理解,也是有两种方式的:
(1)Exclusive独占:只有一个线程可以去执行,比如ReentrantLock。
(2)Share共享:可以多个线程共同访问,比如CyclicBarrier CountDownLatch 读写锁…
不同的自定义同步器争用共享资源的方式也不同。自定义同步器实现时只需要定义状态的获取和释放,不用维护CLH。
再用口述几个AQS的经典组件:
和Synchronized的区别在于,信号量指定了一个共享区域允许多个线程共同访问。
二者的情况其实是差不多的,有一个主线程和若干子线程访问资源的时候,我们可以设置屏障,CountDownLatch的作用是,当左右子线程都经过屏障了之后,主线程,才能从屏障的位置继续执行,也就是被阻塞了,但是子线程不受影响。而CyclicBarrier则是子线程也会受到影响,也就是被阻塞,等子线程都到达了屏障之后,才能与主线程一起重新开始运行。
在Java内存区域中有Java虚拟机栈,堆,程序计数器,本地方法栈,方法区。
其中,线程共享的是堆,方法区,而线程私有的是程序计数器,Java虚拟机栈,本地方法栈。
然后简单介绍一下它们的职责。
Java虚拟机栈里有栈帧,每当一个方法被执行就会产生栈帧,当一个方法被垃圾回收了栈帧就会出栈,所以也是线程私有的,当方法太多了,栈帧就会爆炸,然后oom,但是在爆炸之前,会尝试申请一个新的内存区域防止oom,如果还不够就oom
相对占用较少的内存,并且是线程私有的,目的是通过计数器给线程指引下一个所要执行的字节码指令,本质上属于逻辑计数器,并且只给java方法使用,且不会出现oom
存储所有方法的实例,是JVM里面最大的地方,也是多线程相互共享的一个区域,可以在堆里面进行变量的更新,进行线程的通讯,并且jdk8采用分代回收算法进行垃圾回收,当内存不够,会oom
native
也可以叫元数据区,存储方法的常量,静态变量方法,也是线程共享的,实际上也是存储元数据信息。
标记算法:
当一个方法被引用了,进栈,计数器+1,出栈,计数器-1,计数器=0,垃圾回收。
以GC ROOTS为根,将可以调用到的方法串成一条链,不在链上的就回收。
回收算法
用可达性分析标记,不可达清除,如果不额外整理,会产生内存碎片。
将堆里面的内存按照比例分成对象面和空闲面,如果对象面的内存满了,就把还活着的放到控线面不断循环,缺点就是,不好划分实际比例,优点就是没碎片,适用于成活率不高的对象。
在标记-清除的基础上,使用更多的性能,将对象平移,把内存碎片的坑填上
Minor GC:
新生代GC主要用复制算法,因为成活率不高,在新生代的内存区域,又分为Eden区和Survivor区,二者初始大小比例是8:1,复制就是基于这两者进行的。
当经历15代,或者Eden Survivor炸了,或者产生了巨大的对象,在这三种情况下,会进入老年代。
Full GC:
老年代GC主要用标记清除或者标记整理算法。执行效率比Minor GC慢。
新生代垃圾回收器:
老年代垃圾回收器:
29 用消息队列的好处
主要是异步处理操作提升性能和解耦。
举一个关于异步的简单例子大量数据,实时加载进模型进行数据分析,并且将分析的结果回传给前端去展示,如果是同步的情况,就会很慢,但是异步就可以边加载边计算边展示。
在前后端开发的时候,如果用户的数据直接写入数据库,就会有很大压力,如果我们用消息队列,异步写入数据库,也就不会崩溃了,而且响应快,所以效率也就提升了。但我们也需要考虑其他特殊的因素,比如进入消息队列了,但是从消息队列入库失败了,这就会导致,如果立刻return数据,就会出bug,所以,应该等入库之后再return
还有一点就是解耦,我们将整个项目的资源和使用进程分为消息的发布和订阅模式,就可以让我们在该使用某种数据了之后再去订阅获取。
30 常见的消息队列对比
系统的可用性降低:引用多用了一种中间价去优化,所以有可能出bug然后就炸了
系统的复杂性提高:我们用消息队列就是为了能让数据不爆炸式的读写,但是如果出现了数据丢失,就是另一回事了
系统一致性问题:分布式环境下,如果消费者没有被真正消费的消息没有同步就会出问题
(1)就如我们上面所说的那样,有可能消费者还没有真正消费,数据就消失的情况,所以我们这里可以引用事务的概念,当程序真正运行完成之后,我们才去提交任务的完成,不然我们就将任务去进行回滚。
(2)可以将没持久消息同步到磁盘上并且当broker宕机后可以去恢复
(3)使用发布方确认,比如我们的消息已经到了broker,但是没有添加进队列,这时候宕机了,就会无法保持一致性。
我能想到的有两种方法,第一种就是用redis的set操作,保证所有操作的唯一性;第二种就是为消息指令添加主键,保证主键唯一,不脏读。
(1) redis支持更丰富的数据类型(支持更复杂的应用场景):Redis不仅仅支持简单的kv,还有list set zset hash等数据结构的存储。memcached只支持string
(2)redis支持持久化,memchached不支持
(3)redis有分布式集群一说,memcached只有客户端分布式一说
(4)memchached是使用多线程,非阻塞io的网络模型;Redis是单线程多路复用的IO模型
从redis.conf配置即可,共有6种方案:
(1)volatile-lru:从已设置过期时间的数据集中挑选最近少使用的数据淘汰
(2)从已设置过期施加你的数据集中挑选要过期的数据淘汰
(3)从已设置过期时间的数据集中任意选择数据淘汰
(4)当内存不足以容纳新写入数据的时候,在键空间中,移除最近最少使用的key
(5)从数据集中任意选择数据淘汰
(6)禁止驱逐数据,也就是当内存不足以容纳新写入数据的时候,新写入数据回报错。
RDB 建立快照,直接复制
AOF appends log
默认使用RDB,也可以结合使用
缓存穿透:绕过缓存,拼命访问数据库。
有两种方法:第一种是把不存在的请求存入缓存,但是过期时间设置短一点。第二种是用位图存储所有可能的请求数据做拦截器
缓存雪崩:大面积缓存过期。
解决方法有三种:第一种是选择缓存持久化快速回复缓存,但是这个方法在恢复的过程中无法存入新的缓存。第二种是本地ehcache缓存+hystrix分布式缓存降级第三种是HA。
也就是说,多个节点同时对一个key进行操作,最后导致指令重排。
有两种处理方法,但是都是基于分布式锁的。
第一种是使用redis自带的分布式锁,但是会对系统造成一定的性能问题,所以如果没有这种并发竞争key的问题存在,就不要用。
第二种就是使用zk,zk是一种高可靠的资源管理框架。它可以在某个方法上了锁之后,对访问方法的结点进行一个唯一的瞬时有效的数字,然后根据数字去排序获取锁,最后完成业务再释放锁。
串行化读写
40 简单介绍一下你理解的Nginx
它也是一款轻量级的web服务器,主要功能是反向代理,负载均衡,弹性收缩等服务。简单介绍下
首先先说说正向代理,某些情况下,我们需要用户去访问服务器,需要手动设置好服务器的ip和端口,可以拿学校的内网举例,我们想从家连接到学校内网服务器,就需要经历学校才可以。
而反向代理不是给我们代理的,而是代理服务器的,也就是说,我们不需要去和内网服务器去交互,而是直接和代理服务器交互,我们将http请求发给他,他再转发给内网服务器,然后得到结果之后再返回给客户端。
我们应该在学zk,Ribbon的时候就知道这个了,有很多种负载均衡算法,说白了就是分担http请求平均到每一台服务器。
其实这一点是很常用的,如果我们每一次都去加载网页的静态资源,很浪费服务器资源,所以就可以让nginx做到将静态部分的页面缓存起来。
(1)高并发,高性能(其他web服务器不具有)
(2)可以横向扩展(模块化设计,第三方插件齐全)
(3)高可靠性(可以持续运行)
(4)热部署
(5)BSD许可(可以修改源代码,发布我们自定义的nginx使用)
(1)Nginx二进制可执行文件:由各个模块源码编译出一个文件
(2)Nginx.conf配置文件:控制Nginx行为
(3)access.log:记录所有的HTTP请求
(4)error.log:记录bug
三次握手
四次挥手
https://blog.csdn.net/qq_41936805/article/details/103499343
http1.0默认使用短连接,也就是每进行一个http操作就发送一次请求,建立一次连接,任务结束就断开连接,这样太耗资源了。
http1.1开始默认使用长连接,也就是说前后端用于传递http请求数据的tcp连接不会中断。
①count运算上的区别:因为MyISAM有meta-data,所以全量查询很快,但是INNODB没有
②事务与安全:MyISAM性能会更快,但是不支持事务,INNODB支持事务,所以也支持事务相关的,隔离,回滚,等等的ACID操作
③MyISAM不支持外键,INNODB支持
mysql的索引可以按照一定的顺序引用多列,这样就可以称之为联合索引。而最左前缀原则指的就是,如果查询的时候查询条件能精确匹配到所有列,就可以返回值。如果匹配顺序不同也可以,但是如果匹配项少,就匹配不到了。
由于最左前缀原则,在创建联合索引的时候,索引字段的顺序需要考虑字段值去重之后的个数,较多的放在前面。
但是如果使用联合索引,就需要避免冗余索引,因为在大多数情况下能够命中(name)的也能命中(name,age),也就是少的能命中多的。所以应该尽量扩展已有的索引而不是创建新索引。
首先这两者都是m叉查找树,二者都是为了减少IO次数,而被制作出来,去搜索数据的,因为IO对于资源消耗很多,所以为了减少消耗资源,提升速度,就有了这两者,但是也是有显著的区别的。
①B+树比B树更矮,所以IO次数更少
②B+树的查询效率更加高
③B+树更加有利于对数据库进行扫描
④B+树的非叶子节点不存数据,只充当索引,而B树非根子节点都会存储数据
①限定数据的范围:对于where条件,一定要有所指定,最好不要全量查询。
②读写分离:分库之后,主库负责写,从库负责读
③垂直分区:根据数据库里面的数据表按照相关性拆分。也就是列的拆分。
④水平分区:保持基本的数据结构,对数据分片,然后切分到不同的数据库或者表,可理解为分布式,这样改动比较简单,但是分库才能体现。只不过在写事务的时候会很复杂。最好在客户端进行拆分。
首先,事务具有四大特性:ACID
原子性:要么全做,要么全不做。
一致性:执行事务前后,数据保持一致。
隔离性:事务是单独执行的,不会被其他事务干扰。
持久性:一个事务被提交之后,改变的持久的。
sql标准定义了四个隔离级别:
①read-uncommitted(读未提交):会造成脏读,幻读,不可重复读
②read-committed(读已提交):会造成幻读和不可重复读
③repeatable-read(可重复读):会造成幻读,是mysql的默认隔离级别.
④serializable(可串行化):什么都不会发生
详情也可以看这篇文章:https://blog.csdn.net/qq_41936805/article/details/103175124