JavaSE面试题(一)

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 Tair(兼容Redis),内存型 2GB
简介: JavaSE面试题(一)


什么叫做树

树状图是一种数据结构,它是由n(n>=1)个有限结点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。

树中的一些概念

  • 节点深度:对任意节点x,x节点的深度表示为根节点到x节点的路径长度。所以根节点深度为0,第二层节点深度为1,以此类推
    节点高度:对任意节点x,叶子节点到x节点的路径长度就是节点x的高度
    树的深度:一棵树中节点的最大深度就是树的深度,也称为高度
    父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点
    子节点:一个节点含有的子树的根节点称为该节点的子节点
    节点的层次:从根节点开始,根节点为第一层,根的子节点为第二层,以此类推
    兄弟节点:拥有共同父节点的节点互称为兄弟节点
    度:节点的子树数目就是节点的度
    叶子节点:度为零的节点就是叶子节点
    祖先:对任意节点x,从根节点到节点x的所有节点都是x的祖先(节点x也是自己的祖先)
    后代:对任意节点x,从节点x到叶子节点的所有节点都是x的后代(节点x也是自己的后代)
    森林:m颗互不相交的树构成的集合就是森林

什么是B树

B树的定义:B树(B-tree)是一种树状数据结构,能够用来存储排序后的数据。这种数据结构能够让查找数据、循序存取、插入数据及删除的动作,都在对数时间内完成。B树,概括来说是一个一般化的二叉查找树,可以拥有多于2个子节点。与自平衡二叉查找树不同,B-树为系统最优化大块数据的读和写操作。B-tree算法减少定位记录时所经历的中间过程,从而加快存取速度。这种数据结构常被应用在数据库

传统的平衡二叉树的用来搜索的有很多,红黑树,AVL树,这些书在一般情况下查询很好,但是数据量极大的时候就无能为力了,

什么是B+树

什么是红黑树

mysql索引为什么用的是b+树,而不是其他的树?

平衡二叉树或者平衡搜索树用于索引的数据结构是没有问题的,但是当数据量特别大的时候的会有性能的问题,二叉搜索树单从计算逻辑上来讲他的查找次数很快比较次数也比较少,但是当数据量很大的时候,这时候索引结构不可能全部添加到内存中,那么就会产生大量的io问题,可能出现性能问题,(前提所有的数据都在内存中进行查找),数据库索引是存储在磁盘上的,数据量比较大,所有的索引是不可能一次性加载到内存中的,能做的只是逐一的加载每一个磁盘页,这里的磁盘页对应着搜索树的节点,加入使用这种数据结构,那么查找的时候树的高度是4的话,极有可能查找4次,对应4次io,磁盘io和内存的读取差早可查了好几个数量级,性能问题就暴露出来了,但是使用B树就不会出现这种情况,因为尽可能减少磁盘的io次数,就要尽可能的降低树的高度,我们可以采用B树,这是一种多路平衡树,B树的特点是每个节点可以包含K个孩子 ,K也称为树的阶,K的值取决于磁盘页的大小,这是一个3阶B树,b树的高度较低,对于成千上万次的读写次数也就少了很多次的io,提升了效率。

b树的规则

1、根节点必须两个子节点以上

2、

2.B树和B+树的区别

1)B树的每个结点都存储了key和data,B+树的data存储在叶子节点上。

节点不存储data,这样一个节点就可以存储更多的key。可以使得树更矮,所以IO操作次数更少。

2)树的所有叶结点构成一个有序链表,可以按照关键码排序的次序遍历全部记录

由于数据顺序排列并且相连,所以便于区间查找和搜索。而B树则需要进行每一层的递归遍历。相邻的元素可能在内存中不相邻,所以缓存命中性没有B+树好。

1.排序方式:所有节点关键字是按递增次序排列,并遵循左小右大原则;

2.子节点数:非叶节点的子节点数>1,且<=M ,且M>=2,空树除外(注:M阶代表一个树节点最多有多少个查找路径,M=M路,当M=2则是2叉树,M=3则是3叉);

3.关键字数:枝节点的关键字数量大于等于ceil(m/2)-1个且小于等于M-1个(注:ceil()是个朝正无穷方向取整的函数 如ceil(1.1)结果为2);

4.所有叶子节点均在同一层、叶子节点除了包含了关键字和关键字记录的指针外也有指向其子节点的指针只不过其指针地址都为null对应下图最后一层节点的空格子;

3、索引的分类

MySQL建立索引类型

单列索引,即一个索引只包含单个列,一个表可以有多个单列索引,但这不是组合索引。组合索引,即一个索包含多个列。

索引是在存储引擎中实现的,而不是在服务器层中实现的。所以,每种存储引擎的索引都不一定完全相同,并不是所有的存储引擎都支持所有的索引类型。

普通索引

这是最基本的索引,它没有任何限制。普通索引(由关键字KEY或INDEX定义的索引)的唯一任务是加快对数据的访问速度。因此,应该只为那些最经常出现在查询条件(WHERE column = …)或排序条件(ORDER BY column)中的数据列创建索引。

CREATE INDEX indexName ON mytable(username(length));

唯一索引

它与前面的普通索引类似,不同的就是:普通索引允许被索引的数据列包含重复的值。而唯一索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。

主键索引
它是一种特殊的唯一索引,不允许有空值。一个表只能有一个主键。

一般是在建表的时候同时创建主键索引:

外键索引

如果为某个外键字段定义了一个外键约束条件,MySQL就会定义一个内部索引来帮助自己以最有效率的方式去管理和使用外键约束条件。

组合索引

为什么没有 city,age这样的组合索引呢?这是因为MySQL组合索引“最左前缀”的结果。简单的理解就是只从最左面的开始组合。并不是只要包含这三列的查询都会用到该组合索引。下面的几个SQL就会用到这个组合索引。

什么是聚簇索引?什么是辅助索引?

聚簇索引和主键索引

聚簇索引并不是一种单独的索引类型,而是一种数据存储方式,具体细节依赖于其实现方式。

MySQL数据库中innodb存储引擎,B+树索引可以分为:

聚簇索引(也称聚集索引,clustered index)

辅助索引(有时也称非聚簇索引或二级索引,secondary index,non-clustered index)。

这两种索引内部都是B+树,聚集索引的叶子节点存放着一整行的数据。

InnoDB聚簇索引和主键索引

Innobd中的主键索引是一种聚簇索引,

非聚簇索引都是辅助索引,像复合索引、前缀索引、唯一索引。

InnoDB中,表数据文件本身就是按B+Tree组织的一个索引结构,

聚簇索引就是按照每张表的主键构造一颗B+树,同时叶子节点中存放的就是整张表的行记录数据,也将聚集索引的叶子节点称为数据页。这个特性决定了索引组织表中数据也是索引的一部分;

一般建表会用一个自增主键做聚簇索引,没有的话MySQL会默认创建,但是这个主键如果更改代价较高,故建表时要考虑自增ID不能频繁update这点。

我们日常工作中,根据实际情况自行添加的索引都是辅助索引,辅助索引就是一个为了需找主键索引的二级索引,现在找到主键索引再通过主键索引找数据。

Innodb通过主键聚集数据,如果没有定义主键,innodb会选择【非空】的唯一索引代替。如果没有这样的索引,innodb会隐式的定义一个主键来作为聚簇索引。

mvcc

什么是mvcc

MVCC,多版本并发控制。MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的多个事务的并发访问。

多版本控制: 指的是一种提高并发的技术。最早的数据库系统,只有读读之间可以并发,读写,写读,写写都要阻塞。引入多版本之后,只有写写之间相互阻塞,其他三种操作都可以并行,这样大幅度提高了InnoDB的并发度

mvcc为了实现什么

MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理读-写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读

我们都知道并发访问数据库造成的四种问题(脏写(修改丢失)、脏读、不可重复读、幻读),MVCC就是在尽量减少锁使用的情况下高效避免这些问题

相关概念

MVCC是被Mysql中 事务型存储引擎InnoDB 所支持的;

应对高并发事务, MVCC比单纯的加锁更高效;

MVCC只在 READ COMMITTED 和 REPEATABLE READ 两个隔离级别下工作;

MVCC可以使用 乐观(optimistic)锁 和 悲观(pessimistic)锁来实现;

各数据库中MVCC实现并不统一

但是书中提到 “InnoDB的MVCC是通过在每行记录后面保存两个隐藏的列来实现的”(网上也有很多此类观点), 但其实并不准确, 可以参考MySQL官方文档, 可以看到, InnoDB存储引擎在数据库每行数据的后面添加了三个字段, 不是两个!!

为什么两个事务执行的顺序有偏差?

很简单,如果能够同时交叉修改同一个数据,那不就是“修改丢失(脏写)”并发问题了吗,mysql在执行操作的时候,会对其加锁,另外一个事务就要暂时挂起

对该记录每次更新后,都会将旧值放到一条undo日志中,就算是该记录的一个旧版本,随着更新次数的增多,所有的版本都会被roll_pointer属性连接成一个链表,我们把这个链表称之为版本链,版本链的头节点就是当前记录最新的值。另外,每个版本中还包含生成该版本时对应的事务id

事务id是递增的,

MVcc通过一个行级锁的变种,避免了很多情况下的一个加锁的操作,实现了加更少的锁,减小系统开销,MVcc只能在读已提交和可重复度的两种的隔离级别下才起作用,他针对的是一个快照读下(不是当前读)快照读就是普通的select读取,当前读是就是使用了一种悲观锁,需要去加锁,比如说我们使用了一个insert,update,delete都会去加一个悲观锁,select for update ,select lock in shade mode ,都是需要去加锁来保证一定的目的性。快照读是MVcc实现的,目的是提高读写的时候,减少锁竞争,数据库中的四种隔离级别中的隔离性就是通过,各种加锁机制和mvcc机制来实现,读已提交和可重复度的快照读都是基于mvcc实现的,

readview就是一个快照,他的作用是什么呢?

我的版本连里边有这么多的版本,但是当前一个select语句过来之后,我并不知道去取哪一个版本,我去select,id = 1这条数据之后,

这个readview的作用就是让我们知道我们的知道我们要去版本连里边取拿到哪一条记录来返回给用户,这个readview的参数,他在mysql系统当中就是一个对象,他有自己的几个参数:

没有commit的事务id

最小的一个事务id

生成

记录锁也叫行锁

对于使用唯一索引来搜索并给某一行记录加锁的语句,不会产生间隙锁。(这不包括搜索条件仅包括多列唯一索引的一些列的情况;在这种情况下,会产生间隙锁。)例如,如果id列具有唯一索引,则下面的语句仅对具有id值100的行使用记录锁,并不会产生间隙锁:

SELECT * FROM child WHERE id = 100 FOR UPDATE;

间隙锁策略

首先对mysql锁进行划分:

按照锁的粒度划分:行锁、表锁、页锁
按照锁的使用方式划分:共享锁、排它锁(悲观锁的一种实现)
还有两种思想上的锁:悲观锁、乐观锁。
InnoDB中有几种行级锁类型:Record Lock、Gap Lock、Next-key Lock
Record Lock:在索引记录上加锁
Gap Lock:间隙锁
Next-key Lock:Record Lock+Gap Lock

innodb的加锁原理是基于索引的,。没有索引的话,是会加表锁的,

在不通过索引条件查询的时候,InnoDB使用的确实是表锁!

由于 MySQL 的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行 的记录,但是如果是使用相同的索引键,是会出现锁冲突的。

当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论 是使用主键索引、唯一索引或普通索引,InnoDB 都会使用行锁来对数据加锁。

脏读:所谓的脏读,其实就是读到了别的事务回滚前的脏数据。比如事务B执行过程中修改了数据X,在未提交前,事务A读取了X,而事务B却回滚了,这样事务A就形成了脏读。

幻读的定义:

事务A 按照一定条件进行数据读取, 期间事务B 插入了相同搜索条件的新数据,事务A再次按照原先条件进行读取时,发现了事务B 新插入的数据 称为幻读

不可重复读:事务A首先读取了一条数据,然后执行逻辑的时候,事务B将这条数据改变了,然后事务A再次读取的时候,发现数据不匹配了,就是所谓的不可重复读了。

记录锁,间隙锁,next-key lock

如果更新条件为索引字段,但是并非唯一索引(包括主键索引),例如执行“update from t1 set v2=0 where v1=9;” 那么此时更新会使用Next-Key Lock。使用Next-Key Lock的原因:

默认情况下,InnoDB工作在可重复读隔离级别下,并且会以Next-Key Lock的方式对数据行进行加锁,这样可以有效防止幻读的发生。Next-Key Lock是行锁和间隙锁的组合,当InnoDB扫描索引记录的时候,会首先对索引记录加上行锁(Record Lock),再对索引记录两边的间隙加上间隙锁(Gap Lock)。加上间隙锁之后,其他事务就不能在这个间隙修改或者插入记录。

多线程面试题

2、创建线程几种方式

继承Thread类:Thread是类,有单继承的局限性。

实现Runnable接口:任务和线程分开,不能返回执行结果。

实现Callable接口:利用FutureTask执行任务,能取到执行结果

面试题2:说说submit(和 execute两个方法有什么区别?

submit() 和 execute() 都是用来执行线程池的,只不过使用 execute() 执行线程池不能有返回方法,而使用 submit() 可以使用 Future 接收线程池执行的返回值。

面试题3:shutdownNow() 和 shutdown() 两个方法有什么区别?

shutdownNow() 和 shutdown() 都是用来终止线程池的,它们的区别是,使用 shutdown() 程序不会报错,也不会立即终止线程,它会等待线程池中的缓存任务执行完之后再退出,执行了 shutdown() 之后就不能给线程池添加新任务了;shutdownNow() 会试图立马停止任务,如果线程池中还有缓存任务正在执行,则会抛出 java.lang.InterruptedException: sleep interrupted 异常。

一些方法

submit()/execute():执行线程池
shutdown()/shutdownNow():终止线程池
isShutdown():判断线程是否终止
getActiveCount():正在运行的线程数
getCorePoolSize():获取核心线程数
getMaximumPoolSize():获取最大线程数
getQueue():获取线程池中的任务队列

加粗样式

面试题9:知道线程池中线程复用原理吗?

线程池将线程和任务进行解耦,线程是线程,任务是任务,摆脱了之前通过 Thread 创建线程时的一个线程必须对应一个任务的限制。

在线程池中,同一个线程可以从阻塞队列中不断获取新任务来执行,其核心原理在于线程池对 Thread 进行了封装,并不是每次执行任务都会调用 Thread.start() 来创建新线程,而是让每个线程去执行一个“循环任务”,在这个“循环任务”中不停的检查是否有任务需要被执行,如果有则直接执行,也就是调用任务中的 run 方法,将 run 方法当成一个普通的方法执行,通过这种方式将只使用固定的线程就将所有任务的 run 方法串联起来。

线程池的重要属性

一个线程池的核心参数有很多,每个参数都有着特殊的作用,各个参数聚合再一起后将完成整个线程池的完整工作。其中的六个尤为重要:线程状态和工作线程的数量,核心线程数和最大线程数,创建线程的工厂,缓存任务的阻塞队列,非核心线程存活的时间和拒绝策略。

核心线程数和最大线程数

现在有了标识工作线程的个数的变量了,那到底该有多少个线程才合适呢?线程多了会浪费线程资源,少了又不能发挥线程池的性能。

为了解决这个问题,线程池设计了两个变量来协作,分别是:

核心线程数(corePoolSize):用来表示线程池中的核心线程的数量,也可以称为可闲置的线程数量。

最大线程数(maximumPoolSize):用来表示线程池中最多能够创建的线程数量。

现在我们有一个疑惑,既然已经有了标识工作线程的个数的变量了,为什么还要有核心线程数和最大线程数呢?

其实你这样想就能够理解了,创建线程是有代价的,不能每次要执行一个任务时就创建一个线程,但是也不能在任务非常多的时候,只有少量的线程在执行,这样任务是来不及处理的,而是应该创建合适的足够多的线程来及时地处理任务。

随着任务数量的变化,当任务数量明显减少时,原本创建的多余的线程就没有必要再存活着了,因为这时使用少量的线程就能够处理得过来了,所以说真正工作的线程的数量,是随着任务的变化而变化的。

工作线程的个数可能从0到最大线程数之间变化,当执行一段时间之后可能维持在核心线程数(corePoolSize),但也不是绝对的,取决于核心线程是否允许被超时回收。

Q. 线程池是什么时候创建线程的?

A.任务提交的时候

Q.任务runnable task是先放到core到maxThread之间的线程,还是先放到队列?

A.先放队列!!!

Q. 队列中的任务是什么时候取出来的?

A. worker中 runWorker() 一个任务完成后,会取下一个任务

Q. 什么时候会触发reject策略?

A.队列满并且maxthread也满了, 还有新任务,默认策略是reject

Q. core到maxThread之间的线程什么时候会die?

A. 没有任务时,或者抛异常时。

core线程也会die的,core到maxThread之间的线程有可能会晋升到core线程区间,

core max只是个计数,线程并不是创建后就固定在一个区间了

如何创建线程池

创建线程池主要是ThreadPoolExecutor类来完成,ThreadPoolExecutor 的有许多重载的构造方法,通过参数最多的构造方法来理解创建线程池有哪些需要配置的参数。ThreadPoolExecutor 的构造方法为:

ThreadPoolExecutor(int corePoolSize,

int maximumPoolSize,

long keepAliveTime,

TimeUnit unit,

BlockingQueue workQueue,

ThreadFactory threadFactory,

RejectedExecutionHandler handler)

corePoolSize:线程池中的核心线程数

maximumPoolSize:线程池中最大线程数

keepAliveTime:闲置超时时间

unit:keepAliveTime 超时时间的单位(时/分/秒等)

workQueue:线程池中的任务队列

threadFactory:为线程池提供创建新线程的线程工厂

rejectedExecutionHandler:线程池任务队列超过最大值之后的拒绝策略

如何实现线程池线程复用的?

:Thread.start() 会执行传入Runnable 的run(),同时执行完后,当前THread 就结束了,这怎么实现复用呢?多线可能会有多个Runnable 啊,每次执行完就结束了那岂不是每一个Runnable 都要重新new Thread(runable),这跟自己new Thread 有什么区别?其实线程池创建的Thead 传入的Runnable 并不是我们传入的task Runanble, 而是其内部一个封装的一个Worker 类,我们创建的Runnable 并没有被当做参数传入到Runnable 中,而是被加入到对比当中,在worker 这个runnable 的run()中去循环取出,就是让worker的run()内部执行while循环(循环任务队列,这样就能保证一直复用了),具体分 poll(),和take()两种形式,来区分该线程是否需要被销毁,同时也说明核心线程的其实就是最后活下来的那几个线程

连接池面试题

数据库连接是一种关键的有限的昂贵的资源,这一点在多用户的网页应用程序中体现的尤为突出.对数据库连接的管理能显著影响到整个应用程序的伸缩性和健壮性,影响到程序的性能指标.数据库连接池正式针对这个问题提出来的.数据库连接池负责分配,管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。如下图所示:

数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中, 这些数据库连接的数量是由最小数据库连接数来设定的.无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量.连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。

数据库连接池的最小连接数和最大连接数的设置要考虑到以下几个因素:

最小连接数:是连接池一直保持的数据库连接,所以如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费.

最大连接数:是连接池能申请的最大连接数,如果数据库连接请求超过次数,后面的数据库连接请求将被加入到等待队列中,这会影响以后的数据库操作

如果最小连接数与最大连接数相差很大:那么最先连接请求将会获利,之后超过最小连接数量的连接请求等价于建立一个新的数据库连接.不过,这些大于最小连接数的数据库连接在使用完不会马上被释放,他将被放到连接池中等待重复使用或是空间超时后被释放.

druid配置参数详解

driverClassName = com.mysql.cj.jdbc.Driver

driver=com.mysql.cj.jdbc.Driver

url=jdbc:mysql:///student?serverTimezone=GMT&useSSL=false

username=root

password=yanzhiguo140710

#初始化连接数量#

initialSize = 5

#最大连接数量

maxACtive = 10

#等待时间 3秒

volitial关键字的作用

本文主要介绍Java语言中的volatile关键字,内容涵盖volatile的保证内存可见性、禁止指令重排等。废话不多说,直接进去主题
1 保证内存可见性
1.1 基本概念
可见性是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程修改的结果,另一个线程马上就能看到。
1.2 实现原理
当对非volatile变量进行读写的时候,每个线程先从主内存拷贝变量到CPU缓存中,如果计算机有多个CPU,每个线程可能在不同的CPU上被处理,这意味着每个线程可以拷贝到不同的CPU cache中。
volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,保证了每次读写变量都从主内存中读,跳过CPU cache这一步。当一个线程修改了这个变量的值,新值对于其他线程是立即得知的。

4、volatile与synchronized区别

1)volatile本质是告诉JVM当前变量在寄存器中的值是不确定的,需要从主存中读取。synchronized则是锁定当前变量,只有当前线程可以访问该变量,其它线程被阻塞。

2)volatile仅能使用在变量级别,synchronized则可以使用在变量、方法。

3) volatile不会造成线程阻塞,synchronized会造成线程阻塞。

3)volatile仅能实现变量修改的可见性,而synchronized则可以保证变量修改的可见性和原子性。

synchronized和lock的区别?

lock和synchronized都是java的同步的方式,synchronized用的比较多,lock是一个接口,sychronized是一个关键字,使用lock需要实现lock接口,lock中有lock,unlock,trylock,lockinterruptely(允许被中断),lock需要我们手动的去获得锁和释放锁,sychronized不用我们手动的去获取锁和释放锁,性能上,lock线程较多的时候性能较好,

s是关键字,lock是接口

s是自动获取锁,自动释放锁,释放锁的操作是在jvm当中,l是手动获取和手动释放,需要我们人为的写在代码块当中,

syschronzied异常可以释放锁,lock,异常需要手动释放锁。

synchronizid是不可以中断的,lock是可以进行中断的,提供了中断的方法。

锁竞争小的时候,sychronized性能高一点,所锁竞争小的时候,lock性能会事s的几十倍。

所谓同步代码就是同一时刻只能有一个线程执行(访问,修改)这个代码(变量)。

synchronize的底层的原理

java中的对象有固定格式的对象头,对象头是由固定的格式的,在对象头中有一个Mark word这样的一个64位bit的趋区间,在对象头有两个bit来标志,在对象头有一个bit标志锁,有一个bit来标志偏向锁,

1、并发编程三要素?

1)原子性

原子性指的是一个或者多个操作,要么全部执行并且在执行的过程中不被其他操作打断,要么就全部都不执行。

2)可见性

可见性指多个线程操作一个共享变量时,其中一个线程对变量进行修改后,其他线程可以立即看到修改的结果。

3)有序性

有序性,即程序的执行顺序按照代码的先后顺序来执行。

3)Runnable和Callable的区别

Callable规定(重写)的方法是call(),Runnable规定(重写)的方法是run()。

Callable的任务执行后可返回值,是一个Future类型的对象,而Runnable的任务是不能返回值的。

Call方法可以抛出异常,run方法不可以。

6、线程的状态流转图

线程的生命周期及五种基本状态:

7、Java线程具有五中基本状态

1)新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();

2)就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;

3)运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;

4)阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。

根据阻塞产生的原因不同,阻塞状态又可以分为三种:

a.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;

b.同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;

c.其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

5)死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期

什么是多线程的上下文切换

多线程的上下文切换是指CPU控制权由一个已经正在运行的线程切换到另外一个就绪并等待获取CPU执行权的线程的过程。

Java集合框架的三大类接口

Collection包含:List和Set;

三大接口为:List、Set、Map

共同点:都是集合接口,实现存储多个对象

List

1.可以允许重复的对象。

2.可以插入多个null元素。

3.是一个有序容器,保持了每个元素的插入顺序,输出的顺序就是插入的顺序。

常用的实现类有 ArrayList、LinkedList 和 Vector。

ArrayList和Vector底层都是用数组实现的,最大的区别在于Vector是线程安全的,因此效率不如ArrayList。

LinkedList底层用双链表实现,与前两个相比,优点是便于增删元素,缺点是访问元素性能效率相对低。

hashset底层就是hashmap,

HashMap底层数据结构?

jdk1.8之前hashmap的数据结构是数组+链表,jdk·1.8之后就成了数组+链表+红黑树,hashmap初始化之后呢他的数组长度是16个,hashset

Redis基本的事务操作

事务有acid这几个原则。

Redis当中单条命令是保证原子性的,但是Redis当中的事务是无法保证原子性的,

事务的本质是一组命令一块执行,现在在一个事务中,我们将这些命令放到一个事务中记性执行,当一个命令记性执行的时候,如果有一个命令有错的,如果一个事务中的命令都会被序列化

Redis中的事务没有隔离级别的概念

Redis事务:

开启事务(multi)

命令入队(正常写命令即可)

执行事务(excute)

入队的时候并没有执行,只有当我们执行excude的时候才会进行执行。

放弃事务的方法也就是回滚:(discard)事务中队列中的命令全都不会被执行

如果事务中有代码出错了,怎么办?如果事务中代码命令有错,那么所有队列中的这个命令都不会执行,如果是运行时异常,就是那种事务队列中存在一些语法行错误,执行命令的时候发现了,其他命令是可以正常进行的,没有原子性的这个方法,

1、例如写一个Redis无法识别的错误,getset命令,那么所有都不会执行。这种就是编译行异常,执行事务报错,所有命令都不会执行。

2、第二种是运行时错误,清空Redis,flushdb,比如使用一个incr命令让一个字符串+1,那么编译的时候不会报错,真正执行命令的时候就会有异常产生,这个命令失败,其他的命令成功。写法有问题导致的错误。

redis的持久化的策略

redishis一个内存数据库,断电就小时,他一定要有一写持久化的策略

他的原理概括来讲就是在指定的时间间隔内将内存中的数据集体写入到磁盘中,形成一个版本快照,数据恢复的时候再讲快照中的数据恢复到内存当中。

具体的内容是

RDB使我们最常用的一个持久化策略,他的原理是由主进程来fork出来一个子进程来进行持久化操作,首先会将数据持久化到一个临时文件当中,在用这个临时文件替换上次的持久化版本(dump.rdb)。整个过程中主进程不会参与任何的io操作,所以整体的性能是很高的,如果是大规模数据进行回复,并且对少量数据丢失不是很敏感, 那么rdb是非常优秀的,但是他的确定就是最后一个版本快照之后的数据会丢失。

什么时候会触发rdb

save 60 5 60秒内修改了5次这样的数据就会触发快照策略。

当我们执行了flushall命令也会触发rdb持久化

当我们关闭redis的时候也会触发一个redis文件。这些命令全都会触发rdb,

触发之后就会生成一个dump.rdb一个文件

如何回复rdb文件

只需要将rdb文件放入到redis启动目录下就可以了。redis启动的会后会自动检查这启动目录下的文件,就可以进行恢复了,这是一个全自动的过程。

rdb的有点和缺点

优点:

适合大规模的数据恢复,如果我们对数据完整性要求不高,

父进程不会参与备份性能很高。

缺点:

需要的一定的时间间隔来进行操作,如果redis以外党纪了,最后一次rdb之后的数据就丢了。

fork的子进程会占用一定的内存空间。

生产环境我们会对rdb文件进行备份。我们还是要用人家默认的配置。

AOF

将我们所有的命令都记录下来,相当于是一个历史文件,回复的时候,把这个文件全部在执行一遍

父进程fork一个子进程,子进进程已日志的形式记录每一个写的操作,只会记录写的操作,不会记录读的操作,他只会去追加文件不会去修改和覆盖文件,redis重启之后会重新读取其中的文件中的写的命令来回复数据。aof形成的文件名称叫做appendonly.aof,大量数据的时候开启回复数据会很久。

这个东西默认不不开启的,如果开启我们需要手动记性配置开启,改为yes即可,开启之后我们就可以

redis中从复制

一个叫主,一个叫从

一个主人带3个仆人,就是一个集群,就是一个master的redis服务器,带三台slave的redis的服务器,

数据的复制是单向的,只能由咱们的主节点复制到从节点,只能由主机复制到从机上,读写分离,中从赋值,可以减缓服务的压力。

我们可以通过修改配置文件,在同一个物理机上启动多个redis实例,实现一个伪集群,

redis默认自己本身就是一个主服务,不要专门配置主服务,只需要配置从服务即可。

redis主从服务的原理

默认情况下任何一个redis都认为自己是一个主节点。我们就需要配置主从节点。

查看命令;

默认三天都是主机,如何配置主从复制呢?配置的话,我们需要去配置从机,我们一般情况下,只需要配置从机,就是让他去认老大,连接上某个要当从机的redis实例之后,执行一个slaveof ip port就可以实现了。

就是人一个老大就可以了,配置几个。

这样就实现了主从赋值了。

真是的主从配置应该从文件当中去进行配置,如果使用命令的话,只是当前是这样的。

这是配置这么搞。

主机可以设置值,从机不能写,直接读,所有的写需求是交个主机来的,所有的读需求是交给从机来,主机中的所有的数据都会被从机进行拉取和保存。

从机中是不可能写的,只能读(试来着,确实如此。)

这样加入我们的主机崩了,我们从机还是有数据的,所以还是很安全的。

在没有配置哨兵机制的情况下,主机挂掉,从机不能称为主机,但是没有写操作了。,当主机重新启动之后,从机会重新脸上主机可以拿到值。

最高级的事,主机挂了之后,从机可以选出来一个主机。

哨兵模式

哨兵模式是一种特殊的模式,redis中提供了哨兵的命令,他是一个独立的进程,能够独立运行,哨兵通过发送命令来监控多个redis实例,等待redis实例的响应。了解当前redis实例的运行状态。

他有两个作用,

1、通过发送命令给主机和从机,从机和主机返回他们的运行装状态,达到监控的目的

2、当哨兵发现某个master宕机之后呢,就会选举出来一个新的从机来作为主机,通过发布订阅模式通知其他从机修改配置文件,切换新的主机。

redis-sentinal就是他的哨兵的启动文件。

多哨兵模式,

哨兵之间也要互相进行监控,主节点挂了之后,并不会立刻选举,当有多个哨兵都认为是已经宕机了,哨兵之间就会进行投票,然后,随机投票,哨兵,投票会有一个投票的算法。票数最高的那个来出来进行一个当主机。

redis缓存穿透和雪崩

我们的缓存是redis当中,缓存是客户端的请求是

什么是缓存穿透?

描述:访问一个缓存和数据库都不存在的 key,此时会直接打到数据库上,并且查不到数据,没法写缓存,所以下一次同样会打到数据库上。

此时,缓存起不到作用,请求每次都会走到数据库,流量大时数据库可能会被打挂。此时缓存就好像被“穿透”了一样,起不到任何作用。

解决方案:

1、接口校验。在正常业务流程中可能会存在少量访问不存在 key 的情况,但是一般不会出现大量的情况,所以这种场景最大的可能性是遭受了非法攻击。可以在最外层先做一层校验:用户鉴权、数据合法性校验等,例如商品查询中,商品的ID是正整数,则可以直接对非正整数直接过滤等等。

2、缓存空值。当访问缓存和DB都没有查询到值时,可以将空值写进缓存,但是设置较短的过期时间,该时间需要根据产品业务特性来设置。

3、布隆过滤器。使用布隆过滤器存储所有可能访问的 key,不存在的 key 直接被过滤,存在的 key 则再进一步查询缓存和数据库。

什么是缓存击穿

描述:某一个热点 key,在缓存过期的一瞬间,同时有大量的请求打进来,由于此时缓存过期了,所以请求最终都会走到数据库,造成瞬时数据库请求量大、压力骤增,甚至可能打垮数据库。

1、加互斥锁。在并发的多个请求中,只有第一个请求线程能拿到锁并执行数据库查询操作,其他的线程拿不到

2、热点数据不过期。直接将缓存设置为不过期,然后由定时任务去异步加载数据,更新缓存。

什么是缓存雪崩?

描述:大量的热点 key 设置了相同的过期时间,导在缓存在同一时刻全部失效,造成瞬时数据库请求量大、压力骤增,引起雪崩,甚至导致数据库被打挂。

缓存雪崩其实有点像“升级版的缓存击穿”,缓存击穿是一个热点 key,缓存雪崩是一组热点 key。

解决方案:

1、过期时间打散。既然是大量缓存集中失效,那最容易想到就是让他们不集中生效。可以给缓存的过期时间时加上一个随机值时间,使得每个 key 的过期时间分布开来,不会集中在同一时刻失效。
2、热点数据不过期。该方式和缓存击穿一样,也是要着重考虑刷新的时间间隔和数据异常如何处理的情况。
3、加互斥锁。该方式和缓存击穿一样,按 key 维度加锁,对于同一个 key,只允许一个线程去计算,其他线程原地阻塞等待第一个线程的计算结果,然后直接走缓存即可。

如何使用redis作为分布式锁

使用Redis,基于setnx命令。

setnx key value(线程的私有变量来判断。)

当加锁失败后,订阅锁释放的消息,自身进入阻塞状态。

当持有锁的客户端释放锁的时候,发布锁释放的消息。

当进入阻塞等待的其他客户端收到锁释放的消息后,解除阻塞等待状态,再次尝试加锁。

redis中实现发布和订阅

线程之间的通讯,我们一般会使用一个队列,一个是发送者一个是订阅者,发送者基于管道或者队列将消息通过redis发送消息给rendis,redis推送消息给订阅者。redis是可以实现这种消息的订阅和发布的,一个人可以订阅任何一个频道,订阅发布的一个模式,中间的一个核心还是队列。redis可以使用一个pubscrib来订阅一个频道,

两者保证频道的统一即可。

发布和订阅之后,redis在底层维护了一个字典。

什么是垃圾回收

GC主要用于Java堆的管理,程序在运行过程中,有堆内存中有一些对象,已经没有应用指向他们了,程序用不了它们了,对程序而言它们已经死亡,为了确保程序运行时的性能,java虚拟机在程序运行的过程中不断地进行自动的垃圾回收(GC)。

GC是不定时去堆内存中清理不可达对象。即使程序员能明确地判断出有一块内存已经无用了,是应该回收的,程序员也不能强制垃圾收集器回收该内存块。

程序员唯一能做的就是通过调用System.gc 方法来"建议"执行垃圾收集器,但是他是否执行,什么时候执行却都是不可知的。

** finalize方法作用**

它是在Object类中定义的,因此所有的类都继承了它,finalize()方法是在每次执行GC操作之前时会调用的方法,可以用它做必要的清理工作。finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。

新生代、老年代、永久代(方法区)的区别

Java 中的堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象。

在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。

先不要管为什么要分代,后面有例子

老年代就一个区域。新生代 ( Young ) 又被划分为三个区域:Eden、From Survivor、To Survivor。

这样划分的目的是为了使 JVM 能够更好的管理堆内存中的对象,包括内存的分配以及回收。

默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 ),即:新生代 ( Young ) = 1/3 的堆空间大小。老年代 ( Old ) = 2/3 的堆空间大小。

其中,新生代 ( Young ) 被细分为 Eden 和 两个 Survivor 区域,这两个 Survivor 区域分别被命名为 From Survivor 和 ToSurvivor ,以示区分。

默认的,Edem : From Survivor : To Survivor = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定 ),即: Eden = 8/10 的新生代空间大小,From Survivor = To Survivor = 1/10 的新生代空间大小。

JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的。

因此,新生代实际可用的内存空间为 9/10 ( 即90% )的新生代空间。

永久代就是JVM的方法区。在这里都是放着一些被虚拟机加载的类信息,静态变量,常量等数据。这个区中的东西比老年代和新生代更不容易回收。

为什么要这么分带

其实主要原因就是可以根据各个年代的特点进行对象分区存储,更便于回收,采用最适当的收集算法:

新生代中,每次垃圾收集时都发现大批对象死去,只有少量对象存活,便采用了复制算法,只需要付出少量存活对象的复制成本就可以完成收集。
而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须采用“标记-清理”或者“标记-整理”算法。

新生代又分为Eden和Survivor (From与To,这里简称一个区)两个区。加上老年代就这三个区。数据会首先分配到Eden区当中(当然也有特殊情况,如果是大对象那么会直接放入到老年代(大对象是指需要大量连续内存空间的java对象)。当Eden没有足够空间的时候就会触发jvm发起一次Minor GC,。如果对象经过一次Minor-GC还存活,并且又能被Survivor空间接受,那么将被移动到Survivor空间当中。并将其年龄设为1,对象在Survivor每熬过一次Minor GC,年龄就加1,当年龄达到一定的程度(默认为15)时,就会被晋升到老年代中了,当然晋升老年代的年龄是可以设置的。

Minor GC、Major GC、Full GC区别

Minor GC是新生代GC,指的是发生在新生代的垃圾收集动作。由于java对象大都是朝生夕死的,所以Minor GC非常频繁,一般回收速度也比较快。

Major GC是老年代GC,指的是发生在老年代的GC,通常执行Major GC会连着Minor GC一起执行。Major GC的速度要比Minor GC慢的多。

Full GC是清理整个堆空间,包括年轻代和老年代

Minor GC 触发条件一般为:

eden区满时,触发MinorGC。即申请一个对象时,发现eden区不够用,则触发一次MinorGC。

新创建的对象大小 > Eden所剩空间

Major GC和Full GC 触发条件一般为:

Major GC通常是跟full GC是等价的

每次晋升到老年代的对象平均大小>老年代剩余空间,永久代空间不足

执行System.gc()

堆内存分配很大的对象

本质上都是有对象要进来老年代,老年代的空间满了。

如何判断对象是否存活

你熟知的Gc的算法

这两个问题是等价的。

引用计数法

就是如果一个对象没有被任何引用指向,则可视之为垃圾。这种方法的缺点就是不能检测到环的存在。

首先需要声明,至少主流的Java虚拟机里面都没有选用引用计数算法来管理内存。

什么是引用计数法:每个对象在创建的时候,就给这个对象绑定一个计数器。每当有一个引用指向该对象时,计数器加一;每当有一个指向它的引用被删除时,计数器减一。这样,当没有引用指向该对象时,计数器为0就代表该对象死亡

主流的Java虚拟机里面没有选用引用计数算法来管理内存,其中最主要的原因是它很难解决对象之间相互循环引用的问题。

可达性分析法

该种方法是从GC Roots开始向下搜索,搜索所走过的路径为引用链。当一个对象到GC Roots没用任何引用链时,则证明此对象是不可用的,表示可以回收

这是目前主流的虚拟机都是采用的算法。

复制算法

该算法将内存平均分成两部分,然后每次只使用其中的一部分,当这部分内存满的时候,将内存中所有存活的对象复制到另一个内存中,然后将之前的内存清空,只使用这部分内存,循环下去。

复制算法的优点:

在存活对象不多的情况下,性能高,能解决内存碎片和java垃圾回收算法之-标记清除 中导致的引用更新问题。

复制算法的缺点::

会造成一部分的内存浪费。不过可以根据实际情况,将内存块大小比例适当调整;如果存活对象的数量比较大,复制算法的性能会变得很差。

复制算法的应用场景:

复制算法一般是使用在新生代中,因为新生代中的对象一般都是朝生夕死的,存活对象的数量并不多,这样使用复制算法进行拷贝时效率比较高。

标记–清除算法(Mark-Sweep)

为每个对象存储一个标记位,记录对象的状态(活着或是死亡)。

分为两个阶段,一个是标记阶段,这个阶段内,为每个对象更新标记位,检查对象是否死亡;第二个阶段是清除阶段,该阶段对死亡的对象进行清除,执行 GC 操作。

标记–清除算法(Mark-Sweep)

标记-整理法是标记-清除法的一个改进版。同样,在标记阶段,该算法也将所有对象标记为存活和死亡两种状态;不同的是,在第二个阶段,该算法并没有直接对死亡的对象进行清理,而是将所有存活的对象整理一下,放到另一处空间,然后把剩下的所有对象全部清除。这样就达到了标记-整理的目的。

什么是垃圾回收器

垃圾回收器是具体的垃圾回收算法的具体实现,不同的jvm的版本采用的垃圾回收算法是不一样的
先说结论 默认使用的是 c (新生代) 和 Parallel Old (老年代),基于我的Jdk 1.8.0_181-b13版本

新生代收集器:Serial、Parallel Scavenge(复制算法)

老年代收集器:Serial Old、Parallel Old(标记整理)

整堆收集器:G1

生产环境发现接口请求慢怎么排查?

个人的思路大概是这样的,从三个方面上来看:

第一方面从明面上的环境上看,我们可以先检查:

1、当前电脑访问服务器的网络是否存在延迟,比如ping命令进行测试,可以排除当前电脑/环境的问题

2、使用前端开发者工具看下,相关页面JS、CSS、ajax请求加载是否比平常慢,可能推测出返回数据报文量大,导致响应耗时,比如只有1M带宽。

3、前端使用开发者工具,看下基本ajax接口的请求耗时,看一下是所有接口慢,还是单独某个接口特定情况慢

4、确认下慢的接口是公网访问慢,还是内网访问也慢,内网可以通过curl命令测试接口

5、确认下该接口的代码中的关键SQL是否有可能出慢,是影响的主要条件 第一方面主要是大概找到规律和确认的现象。

第二方面从系统监控上看:

1、监控下慢的时候的CPU和内存和数据库链接使用情况,看看是否出现了连接数过高的情况

2、监控下JVM的使用情况,看下gc的情况

3、使用netstat 查看下系统连接数的使用情况,是否存在大量未释放的链接

第三方面从请求链路和核心组件上看。

1、首先得知道当前接口的请求链路或者架构图是什么样的,客户端->nginx->gateway->service1->service2->redis->db

2、然后使用arthas工具的trace命令,监控后端相关方法的耗时情况,排查出耗时比较大的地方

最后就是看下代码了

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
7月前
|
存储 安全 Java
Java大数据面试复习30天冲刺 - 日积月累,每日五题【Day03】——JavaSE
Java大数据面试复习30天冲刺 - 日积月累,每日五题【Day03】——JavaSE
73 0
|
7月前
|
安全 Java 大数据
Java大数据面试复习30天冲刺 - 日积月累,每日五题【Day01】——JavaSE
Java大数据面试复习30天冲刺 - 日积月累,每日五题【Day01】——JavaSE
79 0
|
开发框架 分布式计算 Java
【面试题精讲】JavaSe和JavaEE的区别
【面试题精讲】JavaSe和JavaEE的区别
|
7月前
|
存储 前端开发 算法
毕业季--JavaSE高级面试题
毕业季--JavaSE高级面试题
|
7月前
|
Java 大数据
Java大数据面试复习30天冲刺 - 日积月累,每日五题【Day04】——JavaSE
Java大数据面试复习30天冲刺 - 日积月累,每日五题【Day04】——JavaSE
68 0
|
7月前
|
存储 安全 Java
Java大数据面试复习30天冲刺 - 日积月累,每日五题【Day02】——JavaSE
Java大数据面试复习30天冲刺 - 日积月累,每日五题【Day02】——JavaSE
63 0
|
7月前
|
安全 JavaScript Java
JavaSE面试题(二)
JavaSE面试题(二)
|
XML 安全 Java
JavaSE基础面试题(精简版)
把CPU处理器与操作系统的整体叫平台
47 0
|
存储 SQL 安全
Java大数据面试复习30天冲刺 - 日积月累,每日五题【Day02】——JavaSE
接口不能用 new 实例化,但可以声明,但是必须引用一个实现该接口的对象 从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。
140 1
|
存储 前端开发 Java
毕业季--JavaSE高级面试题
毕业季--JavaSE高级面试题
毕业季--JavaSE高级面试题

热门文章

最新文章