本想着一次性全部放上来,但是最后发现内容还是有点多,最后想想还是分为5块儿放上来吧~如果有需要的小伙伴可以自取哈!
另外,面试的问题较多,有些问题过于复杂,我就没有把答案写上来,大家可以针对性的搜索答案即可。
有些问题,在多次面试冲重复出现,我就简单的进行合并了,大家按顺序看即可。
可以使用synchronize关键字,对操作数进行加锁,或者使用volatile关键字,使得变量具有可见性。
线程之间的通信可以使用管道pipe进行通信,在Java中对应的就是pipedWriter和pipedReader进行通信,类似于生产者-消费者模式。
线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。
kafka的每个分区都有多个副本,如果一个broker宕机了,zookeeper会自动选择另一个副本作为主副本。在选择从副本的时候,使用ISR优先的原则来完成选举。
本地缓存会占用内存,会影响jvm的可使用内存的大小。
同一个消费者组内使用的是点到点,避免重复消费;
不同的消费者组之间,是订阅发布者模式。
每个主题内的消费者组,同组中的所有消费者不能订阅相同的分区。
TCP的报文格式如下所示:
3次握手过程如下:
syn-sent状态
,此时有同步号标志位SYN=1,发送的序列长度seq=x,没有确认号。SYN-RCVD
。在响应给客户端的数据报中,依旧需要将同步位设置为SYN=1,ACK=1
,并且设置对应的确认号ack=x+1
,表示下一次客户端可以从x+1的位置开始传输数据,同时也将自己的数据报的长度写入数据报中,seq=y
。经过三次握手之后,客户端与服务器端的连接就建立好了,双方开始正常的发送数据。
为什么要有第三次握手?
假如没有第三次握手过程,仅有两次。可能会出现这么一种情况:客户端第一次发送请求,但是由于网络不通畅,很久还没有到达服务器端,此时客户端会以为数据报丢失,重新发送一个新的请求连接的数据报,然后服务器接收到第二次的连接请求后,就和客户端建立连接。
但是过了一段时间之后,客户端第一次发送的请求连接数据报到达了服务器端,服务器端会做出响应,给客户端发送一个确认报文,然后就以为与客户端建立了连接,开始等待客户端发送数据。
可是客户端已经与服务器端建立了连接,便不会响应服务端第二次发送过来的响应报文,那么服务器端就会以为建立了新的连接,一直等待客户端传送数据,使得服务器端处于浪费资源的状态。
四次挥手过程:
客户端状态转换为FIN-WAIT1
服务器端的状态更改为CLOSE-WAIT
,客户端收到数据报之后,状态更改为FIN-WAIT2
服务器状态转换为LAST-ACK
。closed
为什么第四次挥手后,客户端不立即关闭?
假如第四次挥手的数据报在传输过程中丢失,那么服务器端将无法收到第四次挥手的报文,就会重新发送第三次挥手的报文给客户端,此时的客户端收到重新发送过来的挥手报文,会重新给服务器端发送第四次的挥手报文。
假如说客户端在发送完第四次挥手报文之后,立即关闭。那么上述情况发生之后,服务器端将无法收到关闭连接的确认请求,将会不断的向客户端发送第三次挥手请求,同时,客户端已经关闭,无法响应,那么服务器端将会一直处于LAST-ACK状态中,将永远无法释放此次的连接。
B+树是把所有的数据都存储在叶子节点中,每个叶子节点都会有一个页表,对应着叶子节点中的每条数据的索引值。
B+树的优点是,高度低,每次在非叶子节点做判断的时候,就可以直接知道下一次要前往哪个子节点去。由于树的高度低,并且所有的数据都是存放在硬盘中,这样就会使得我们把数据从硬盘读取到内存中的次数较少,提高效率。
在数据库的底层,会有一个undo.log和一个redo.log,在宕机之后,会从这两个log文件中进行恢复操作。与此同时在innodb中还会有MVCC来保证并发性更改数据同步。
在mysql中使用wal的方式进行数据存储,所有的数据在写入之前,首先将命令写入到redo.log文件中。开机时,会检查磁盘中的文件与redo.log的文件中记录的数据是否一致。在使用undo.log文件的时候,使用的就是撤销操作,对每次的操作命令记录其对应的逆操作命令,比如:add命令,就对应着delete命令,在事务内部依次执行即可保证事务的原子性。
①使用标志位的方式使得整个线程运行完run方法
②使用interrupt()方法中断当前线程
③直接使用stop方法,但是这种方法目前已经被弃用,是一种安全的方法,类似于直接断电的方式
标记复制,标记整理,标记清除
具体的流程在下面的这种图中,当加载一个类的时候,首先是将其交给父类加载器进行加载,如果父类加载器无法加载,然后再依次向下进行传递。
在jvm规范中,只分为两类类加载器,第一类是系统类加载器:启动类加载器(由c编写)。第二类是用户类加载器:除了启动类加载器都属于这一类。
优势:
.class
。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全。.class
不能被篡改。通过委托方式,不会去篡改核心.clas
,即使篡改也不会去加载,即使加载也不会是同一个.class
对象了。.class
也不是同一个Class
对象。这样保证了Class
执行安全。在Redis中可以使用一个队列来实现限流操作。比如每来一次请求,查看队首的时间,如果队首时间与当前时间间隔超过1秒,则移除队首元素,把当前元素添加在尾部,如果间隔不到一秒,再判断队列中的请求个数,如果没有超过5万,则加入队列,否则阻塞等待,否则。
java中的免锁容器主要实现的原理是:对容器的修改可以与读取操作同时发生。
concurrentHashMap使用的是分段锁的技术,保证读取时可以有较高的速度,并在写的时候利用写时复制技术,保证整体的线程安全。
treeMap中的键值对是有序存储(此处的有序是指按照自然序进行排序)。
hashmap中是无序的。
hash碰撞无论是什么算法都是无法完全避免的,只能使用好的hash算法来降低hash碰撞的次数。
当发生hash碰撞的时候,hashmap使用的是散列表(数组+链表)的方式来进行解决,在jdk1.8之后,还加入了红黑树来提升效率。
王坚(阿里云创始人),多隆,褚霸,吴翰清
ArrayList 底层实现就是数组,且ArrayList实现了RandomAccess,表示它能快速随机访问存储的元素,通过下标 index 访问,数组支持随机访问, 查询速度快, 增删元素慢; LinkedList 底层实现是链表, LinkedList 没有实现 RandomAccess 接口,链表支持顺序访问, 查询速度慢, 增删元素快.
Arraylist 根据下标查询,顺序存储知道首个元素的地址,其他的位置很快就能确定,时间复杂度为O(1); Linkedlist 从首个元素开始查找,直到查找到第 i个位置,时间复杂度为O(n);
Arraylist插入指定位置元素,后面元素需要向后移动,时间复杂度为O(n); Linkedlist插入指定位置元素,直接指针操作(设置前驱结点和后驱节点)时间复杂度为O(1);
Arraylist删除指定位置元素,后面元素需要向前移动,时间复杂度为O(n); Linkedlist删除指定位置元素,直接指针操作(将前驱结点和后驱结点相连),时间复杂度O(1);
容量不够,首先创建一个新的容量数组,再将原来数组复制到新的数组中去,释放旧数组。
传进来的索引小于集合size/2就从头节点开始遍历查询,反之从尾节点反向遍历查询;
TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。数据传输时,应用程序向TCP层发送数据流,TCP就会将接受到的数据流切分成报文段(会根据当前网络环境来调整报文段的大小),然后经过下面的层层传递,最终传递给目标节点的TCP层。为了防止丢包,TCP协议会在数据包上标有序号,对方收到则发送ACK确认。未收到则重传。
这个步骤就是我们通常所说的TCP建立连接的三次握手。同时TCP会通过奇偶校验和的方式来校验数据传输过程中是否出现错误。
第一次握手:当客户端需要去建立连接时,客户端就会发送SYN包(seq=x)到服务器,然后客户端进入SYN_SEND
的状态,代表已经发SYN包过去, 并且在等待服务器确认。此时ACK=0,SYN=1
,这时候由于才第一次握手,所以没有ACK标志。第二次握手:服务器收到SYN包,就会进行确认,由上面的标志位介绍我们可以知道SYN是表示同步序号,这时候会使得ack=x+1
, 然后服务器也会像客户端发送一个SYN包(seq=y),这时候也就是服务器会发送SYN+ACK包,来表示服务器确认到了客户端的一次握手并且二次握手建立,此时服务器进入SYN_RECV
状态。此时SYN=1,ACK=1,这时候由于是第二次握手,所以就会有一个服务器给客户端的确认标志。第三次握手:客户端收到服务器的SYN+ACK包,然后就会像服务器发送确认包ACK(ack=k+1
)和seq=x+1
,等到这个包发送完毕之后,客户端和服务器就会进入ESTABLISHED
状态,完成三次握手,而后就可以在服务端和客户端之间传输数据。此时SYN标志位已经不需要,因为当我们发送ACK标志位的时候代表三次握手成功,已经建立完连接了,接下来可以传送数据过去了。
我们在发布或者修改一个帖子的时候,会使用kafka来监听,告诉elasticsearch进行写入操作,通过这种方式来保证我们elasticsearch可以进行实时的搜索。
kafka同时支持两种消息投递模式:
我们是将帖子的内容content和帖子的标题title作为索引。具体如下所示:
package com.nowcoder.community.entity;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import java.util.Date;
//@Document注解用于配置elasticSearch的内容,依次为:索引名称,类型,分片数量,副本数量
@Document(indexName = "discusspost", type = "_doc", shards = 6, replicas = 3)
public class DiscussPost {
@Id
private int id;
@Field(type = FieldType.Integer)
private int userId;
//第二个analyzer,是存储拆分器,将存入的内容,尽可能查分成为多个词语
//第三个属性searchAnalyzer,是搜索拆分器,智能的将搜索内容查分成具有较好语义的几个词语
@Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
private String title;
@Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
private String content;
@Field(type = FieldType.Integer)
private int type;
@Field(type = FieldType.Integer)
private int status;
@Field(type = FieldType.Date)
private Date createTime;
@Field(type = FieldType.Integer)
private int commentCount;
@Field(type = FieldType.Double)
private Double score;
@Override
public String toString() {
return "DiscussPost{" +
"id=" + id +
", userId='" + userId + '\'' +
", title='" + title + '\'' +
", content='" + content + '\'' +
", type=" + type +
", status=" + status +
", createTime=" + createTime +
", commentCount=" + commentCount +
", score=" + score +
'}';
}
}
线程池一开始不会创建核心线程数量,而是没来一个任务,就创建一个线程。
这里需要注意
一点,在线程池中的线程数量尚未达到核心线程数量之前,每次来一个任务,不管此时有没有空闲的可用线程,线程池都会新创建一个线程来给新任务使用。
线程池创建的7大参数我们直接找到源码,如下所示:
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @param threadFactory the factory to use when the executor
* creates a new thread
* @param handler the handler to use when execution is blocked
* because the thread bounds and queue capacities are reached
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue}
* or {@code threadFactory} or {@code handler} is null
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = (System.getSecurityManager() == null)
? null
: AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
(1)数组+链表+红黑树。
(2)安全问题有两个:
put值
的时候,发生hash碰撞,然后同时将对应的数组中的next指针指向自己,这样就会导致有一个线程的插入值被覆盖transfer方法
,在transfer方法之后只会有一个线程对结果生效,假如A、B线程都对resize之后的链表中插入了值,那么将会导致有一个线程插入的值失效。(3)死锁问题
在1.8之前,在使用transfer的时候使用头插法
,也就是链表的顺序会翻转,这样在A线程插入到一半时临时挂起,此时B线程同时也去更改,这样就会产生一种循环链表的问题。造成最后的死循环。
在jdk1.8之后,改用了尾插法
,使得转移前后链表中的元素顺序不会发生改变,所以就解决了死锁问题。
1.7版本:segment+hashEntrys
1.8版本:散列表+红黑树
首先使用分段锁+cas算法+volatile,实现整体的不加锁。在put方法的时候,首先检查是否已经初始化,如果没有初始化,则进行初始化工作,然后使用rehash对键的hash值进行再散列,然后使用(n-1)& hash值,获取元素所在table的位置。
get方法不加锁,直接获取,我们可以发现,Node节点是重写的,设置了volatile关键字修饰
,致使它每次获取的都是最新设置的值。
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
volatile V val;
volatile Node<K,V> next;
Node(int hash, K key, V val) {
this.hash = hash;
this.key = key;
this.val = val;
}
....
}
网络传输层:TCP/UDP
应用层:http/dns/stmp/pop3
https://www.cnblogs.com/yinrw/p/10694474.html
在GET请求的URL中发送查询字符串(名称/值对),需要这样写:
/test/demo_form.php?name1=value1&name2=value2
按照请求的目的的不同,我们将每种功能的请求区分开,比如使用get请求做索引,put请求刷新,post请求做增加等等
cookie存放在客户端,不安全,存量小,
session存放在服务器端,安全,存量大
token将用户信息进行加密作为令牌进行网络传输
http://www.mybatis.cn/archives/764.html
ORM是Object和Relation之间的映射,包括Object->Relation和Relation->Object两方面。Hibernate是个完整的ORM框架,而MyBatis完成的是Relation->Object,也就是其所说的Data Mapper Framework。
JPA是ORM框架标准,主流的ORM框架都实现了这个标准。MyBatis没有实现JPA,它和ORM框架的设计思路不完全一样。MyBatis是拥抱SQL,而ORM则更靠近面向对象,不建议写SQL,实在要写,则推荐你用框架自带的类SQL代替。MyBatis是SQL映射框架而不是ORM框架,当然ORM和MyBatis都是持久层框架。
进程属于系统中的概念,属于操作系统分配给每个执行程序的最小资源单位,一段执行代码运行起来之后叫做进程。进程中包括了系统的内存空间,寄存器等等信息。
线程属于进程中的最小执行单元,一个进程包括有多个线程,线程是除了进程中的内存空间,其他变量信息以外的东西。
创建方式:Thread,Runnable,Callable
3: monitorenter //进入同步方法
//..........省略其他
15: monitorexit //退出同步方法
ACC_SYNCHRONIZED
,每当有线程进入的时候,会首先检查该标志位是否已经被访问
public
synchronized
void
syncTask();
descriptor: ()V
//方法标识ACC_PUBLIC代表public修饰,ACC_SYNCHRONIZED指明该方法为同步方法
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: dup
2: getfield #2
// Field i:I
5: iconst_1
6: iadd
7: putfield #2
// Field i:I
10: return
LineNumberTable:
line 12: 0
line 13: 10
总共4类,如下所示:
ExecutorService service = Executors.newFixedThreadPool(5);// 固定容量的线程池
ExecutorService service = Executors.newSingleThreadExecutor();// 容量为1的线程
ExecutorService service = Executors.newCachedThreadPool();//根据当前任务量自动调节池中线程数
ScheduledExecutorService service = Executors.newScheduledThreadPool(5);//定时任务的线程池
String 安全,被final修饰,当我们写如下代码的时候:
String a = "anc";
String b = "efg";
String c = a + b;
上面的代码将两个字符串相连时,底层是使用了StringBuilder
类来进行转换。
ArrayList不安全,可以使用CopyOnWriteArrayList这个类是安全的。
线程通信方式:
两种。myisam和innodb
只有A = *** 会使用到
隔离级别:
幻读使用的是间隙锁:gap lock + next key lock 解决
因为每次都要更新索引,时间复杂度较大,占用内存较多。会额外的增加IOP频率
https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485303&idx=1&sn=9e4626a1e3f001f9b0d84a6fa0cff04a&chksm=cea248bcf9d5c1aaf48b67cc52bac74eb29d6037848d6cf213b0e5466f2d1fda970db700ba41&token=1667678311&lang=zh_CN#rd
单例模式,代理模式,工厂模式、模板方法模式、观察者
使用的是hashMap进行实现的
HashCode的String值