java知识点整理

一:网络编程

HTPT1 与HTTP2的主要区别

http1.0:默认长连接,支持发送header信息,http1.1支持Host域

http2.0:

  1. 多路复用的技术,做到同一个连接并发处理多个请求,JAVA NIO是非阻塞的,同时实现了IO多路复用。NIO中用户线程不会被读写操作阻塞住,它可以继续干事情,所以NIO是可以做到用一个线程来处理多个操作的。假设有10000个请求过来,根据实际情况,可以分配50或者100个线程来处理。不像之前的阻塞IO那样,非得分配10000个。
  2. HTTP2.0使用HPACK算法对header的数据进行压缩,
  3. 服务器会顺便把一些客户端需要的资源一起推送到客户端,免得客户端再次创建连接发送请求到服务器端获取。这种方式非常合适加载静态资源。

TCPIP模型与协议

  1. 应用层:单位是数据段,协议有FTP、TELNET、HTTP、SMTP、SNMP、TFTP、NTP、DNS,负责管理主机之间的会话进程,负责建立、管理、终止进程之间的会话
  2.  运输层:单位是数据包,协议有TCP、UDP,将上层数据分段并提供端到端的、可靠的或不可靠的传输,还要处理端到端的差错控制和流量控制问题
  3.  网络层:单位是数据帧,协议有IP、IPX、RIP、OSPF,对子网间的数据包进行路由选择。此外,网络层还可以实现拥塞控制、网际互连等功能
  4.  网络接口层:单位是比特,ARP、RARP,数据链路层:在不可靠的物理介质上提供可靠的传输。该层的作用包括:物理地址寻址、数据的成帧、流量控制、数据的检错、重发等。

BIO NIO AIO

  1. BIO:同步阻塞IO,每个请求都要一个线程来处理。
  2. NIO:同步非阻塞IO,一个线程可以处理多个请求,适用于短连接、小数据。
  3. AIO:异步非阻塞IO,一个线程处理多个请求,使用回调函数实现,适用于长连接、大数据。

RPC与HTTP服务的区别

  1. RPC(即Remote Procedure Call,远程过程调用)和HTTP(HyperText Transfer Protocol,超文本传输协议)他们最本质的区别,就是RPC主要工作在TCP协议之上,而HTTP服务主要是工作在HTTP协议之上,我们都知道HTTP协议是在传输层协议TCP之上的,所以效率来看的话,RPC当然是要更胜一筹。
  2. RPC服务主要是针对大型企业的,而HTTP服务主要是针对小企业的,因为RPC效率更高,而HTTP服务开发迭代会更快。
  3. 简单来说一个成熟的RPC库相对于http容器更多的是封装了“服务发现”,"错误重试"一类面向服务的高级特性。可以这么理解,rpc框架是面向服务的更高级的封装。如果把一个http server容器上封装一层服务发现和函数代理调用,那它就已经可以做一个rpc框架了。

Tcp三次握手,四次挥手

 

HTTPS和HTTP的区别主要如下

  1. https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。
  2. http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
  3. http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
  4. http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

加密方式

  1. 对称加密:加密和解密都是同一个密匙。
  2. 非对称加密:密钥成对出现,分为公钥和私钥,公钥加密需要私钥解密,私钥加密需要公钥解密。

两者区别

  1. 对称加密速度快,非对称加密速度慢。
  2. 对称加密要将密钥暴露,和明文传输没区别。
  3. 非对称加密将公钥暴露,供客户端加密,服务端使用私钥解密。

一、Nginx负载均衡算法

  1.     轮询(默认):每个请求按时间顺序逐一分配到不同的后端服务,如果后端某台服务器死机,自动剔除故障系统,使用户访问不受影响。
  2. weight(轮询权值): weight的值越大分配到的访问概率越高,主要用于后端每台服务器性能不均衡的情况下。或者仅仅为在主从的情况下设置不同的权值,达到合理有效的地利用主机资源。
  3. ip_hash: 每个请求按访问IP的哈希结果分配,使来自同一个IP的访客固定访问一台后端服务器,并且可以有效解决动态网页存在的session共享问题。
  4. fair:比 weight、ip_hash更加智能的负载均衡算法,fair算法可以根据页面大小和加载时间长短智能地进行负载均衡,也就是根据后端服务器的响应时间 来分配请求,响应时间短的优先分配。Nginx本身不支持fair,如果需要这种调度算法,则必须安装upstream_fair模块。
  5. url_hash: 按访问的URL的哈希结果来分配请求,使每个URL定向到一台后端服务器,可以进一步提高后端缓存服务器的效率。Nginx本身不支持url_hash,如果需要这种调度算法,则必须安装Nginx的hash软件包。

 

二:数据库原理

数据库遇到性能问题如何处理,

(1) 硬件提升:容量不足,磁盘不足,使用share memory

(2) 软件提升:性能不够,可以使用分库,分表,读写分离

(3)分布式部署:一个事务,两个数据库联动。使用分布式事务。 

MYISAM与innodb搜索引擎原理

  1. MyISAM引擎使用B+Tree作为索引结构,叶节点的data域存放的是数据记录的地址。其采用索引文件与数据文件,索引文件只存放索引,叶子节点存放数据的物理地址。数据文件存放数据。其索引方式是非聚集的。
  2.  InnoDB也使用B+Tree作为索引结构。但是它的主索引与数据都放在一个文件中。这种索引叫做聚集索引,因为InnoDB的数据文件本身要按主键聚集,所以InnoDB要求表必须有主键(MyISAM可以没有),如果没有显式指定,则MySQL系统会自动选择一个可以唯一标识数据记录的列作为主键,如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整形。
  3. 区别一:InnoDB的主索引与数据都放在一个文件中。而MYISAM是分开存放的。
  4.  区别二:InnoDB的辅助索引data域存储相应记录主键的值而不是地址。
  5.  区别三:InnoDB的主键索引是聚集索引,而MYISAM不是聚集索引。

索引类型

  1. 聚集(clustered)索引,也叫聚簇索引。数据行的物理顺序与列值(一般是主键的那一列)的逻辑顺序相同,一个表中只能拥有一个聚集索引。
  2.  非聚集(unclustered)索引。该索引中索引的逻辑顺序与磁盘上行的物理存储顺序不同,一个表中可以拥有多个非聚集索引。会发生二次查询。
  3.  稠密索引:稠密索引文件中的索引块保持键的顺序与文件中的排序顺序一致。
  4.  稀疏索引:稀疏索引没有为每个数据都创建一个索引,它比稠密索引节省了更多的存储空间,但查找给定值的记录需更多的时间。只有当数据文件是按照某个查找键排序时,在该查找键上建立的稀疏索引才能被使用,而稠密索引则可以应用在任何的查找键。
  5.  联合索引:将一张表中多个列组成联合索引(col1,col2,col3),其生效方式满足最左前缀原则。
  6. 覆盖索引:对于二级索引而言,在innodb中一般是需要先根据二级索引查询到主键,然后在根据一级索引查询到数据。但是如果select的列都在索引中,就避免进行一级查询。

索引的好处

  1. 提高数据检索的效率,降低检索过程中必须要读取得数据量,降低数据库IO成本。
  1. 降低数据库的排序成本。因为索引就是对字段数据进行排序后存储的,如果待排序的字段与索引键字段一致,就在取出数据后不用再次排序了,因为通过索引取得的数据已满足排序要求。另外,分组操作是先排序后分组,所以索引同样可以省略分组的排序操作,降低内存与CPU资源的消耗。

索引的弊端

  1. 索引会增加 增、删、改操作所带来的IO量与调整索引的计算量。
  2. 索引要占用空间,随着数据量的不断增大,索引还会带来存储空间的消耗。

判断是否应该建索引的条件

  1. 较频繁的作为查询条件的字段应该创建索引
  2. 唯一性太差的字段不适合单独创建索引,即使频繁作为查询条件
  3. 增、删、改操作较多的数据库字段不适合建索引

 

主键选择

  •  在使用InnoDB存储引擎时,如果没有特别的需要,请永远使用一个与业务无关的自增字段作为主键。
  •  where 1 = 1:能够方便我们拼sql,但是使用了之后就无法使用索引优化策略,因此会进行全表扫描,影响效率。、

分表分库

  1. 水平拆分:依据表中的数据逻辑关系,将同一个表中的数据依照某种条件拆分到多台数据库上面,按照1个或多个字段以及相应的规则将一张表的数据分到多张表中去。比如安装ID%5的规则,将一张大表的数据拆分成5张小表,适合具有超大表的系统
  2. 垂直查分:依照不同的表或业务来切分到不同的数据库之上。适合业务之间耦合度非常低的系统

事务的四大特性:

  1. 原子性(Atomicity):原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚
  2. 一致性(Consistency):一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
  3. 隔离性(Isolation):隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
  4. 持久性(Durability): 持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。

隔离级别

  •  read uncommit(读未提交):读不加锁,写加共享锁。会产生脏读、幻读。
  • read commit(读已提交):读加共享锁,写加排它锁,但不加间隙锁。间隙锁的主要作用是防止不可重复读,但会加大锁的范围。
  • repeatable read(可重复读):innodb默认,读加共享锁,写加间隙排它锁。会产生幻读,当事务A先前读取了数据,或者修改了全部数据,事务B还是可以insert数据提交,这时事务A就会发现莫名其妙多了一条之前没有的数据,这就是幻读,读用读锁,写用写锁,读锁和写锁互斥,这么做可以有效的避免幻读、不可重复读、脏读等问题,但会极大的降低数据库的并发能力。但是MySQL、ORACLE、PostgreSQL等成熟的数据库,出于性能考虑,都是使用了以乐观锁为理论基础的MVCC(多版本并发控制)来实现
  • serialization(串行化):会给整张表加锁,强一致,但是效率低
事务隔离级别脏读不可重复读幻读
读未提交(read-uncommitted)
不可重复读(read-committed)
可重复读(repeatable-read)
串行化(serializable)

MVCC多版本并发控制技术

 MVCC(multi-Version Concurrency Control):即多版本并发控制技术,它使得大部分支持行锁的事务引擎,不再单纯的使用行锁来进行数据库的并发控制,取而代之的是把数据库的行锁与行的多个版本结合起来,只需要很小的开销,就可以实现非锁定读,从而大大提高数据库系统的并发性能,InnoDB的MVCC,通过在每行记录后面保存两个隐藏的列来实现:一个保存了行的创建时间,一个保存行的过期时间(删除时间),当然,这里的时间并不是时间戳,而是系统版本号,每开始一个新的事务,系统版本号就会递增。在RR隔离级别下,MVCC的操作如下:

  1. select操作。a. InnoDB只查找版本早于(包含等于)当前事务版本的数据行。可以确保事务读取的行,要么是事务开始前就已存在,或者事务自身插入或修改的记录。b. 行的删除版本要么未定义,要么大于当前事务版本号。可以确保事务读取的行,在事务开始之前未删除。
  2. insert操作。将新插入的行保存当前版本号为行版本号。
  3. delete操作。将删除的行保存当前版本号为删除标识。
  4. update操作。变为insert和delete操作的组合,insert的行保存当前版本号为行版本号,delete则保存当前版本号到原来的行作为删除标识。

事务的二段提交机制(2PC)

  1. 请求阶段(commit-request phase,或称表决阶段,voting phase)在请求阶段,协调者将通知事务参与者准备提交或取消事务,然后进入表决过程。在表决过程中,参与者将告知协调者自己的决策:同意(事务参与者本地作业执行成功)或取消(本地作业执行故障)

  2. 提交阶段(commit phase)在该阶段,协调者将基于第一个阶段的投票结果进行决策:提交或取消。当且仅当所有的参与者同意提交事务协调者才通知所有的参与者提交事务,否则协调者将通知所有的参与者取消事务。参与者在接收到协调者发来的消息后将执行响应的操作。

两阶段提交的缺点

  1. 同步阻塞问题。执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。
  2. 单点故障。由于协调者的重要性,一旦协调者发生故障。参与者会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)
  3. 数据不一致。在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这回导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据部一致性的现象。

两阶段提交无法解决的问题

当协调者出错,同时参与者也出错时,两阶段无法保证事务执行的完整性。
考虑协调者再发出commit消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。
那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交。

事务的三段提交机制(3PC)

  1. CanCommit阶段:3PC的CanCommit阶段其实和2PC的准备阶段很像。协调者向参与者发送commit请求,参与者如果可以提交就返回Yes响应,否则返回No响应。
  2. PreCommit阶段:Coordinator根据Cohort的反应情况来决定是否可以继续事务的PreCommit操作。
    根据响应情况,有以下两种可能。
    1. A.假如Coordinator从所有的Cohort获得的反馈都是Yes响应,那么就会进行事务的预执行:发送预提交请求。Coordinator向Cohort发送PreCommit请求,并进入Prepared阶段。事务预提交。Cohort接收到PreCommit请求后,会执行事务操作,并将undo和redo信息记录到事务日志中。响应反馈。如果Cohort成功的执行了事务操作,则返回ACK响应,同时开始等待最终指令。
    2. 假如有任何一个Cohort向Coordinator发送了No响应,或者等待超时之后,Coordinator都没有接到Cohort的响应,那么就中断事务:发送中断请求。Coordinator向所有Cohort发送abort请求。中断事务。Cohort收到来自Coordinator的abort请求之后(或超时之后,仍未收到Cohort的请求),执行事务的中断。
  3. DoCommit阶段:该阶段进行真正的事务提交,也可以分为以下两种情况:
    1. 执行提交
      1. 发送提交请求。Coordinator接收到Cohort发送的ACK响应,那么他将从预提交状态进入到提交状态。并向所有Cohort发送doCommit请求。
      2. 事务提交。Cohort接收到doCommit请求之后,执行正式的事务提交。并在完成事务提交之后释放所有事务资源。
      3. 响应反馈。事务提交完之后,向Coordinator发送ACK响应。
      4. 完成事务。Coordinator接收到所有Cohort的ACK响应之后,完成事务。
    2. 中断事务:Coordinator没有接收到Cohort发送的ACK响应(可能是接受者发送的不是ACK响应,也可能响应超时),那么就会执行中断事务。

三阶段提交协议和两阶段提交协议的不同

对于协调者(Coordinator)和参与者(Cohort)都设置了超时机制(在2PC中,只有协调者拥有超时机制,即如果在一定时间内没有收到cohort的消息则默认失败)。
在2PC的准备阶段和提交阶段之间,插入预提交阶段,使3PC拥有CanCommit、PreCommit、DoCommit三个阶段。
PreCommit是一个缓冲,保证了在最后提交阶段之前各参与节点的状态是一致的。

三阶段提交协议的缺点

如果进入PreCommit后,Coordinator发出的是abort请求,假设只有一个Cohort收到并进行了abort操作,
而其他对于系统状态未知的Cohort会根据3PC选择继续Commit,此时系统状态发生不一致性。

BASE是指基本可用(Basically Available)、软状态( Soft State)、最终一致性( Eventual Consistency)。

  • 基本可用(Basically Available) 
    基本可用是指分布式系统在出现故障的时候,允许损失部分可用性,即保证核心可用。 
    电商大促时,为了应对访问量激增,部分用户可能会被引导到降级页面,服务层也可能只提供降级服务。这就是损失部分可用性的体现。

  • 软状态( Soft State) 
    软状态是指允许系统存在中间状态,而该中间状态不会影响系统整体可用性。分布式存储中一般一份数据至少会有三个副本,允许不同节点间副本同步的延时就是软状态的体现。mysql replication的异步复制也是一种体现。

  • 最终一致性( Eventual Consistency) 
    最终一致性是指系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。弱一致性和强一致性相反,最终一致性是弱一致性的一种特殊情况。

BASE模型是传统ACID模型的反面,不同与ACID,BASE强调牺牲高一致性,从而获得可用性,数据允许在一段时间内的不一致,只要保证最终一致就可以了

ACID模型

ACID是传统数据库常用的设计理念,追求强一致性模型。 
关系数据库的ACID模型拥有 高一致性 + 可用性 很难进行分区: 
Atomicity原子性:一个事务中所有操作都必须全部完成,要么全部不完成。 
Consistency一致性. 在事务开始或结束时,数据库应该在一致状态。 
Isolation隔离层. 事务将假定只有它自己在操作数据库,彼此不知晓。 
Durability. 一旦事务完成,就不能返回。

ACID模型要求一个事物必须满足上面的四点,这是对关系型传统数据库的指导性依据。而非关系型数据库NoSql则不再依赖这一模型。

CAP理论

2000年7月,加州大学伯克利分校的Eric Brewer教授在ACM PODC会议上提出CAP猜想。2年后,麻省理工学院的Seth Gilbert和Nancy Lynch从理论上证明了CAP。之后,CAP理论正式成为分布式计算领域的公认定理。

CAP理论为: 
一个分布式系统最多只能同时满足

  • Consistency(一致性), 数据一致更新,所有数据变动都是同步的
  • Availability(可用性), 好的响应性能
  • Partition tolerance(分区容错性) 可靠性

 join原理

  1. Simple Nested-Loop Join:效率最低,按照join的次序,在join的属性上一个个扫描,并合并结果。
  2. Index Nested-Loop Join:效率最高,join的属性上面有索引,根据索引来匹配
  3.  Block Nested-Loop Join:用于没有索引的列。它会采用join buffer,将外表的值缓存到join buffer中,然后与内表进行批量比较,这样可以降低对外表的访问频率

面试题

系统允许一段时间,数据量已经很大,这时候系统升级,有张表A需要增加个字段,并发量百天晚上都很大,请问在, 修改表结构。

考点:修改表结构会导致表锁,数据量大修改数据时间很长会导致用户读锁,无法访问!

答:

  1.  首先常见一个和你要执行alter操作的表一样的空的表结构
  2. 执行我们赋予的表结构的修改,然后copy源表中的数据到新表中来
  3. 在源表上创建一个触发器,在数据copy的过程中,将原表的更新数据的操作全部更新到新表中来。
  4. copy完成后,用rename table新表代替原表

 MyISAM引擎与InnoDB的区别(百度面试):

1、MyIASM是非事务安全的,不支持事务,而InnoDB是事务安全的,支持事务这是最重要的区别(百度面试)

2、MyIASM锁的粒度是表级的,而InnoDB支持行级锁

3、MyIASM支持全文类型索引,而InnoDB不支持全文索引

4、MyIASM相对简单,效率上要优于InnoDB,小型应用可以考虑使用MyIASM

5、MyIASM表保存成文件形式,跨平台使用更加方便 

三:多线程

 

线程池潜在宕机风险

使用Executors来创建要注意潜在宕机风险.其返回的线程池对象的弊端如下: FixedThreadPool和SingleThreadPoolPool : 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM. CachedThreadPool和ScheduledThreadPool : 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM.

综上所述, 在可能有大量请求的线程池场景中, 更推荐自定义ThreadPoolExecutor来创建线程池, 具体构造函数配置见下文.

线程池大小配置

一般根据任务类型进行区分, 假设CPU为N核 CPU密集型任务需要减少线程数量, 降低线程之间切换造成的开销, 可配置线程池大小为N + 1. IO密集型任务则可以加大线程数量, 可配置线程池大小为 N * 2. 混合型任务则可以拆分为CPU密集型与IO密集型, 独立配置.

线程安全问题

  • 多个线程同时访问一个共享资源时,会导致程序运行结果并不是想看到的结果不过,当多个线程执行一个方法,方法内部的局部变量并不是共享资源,因为方法是在栈上执行的,而Java栈是线程私有的,因此不会产生线程安全问题。
  • 在Java中,提供了两种方式来实现同步互斥访问:synchronized和Lock


synchronized

  • 是java内置的一种互斥锁,如果对临界资源加上互斥锁,当一个线程在访问该临界资源时,其他线程便只能等待    
  • 在Java中,可以使用synchronized关键字来标记一个方法或者代码块,当某个线程调用该对象的synchronized方法或者访问synchronized代码块时,这个线程便获得了该对象的锁,其他线程暂时无法访问这个方法,只有等待这个方法执行完毕或者代码块执行完毕,这个线程才会释放该对象的锁,其他线程才能执行这个方法或者代码块。

 

  • 实现原理:synchronized是基于Monitor来实现同步的,从反编译获得的字节码可以看出,synchronized代码块实际上多了monitorenter(监控进入)和monitorexit(监控退出)两条指令。monitorenter指令执行时会让对象的锁计数加1,而monitorexit指令执行时会让对象的锁计数减1,对于synchronized方法,执行中的线程识别该方法的 method_info 结构是否有 ACC_SYNCHRONIZED 锁标记设置,然后它自动获取对象的锁,调用方法,最后释放锁。如果有异常发生,线程自动释放锁。
  • synchronized的锁优化:JavaSE1.6为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”。
    在JavaSE1.6中,锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级,而且锁只能升级不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率。

  1. 偏向锁:只有一个线程获得了锁进入偏向模式,每一次访问这个锁的同步代码块时都不需要再进行同步操作,除非有其他线程访问这个锁
  2. 轻量级锁:   相对于偏向锁,当多个线程访问锁时偏向锁就升级为了轻量级锁,轻量级锁的思想是当多个线程进入同步代码块后,多个线程未发生竞争时一直保持轻量级锁,如果发生竞争,首先会采用CAS自旋操作来获取锁,自旋在极短时间内发生,有固定的自旋次数,一旦自旋获取失败,则升级为重量级锁
  3. 重量级锁:线程间进行阻塞

 

Lock
synchronized缺点:

  1. 那么如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待
  2. 如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程只能等待无法进行读操作

    通过Lock就可以办到

AQS

Lock的两个常见的锁都是基于它来实现的

  • AQS其实就是一个可以给我们实现锁的框架,并发包中很多可阻塞的类都是基于AQS构建的
  • 内部实现的关键是:先进先出的队列、state状态,子类只要重写部分的代码即可实现(大量用到了模板代码)
  • 定义了内部类ConditionObject
  • 拥有两种线程模式

    • 独占模式
    • 共享模式
  • 一般我们叫AQS为同步器


locks包下常用的类:

  1. ReentrantLock可重入锁
  2. ReentrantReadWriteLock读写锁:一个用来获取读锁,一个用来获取写锁。也就是说将文件的读写操作分开,分成2个锁来分配给线程,从而使得多个线程可以同时进行读操作

锁的相关概念介绍

  1. 可重入锁:基于线程的分配,如果当前锁住的方法中需要调用别的方法时,线程不必重新去申请锁,而是可以直接执行方法
  2. 可中断锁:如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁
  3. 公平锁: 如果在时间上,先对锁进行获取的请求,一定先被满足,这个锁就是公平的,不满足,就是非公平的,非公平的效率一般来讲更高在Java中,synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序,而对于ReentrantLock和ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁。
  4. 读写锁:读写锁将对一个资源(比如文件)的访问分成了2个锁,一个读锁和一个写锁。
  5. 总结来说,Lock和synchronized有以下几点不同:
    1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
    2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
    3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
    4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
    5)Lock可以提高多个线程进行读操作的效率。

CAS

CAS,在Java并发应用中通常指CompareAndSwap或CompareAndSet,即比较并交换。

  1. CAS是一个原子操作,在修个一个内存位置的值时,它会比较这个内存位置的值与原理的值是否相等,只有相等的情况下才会修个成,保证值总是最新的,如果有其他线程在这期间修改了这个值则CAS失败。
  2. JVM中的CAS操作是利用了处理器提供的CMPXCHG指令实现的。

 

死锁

死锁是指当一个线程永远地持有一个锁,并且其他线程都尝试获得这个锁时,那么它们将永远被阻塞。比如,线程1已经持有了A锁并想要获得B锁的同时,线程2持有B锁并尝试获取A锁,那么这两个线程将永远地等待下去

如何排查和检查死锁

可以使用JDK自带的 一些工具jps ,jstack,jconsole,jvisualvm

  1. 使用top命令查看cpu占用资源较高的PID
  2. 通过jps 找到当前用户下的java程序PID
  3. 使用 pidstat -p 找到cpu占用较高的线程TID
  4. 将TID转换为十六进制的表示方式,通过jstack -l 输出当前PID的线程dunp信息
  5. 查找 TID对应的线程(输出的线程id为十六进制),找到对应的代码

 

如何避免死锁

  1. 避免一个线程同时获取多个锁
  2. 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源
  3. 尝试使用定时锁,使用lock.tryLock来代替使用内置锁

ConcurrentHashMap

  • 基于分度锁实现:假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问
  • ConcurrentHashMap 参考:ConcurrentHashMap实现原理

线程池

  •  减少在创建和销毁线程上所花的时间以及系统资源的开销 。当前任务与主线程隔离,能实现和主线程的异步执行,特别是很多可以分开重复执行的任务。
  • 线程池总会创建指定个数的线程,当线程池的可用线程数不足时,则将线程任务放入阻塞队列,如果线程的的可以线程不足,并且阻塞队列已经装满时,JDK为我们提供了拒绝策略的接口RejectedExecutionHandler默认:抛出异常
  • 原理参考:线程池的实现原理

阻塞队列

  • ArrayBlockingQueue:基于数组实现的一个阻塞队列,在创建ArrayBlockingQueue对象时必须制定容量大小。并且可以指定公平性与非公平性,默认情况下为非公平的,即不保证等待时间最长的队列最优先能够访问队列。
  • LinkedBlockingQueue:基于链表实现的一个阻塞队列,在创建LinkedBlockingQueue对象时如果不指定容量大小,则默认大小为Integer.MAX_VALUE。
  • PriorityBlockingQueue:以上2种队列都是先进先出队列,而PriorityBlockingQueue却不是,它会按照元素的优先级对元素进行排序,按照优先级顺序出队,每次出队的元素都是优先级最高的元素。注意,此阻塞队列为无界阻塞队列,即容量没有上限(通过源码就可以知道,它没有容器满的信号标志),前面2种都是有界队列。
  • DelayQueue:基于PriorityQueue,一种延时阻塞队列,DelayQueue中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue也是一个无界队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。
  • 阻塞队列的原理:
        其实它的内部是维护看一个ReentrantLock 可重入锁,往队列中放入数据时它显示获取锁,然后判断当前元素的个数是否等于队列的容量大小,如果相等则调用await方法进行等待,否则将调用insert方法,insert方法出入元素后释放锁

ThreadLocal

ThreadLocal,很多地方叫做线程本地变量,
原理:
首先在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。
初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。

 

并发编程辅助类:

  1. CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同:
    CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;
    而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;
    另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。
  2. Semaphore其实和锁有点类似,它一般用于控制对某组资源的访问权限。

四:JVM

 

可以不可以自己写个String类

答案:不可以,因为 根据类加载的双亲委派机制,会去加载父类,父类发现冲突了String就不再加载了;

能否在加载类的时候,对类的字节码进行修改

答案:可以,使用Java探针技术,动态代理就是基于java 探针技术

类加载过程:

答案:类加载分为三个步骤:加载连接初始化

  1. 加载:读取一个类的二进制字节流到JVM内部,成产一个class实例
  2. 连接包括格式数据校验,为类中的所有静态变量分配内存空间,并为其设置一个初始值,得到类或者字段、方法在内存中的指针或者偏移量,以便直接调用该方法
  3. 初始化,根据程序员自己写的逻辑去初始化类变量和其他资源

jvm内存模型

JVM会把内存划分为:方法区,堆,栈,本地方法栈,程序计数器

方法区:被线程共享的区域,在方法区中,存储了每个类的源数据,以及编译后的代码,对应的常量池也会被创建出来。

堆:被线程共享的,主要存储对象本身,

栈:java栈中存放的是 一个个栈帧,每个栈帧对应一个被调用的方法,在栈帧中包括局部变量表,操作数栈,还有一当前类的常量的引用,和方法返回地址,当方法执行完毕后,就会将栈帧出栈,

本地方法栈:与java栈类型,只不过java栈是执行java方法,而本地方法栈则是执行本地方法

程序计数器:是线程私用的,记录着每个线程的执行位置,并且互相不干扰

JVM内存溢出的情况

程序计数器是唯一一个在Java虚拟机规范中没有规定任何OOM(内存溢出:OutOfMemoryError)情况的区域

栈:

  1. 如果线程请求的栈的胜读大于虚拟机允许的深度会抛出StackOVerflowError的异常
  2. 如果虚拟机在动态扩展栈时无法申请到足够的内存空间,则会抛出OutOfMemoryError异常

堆:

  • 堆是内存中最大的一块,是来及回收器管理的主要区域,当堆中没有可用内存是,将会抛出OutOfMemoryError异常

方法区:

  • 方法区被称为永久带,当方法区无法满足内存分配需求是,会抛出OutOfMemoryError异常

垃圾回收算法

一:标记-清楚算法 (最基础的垃圾回收算法)
                 标记-清除算法分为两个阶段:标记阶段和清除阶段。
                 标记阶段的任务是标记出所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。
                 缺点:标记-清除算法容易实现,但问题是,容易产生内存碎片,碎片太多可能会导致后续过程中需要为大对象分配空间时无法找到足够的空间而提前触发新的一次垃圾收集动作。
二:复制算法:
                将可用内存按容量划分为大小相等的两块,每次只使用其中一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。
                优点:复制(Copying)算法实现简单,运行高效且不容易产生内存碎片,
                缺点:使能够使用的内存缩减到原来的一半。复制算法的效率跟存活对象数目多少有很大的关系,如果存活对象很多,那么Copying算法的效率将大大降低。
 三:标记-整理算法
                该算法标记阶段和标记-清除(Mark-Sweep)一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。

四:分代回收算法
                jvm其实是将内存中的对象放在了不同的内存区域新生代和老年代,根据不同区域的特点,使用不同的垃圾啊回收策略
                一:新生代的算法:采用了GC的复制算法,速度快,因为新生代一般是新对象,都是瞬态的用了可能很快被释放的对象。
                二:老年代的算法 标记/整理算法,GC后会执行压缩,整理到一个连续的空间,这样就维护着下一次分配对象的指针,下一次对象分配就可以采用碰撞指针技术,将新对象分配在第一个空闲的区域。
jvm垃圾回收器:

JDK默认的垃圾回收器:并行垃圾回收器ParallelGC

垃圾回收器分类:

1串行垃圾回收器
            Serial/Serial Old
                它是一个单线程收集器,并且在它进行垃圾收集时,必须暂停所有用户线程。
                Serial收集器是针对新生代的收集器,采用的是复制算法,Serial Old收集器是针对老年代的收集器,采用的是标记-整理算法。
                优点是实现简单高效,缺点是会给用户带来停顿。

        2并行垃圾回收器(吞吐量)
            并行收集器:多条垃圾收集线程并行工作,而用户线程仍处于等待状态
            ParNew:
                ParNew收集器是Serial收集器的多线程版本,使用多个线程进行垃圾收集
            Parallel Scavenge:
                该收集器是一个新生代的多线程收集器(并行收集器),它在回收期间不需要暂停其他用户线程,其采用的是Copying复制算法,
            Parallel Old:
                该收集器是Parallel Scavenge收集器的老年代版本(并行收集器),使用多线程和Mark-Compact(标记-整理)算法。
        
        3并发垃圾回收器
            并发收集器:垃圾收集线程与用户线程一段时间内同时工作(不是并行,而是交替执行)
            
            CMS(Concurrent Mark Sweep):
                CMS收集器是一种以获取最短回收停顿为目标的收集器,它是一种并发收集器,采用的是Mark-Sweep(标记-清除)算法。

        G1:
            G1收集器是一款面向服务端应用的收集器,它能充分利用多CPU、多核环境。G1收集器是一款并行与并发收集器,并且它能建立可预测的停顿时间模型。

G1收集器是在JDK1.7开始可以设置使用,在JDK1.9时设置为默认垃圾收集器。G1收集器和其他收集器相比有以下特点

并行与并发:G1能充分利用多CPU、多核的硬件优势,来缩短Stop-The-World停顿时间
分代收集:和其他收集器相同,分代概念依然保留。G1收集器不需要其他收集器的配合就可以管理整个堆,可以根据不同的方式去处理新创建的对象、存活了一段时间的对象和熬过多次GC的对象。
空间整合:不同于CMS的标记-清理,G1采用的是标记-整理的方式来处理碎片化的空间。这种方式意味着G1收集器运作期间不会产生内存空间碎片,收集后提供规整的可用空间
可预测的停顿:G1相对于CMS另外一个更大的优势,就是可以设置可预测的停顿模型,能够使开发者明确指定在长度为M毫秒的时间片段内,消耗在垃圾收集器上的时间不能超过M毫秒。
    G1提供了三种模式的垃圾回收

    1、Minor GC 2、Mixed GC 3、Full GC

     在G1当中把堆内存分成了一个个Region,新生代,老年代不再是物理上面的分割,而是逻辑上的分割

     

       Minor GC

       当eden区满的时候,会触发一次Minor GC,这种触发机制和之前的其他垃圾收集器差不多,都是将Eden区和其中一个Survivor区的存活对象拷贝到另外一个Survivor区或者晋升到Old 区域,然后清空Eden区和Survivor区。

       Mixed GC

        当越来越多的年轻代中的对象晋升到老年代,会触发一次混合的垃圾收集器。也就是除了回收老年代,也会回收年轻代。这里是一部分老年代,可以选择哪些老年代的对象需要收集。

      Full GC

       如果对象内存分配的过快,mixed gc来不及回收,老年代被填满,就会触发Full GC.G1的Full GC就是Serial Old gc, 会暂停工作线程。

   G1收集器的大概的工作过程

    1、初始标记,整个过程STW,标记了从GC Roots 的可达对象

    2、并发标记 ,真个过程用户线程的垃圾回收线程共同执行,标记出GC Roots可达对象的关联对象,收集整个Region的存活对象。

    3、最终标记,整个过程STW,标记出并发标记遗漏的,以及引用关系发生变化的存活对象。

    4、筛选回收,垃圾清理过程,如果整个Region没有存活对象,将Region加入到存活列表当中。
 

新生代和老年代的区别(阿里面试官的题目):

**所谓的新生代和老年代是针对于分代收集算法来定义的,新生代又分为Eden和Survivor两个区。加上老年代就这三个区。数据会首先分配到Eden区 当中(当然也有特殊情况,如果是大对象那么会直接放入到老年代(大对象是指需要大量连续内存空间的java对象)。),当Eden没有足够空间的时候就会 触发jvm发起一次Minor GC。如果对象经过一次Minor GC还存活,并且又能被Survivor空间接受,那么将被移动到Survivor空 间当中。并将其年龄设为1,对象在Survivor每熬过一次Minor GC,年龄就加1,当年龄达到一定的程度(默认为15)时,就会被晋升到老年代 中了,当然晋升老年代的年龄是可以设置的。如果老年代满了就执行:Full GC 因为不经常执行,因此采用了 Mark-Compact算法清理

其实新生代和老年代就是针对于对象做分区存储,更便于回收等等**

如果遇到了Full GC如何处理

(a)看一下有没有大对象

(b)大的静态对象

(c) Dump 内存快照,进行分析 

redis

1. 使用redis有哪些好处?

(1) 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1) 
(2) 支持丰富数据类型,支持string,list,set,sorted set,hash 
(3) 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行 
(4) 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除

2. redis相比memcached有哪些优势?

(1) memcached所有的值均是简单的字符串,Redis作为其替代者,支持更为丰富的数据类型 
(2) redis的速度比memcached快很多 
(3) redis可以持久化其数据

3. Memcache与Redis的区别都有哪些?

1)、存储方式 
Memecache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。 
Redis有部份存在硬盘上,这样能保证数据的持久性。 
2)、数据支持类型 
Memcache对数据类型支持相对简单。 
Redis有复杂的数据类型。 
3)、使用底层模型不同 
它们之间底层实现方式 以及与客户端之间通信的应用协议不一样。 
Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。  

4)value大小

redis最大可以达到1GB,而memcache只有1MB 

4. redis常见性能问题和解决方案:

(1) Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件 

    (a)Master写内存快照,save命令调度rdbSave函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务,所以Master最好不要写内存快照。

    (b)Master AOF持久化,如果不重写AOF文件,这个持久化方式对性能的影响是最小的,但是AOF文件会不断增大,AOF文件过大会影响Master重启的恢复速度。Master最好不要做任何持久化工作,包括内存快照和AOF日志文件,特别是不要启用内存快照做持久化,如果数据比较关键,某个Slave开启AOF备份数据,策略为每秒同步一次。

   (c)Master调用BGREWRITEAOF重写AOF文件,AOF在重写的时候会占大量的CPU和内存资源,导致服务load过高,出现短暂服务暂停现象。
(2) 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次 
(3) 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内 
(4) 尽量避免在压力很大的主库上增加从库 
(5) 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3… 
这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。

5. mySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据

相关知识:redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略

redis 提供 6种数据淘汰策略: 

  1. volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰 
  2. volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰 
  3. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰 
  4. allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰 
  5. allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰 
  6. no-enviction(驱逐):禁止驱逐数据

 

 

kafka

 

 

zk

分布式锁:

基于zookeeper瞬时有序节点实现的分布式锁,其主要逻辑如下: 

 * 客户端对某个功能加锁时,在zookeeper上与该功能对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 <br>

 * 判断是否获取锁的方式,只需要判断有序节点中序号最小的一个,如果最小的节点与当客户端记录节点号相同获得锁<br>

 * 当释放锁的时候,只需将这个瞬时节点删除即可。

RPC

原理:

RPC调用是基于TCP进行通信,首先,服务端需要交对外提供的服务进行注册,服务注册可以基于ZK来做,之后,调用端,将需要调用的服务对象进行代理,其实就是创建一个代理对象,当执行代理对象的方法时会调用代理的InvocationHandler的inovke方法,将服务类的全路径+类名,防范,和参数通过socket进行外出调用,服务端接到请求后,会到注册中将服务类取出,并执行指定的方法,最后将结果方法

 

spring 

我们是在使用Spring框架的过程中,其实就是为了使用IOC,依赖注入,和AOP,面向切面编程,这两个是Spring的灵魂。

主要用到的设计模式有工厂模式和代理模式。

IOC是工厂模式参考:设计模式-工厂模式-场景以及优缺点-目的就是应对变化 (国江面试回答的)

AOP代理模式参考:设计模式-代理模式(Proxy)

参考:深入理解Java反射+动态代理

IOC就是典型的工厂模式,通过sessionfactory去注入实例。

AOP就是典型的代理模式的体现。

可以参考:Spring 学习 3- AOP

代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。  

spring的IoC容器是spring的核心,spring AOP是spring框架的重要组成部分。

在传统的程序设计中,当调用者需要被调用者的协助时,通常由调用者来创建被调用者的实例。但在spring里创建被调用者的工作不再由调用者来完成,因此控制反转(IoC);创建被调用者实例的工作通常由spring容器来完成,然后注入调用者,因此也被称为依赖注入(DI), 依赖注入和控制反转是同一个概念 。

面向方面编程(AOP)是以另一个角度来考虑程序结构 ,通过分析程序结构的关注点来完善面向对象编程(OOP)。OOP将应用程序分解成各个层次的对象,而AOP将程序分解成多个切面。spring AOP 只实现了方法级别的连接点,在J2EE应用中,AOP拦截到方法级别的操作就已经足够。在spring中,未来使IoC方便地使用健壮、灵活的企业服务,需要利用spring AOP实现为IoC和企业服务之间建立联系。 
IOC:控制反转也叫依赖注入。

利用了工厂模式将对象交给容器管理,你只需要在spring配置文件总配置相应的bean,以及设置相关的属性,让spring容器来生成类的实例对象以及管理对象。在spring容器启动的时候,spring会把你在配置文件中配置的bean都初始化好,然后在你需要调用的时候,就把它已经初始化好的那些bean分配给你需要调用这些bean的类(假设这个类名是A),分配的方法就是调用A的setter方法来注入,而不需要你在A里面new这些bean了。 
注意:面试的时候,如果有条件,画图,这样更加显得你懂了.

AOP:面向切面编程。(Aspect-Oriented Programming) 
AOP可以说是对OOP的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。 
将程序中的交叉业务逻辑(比如安全,日志,事务等),封装成一个切面,然后注入到目标对象(具体业务逻辑)中去。

实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态植入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码CGLib.

简单点解释,比方说你想在你的biz层所有类中都加上一个打印‘你好’的功能,这时就可以用aop思想来做.你先写个类写个类方法,方法经实现打印‘你好’,然后Ioc这个类 ref=“biz.*”让每个类都注入即可实现。 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值