答:
• 数据传输方式不同:GET请求通过URL传输数据,而POST的数据通过请求体传输。
• 安全性不同:POST的数据因为在请求主体内,所以有一定的安全性保证,而GET的数据在URL中,通过历史记录,缓存很容易查到数据信息。
• 数据类型不同:GET只允许 ASCII 字符,而POST无限制。
• GET无害: 刷新、后退等浏览器操作GET请求是无害的,POST可能重复提交表单。
• 特性不同:GET是安全(这里的安全是指只读特性,就是使用这个方法不会引起服务器状态变化)且幂等(幂等的概念是指同一个请求方法执行多次和仅执行一次的效果完全相同),而POST是非安全非幂等。
• 简单来说:GET产生一个TCP数据包,POST产生两个TCP数据包。
严格的说:对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);而对于POST请求。浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。
步骤:
①输入网址:输入URL。
②缓存解析:获取URL后解析,先去服务器缓存中查看是否有目标网页的缓存,如果有就从缓存中加载(缓存就是把之前访问过的js、css、图片等资源存储在系统磁盘或内存中),没有就进行下一步。
④TCP连接,三次握手:域名解析后,浏览器向服务器发送http请求,此时就要进行TCP三次握手连接。
⑤服务器收到请求:TCP连接建立成功后,服务器收到浏览器发送的请求信息(包含请求头和请求体),返回一个响应头和响应体。
⑥页面渲染:浏览器收到服务器返回的响应头和响应体,进行客户端渲染,生成Dom树,解析css样式,进行js交互等。
答:
三次握手
四次挥手
【问题一】为什么使用三次握手?
答:为了防止已失效的连接请求报文段突然又传送到了服务端,使服务端一直等待,浪费资源。
——谢希仁著《计算机网络》第四版
谢希仁版《计算机网络》中的例子是这样的,“已失效的连接请求报文段”的产生在这样一种情况下:client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。”主要目的防止server端一直等待,浪费资源。
【问题二】为什么连接的时候是三次握手,关闭的时候却是四次握手?
答:因为当Server端收到Client端的SYN连接请求报文后,可以将SYN+ACK放在同一个报文中发送(其中ACK报文是用来应答的,SYN报文是用来同步的)。但是关闭连接时,当Server端收到FIN报文时,仅仅表示对方没有数据发送过来了,而并不会立即关闭SOCKET(因为数据可能还没有发送完),所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。
【问题三】为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?
答:虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假想网络是不可靠的,有可能最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。
【问题四】为什么不能用两次握手进行连接?
答:3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。
现在把三次握手改成仅需要两次握手,可能会发生死锁。作为例子,考虑计算机S和C之间的通信,假定C给S发送一个连接请求分组,S收到了这个分组,并发送了确认应答分组。按照两次握手的协定,S认为连接已经成功地建立了,可以开始发送数据分组。可是,C在S的应答分组在传输中被丢失的情况下,将不知道S 是否已准备好,不知道S建立什么样的序列号,C甚至怀疑S是否收到自己的连接请求分组。在这种情况下,C认为连接还未建立成功,将忽略S发来的任何数据分组,只等待连接确认应答分组。而S在发出的分组超时后,重复发送同样的分组。这样就形成了死锁。
【问题五】如果已经建立了连接,但是客户端突然出现故障了怎么办?
答:TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
文章略长,
TCP协议对应于传输层,而HTTP协议对应于应用层,从本质上来说,二者没有可比性。
①http对应于应用层。
②TCP协议对应于传输层。
③http协议是在TCP协议之上建立的,http在发起请求时通过TCP协议建立起连接服务器的通道,请求结束后,立即断开TCP连接。
说明:从HTTP/1.1起,默认都开启了Keep-Alive,保持连接特性,简单地说,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。
④http是无状态的短连接,而TCP是有状态的长连接。
https是安全版的http,因为http协议的数据都是明文进行传输的,所以对于一些敏感信息的传输就很不安全,HTTPS就是为了解决HTTP的不安全而生的。
HTTPS = HTTP + SSL(Secure Sockets Layer 安全套接字协议)
①客户端向服务器发起HTTPS的请求,连接到服务器的443端口;
②服务器将非对称加密的公钥传递给客户端,以证书的形式回传到客户端;
③服务器接收到该公钥进行验证,就是验证2中证书,如果有问题,则HTTPS请求无法继续;如果没有问题,则上述公钥是合格的。(第一次HTTP请求)客户端这个时候随机生成一个私钥,称为client key 客户端私钥,用于对称加密数据的。使用前面的公钥对client key进行非对称加密;
④进行二次HTTP请求,将加密之后的client key传递给服务器;
⑤服务器使用私钥进行解密,得到client key,使用client key对数据进行对称加密
⑥将对称加密的数据传递给客户端,客户端使用非对称解密,得到服务器发送的数据,完成第二次HTTP请求。
文章略长,
LVS:是基于四层的转发,介于网络层和运输层之间
Nginx:可以做七层的转发,是web服务器,缓存服务器,反向代理服务器
HA:负载均衡器,是基于四层和七层的转发
CP通过序列号、检验和、确认应答信号、重发控制、连接管理、窗口控制、流量控制、拥塞控制实现可靠性。
UDP通过实现确认机制、重传机制、窗口确认机制实现可靠性。
1)进程
(2)线程
线程是CPU调度和分派的基本单位,它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
(3)联系
线程是进程的一部分,一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。
(4)区别:理解它们的差别,我从资源使用的角度出发。(所谓的资源就是计算机里的中央处理器,内存,文件,网络等等)
根本区别:①进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位
②在开销方面:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。
③所处环境:在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行)
④内存分配方面:系统在运行的时候会为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源。
⑤包含关系:没有线程的进程可以看做是单线程的,如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。
①先来先服务调度算法FCFS
先到的进程先调度,执行过程不会被中断直到进程结束。
优点:易于实现,且相当公平。
缺点:比较有利于长进程,而不利于短进程。
②短作业优先调度算法SJF
优先分配给短进程执行。
优点:平均周转时间最短,进程等待时间缩短,可以增大系统吞吐量。
缺点:难以准确预估进程执行时间,开销较大;不利于长进程,有可能“饥饿”现象。
③高响应比调度算法HRRN
一种关于先来先服务和短作业优先的折中算法,当一个长进程等待时间过长,就会获得较高的优先权,因此不会出现“饥饿”现象。
优先级D=(执行时间+等待时间)/执行时间
优点:不会出现“饥饿”现象,长作业也有机会被调度。
缺点:每次都需要计算优先级,系统开销大。
④时间片轮转调度算法RR
为进程设定时间片,即每个进程运行的时间,在一个时间片结束时,发生时钟中断,调度程序暂停执行并加入队尾,通过上下文切换执行当前队首进程
优点:算法简单,响应时间短。
缺点:不利于处理紧急作业;时间片过小会导致频繁进程上下文切换,增大系统开销;时间片过长则会退化为FCFS。
①目的
页是信息的物理单位,分页是为实现离散分配方式,以消减内存的外零头,提高内存的利用率。或者说,分页是出于系统管理的需要而不是用户需要。
段是信息的逻辑单位,它含有一组其意义相对完整的信息。分段的目的是为了更好地满足用户的需要。
②长度
段的长度不固定,决定于用户所编写的程序,通常由编译程序在对程序进行编译时,根据信息的性质来划分。
④碎片
分页有内部碎片无外部碎片
分段有外部碎片无内部碎片
⑥管理方式
对于分页,操作系统必须为每个进程维护一个页表,以说明每个页对应的的页框。当进程运行时,它的所有页都必须在内存中,除非使用覆盖技术或虚拟技术,另外操作系统需要维护一个空闲页框列表。
特别的,当使用虚拟技术是,把一页或一段写入内存时可能需要把一页或几个段写入磁盘。
⑦共享和动态链接
分页不容易实现,分段容易实现
操作系统(daoOperating System,简称OS)是一管理电脑硬件与软件资源的程序,同时也是计算机系专统的内核与基石。操作系属统是一个庞大的管理控制程序,大致包括5个方面的管理功能:进程与处理机管理、作业管理、存储管理、设备管理、文件管理。
定义:多个进程在执行过程中,因争夺同类资源且资源分配不当而造成的一种互相等待的现象,若无外力作用,它们都将永远无法继续执行,这种状态称为死锁,这些处于等待状态的进程称为死锁进程。
产生条件:
互斥:一个资源每次只能被一个进程使用(资源独立)
请求与保持:一个进程因请求资源而阻塞时,对已获得的资源保持不放(不释放锁)
不剥夺:进程已获得的资源,在未使用之前,不能强行剥夺(抢夺资源)
循环等待:若干进程之间形成一种头尾相接的循环等待的资源关闭(死循环)
避免死锁:
①加锁顺序(线程按照一定的顺序加锁)
②加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
③死锁检测
银行家算法:
①操作系统按照银行家指定的规则为进程分配资源,当进程首次申请资源时,需要测试该进程对资源的最大需求量,如果系统现存的资源可以满足它的最大需求量则按当前的申请资源分配资源,否则就推迟分配;
②当进程在执行中继续申请资源时,先测试该进程本次申请的资源数,是否超过了该资源剩余的总量,若超过则拒绝分配资源,若能满足则按当前的申请量分配资源,否则也要推迟分配。
①管道:速度慢,容量有限,只有父子进程能通讯
②FIFO:任何进程间都能通讯,但速度慢
③消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题
④信号量:不能传递复杂消息,只能用来同步
⑤共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存
1).线程有两种实现方式,一种是实现Runnable接口,一种是继承Thread类,但不管怎样,当我们new了这个对象后,线程就进入了初始状态;
2).当该对象调用了start()方法,就进入可运行状态;
3).进入可运行状态后,当该对象被操作系统选中,获得CPU时间片就会进入运行状态;
4).进入运行状态后情况就比较复杂了
4.1、run()方法或main()方法结束后,线程就进入终止状态;
4.2、当线程调用了自身的sleep()方法或其他线程的join()方法,就会进入阻塞状态(该状态既停止当前线程,但并不释放所占有的资源)。当sleep()结束或join()结束后,该线程进入可运行状态,继续等待OS分配时间片;
4.3、线程调用了yield()方法,意思是放弃当前获得的CPU时间片,回到可运行状态,这时与其他进程处于同等竞争状态,OS有可能会接着又让这个进程进入运行状态;
4.4、当线程刚进入可运行状态(注意,还没运行),发现将要调用的资源被synchroniza(同步),获取不到锁标记,将会立即进入锁池状态,等待获取锁标记(这时的锁池里也许已经有了其他线程在等待获取锁标记,这时它们处于队列状态,既先到先得),一旦线程获得锁标记后,就转入可运行状态,等待OS分配CPU时间片;
4.5、当线程调用wait()方法后会进入等待队列(进入这个状态会释放所占有的所有资源,与阻塞状态不同),进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒(由于notify()只是唤醒一个线程,但我们由不能确定具体唤醒的是哪一个线程,也许我们需要唤醒的线程不能够被唤醒,因此在实际使用时,一般都用notifyAll()方法,唤醒有所线程),线程被唤醒后会进入锁池,等待获取锁标记。
区别:同步是一种更为复杂的互斥,而互斥是一种特殊的同步。
也就是说互斥是两个线程之间不可以同时运行,他们会相互排斥,必须等待一个线程运行完毕,另一个才能运行,而同步也是不能同时运行,但他是必须要安照某种次序来运行相应的线程(也是一种互斥)!
总结:
互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。
方式:
①通过Object的wait和notify
②通过Condition的awiat和signal
③通过一个阻塞队列
④通过两个阻塞队列
⑤通过SynchronousQueue
⑥通过线程池的Callback回调
⑦通过同步辅助类CountDownLatch
⑧通过同步辅助类CyclicBarrier
①指代不同
动态链接库:是微软公司在微软Windows操作系统中,实zhi现共享函数库概念的一种dao方式。
静态链接库:函数和数据被编译进一个二进制文件(通常扩展名为*.LIB),Visual C++的编译器在链接过程中将从静态库中恢复这些函数和数据并把他们和应用程序中的其他模块组合在一起生成可执行文件。
②特点不同
动态链接库:库函数的扩展名是 ”.dll"、".ocx"(包含ActiveX控制的库)或者 ".drv"(旧式的系统驱动程序)。
静态链接库:使用的.lib文件,库中的代码最后需要连接到可执行文件中去。
③调用方法不同
动态链接库:提供了一种使进程可以调用不属于其可执行代码的函数。函数的可执行代码位于一个 DLL 文件中,该 DLL 包含一个或多个已被编译、链接并与使用它们的进程分开存储的函数。
静态链接库:用程序所需的全部内容都是从库中复制了出来,所以静态库本身并不需要与可执行文件一起发行。
方法1:
从头开始遍历整个单链表,每遍历一个新节点,就把它与之前遍历过的节点进行比较,如果值相同,那么就认为这两个节点是一个节点,则证明链表有环,停止遍历,否则继续遍历下一个节点,重复刚才的操作,直到遍历结束。
方法2:
这种方法用到了HashSet中add方法去重的特点,思路是这样的:
①new一个HashSet,用来存储之前遍历过的节点值
②从头节点head开始,依次遍历链表中的节点,并把它add到集合中
③如果在集合中已经有一个相同的值,那么会返回false,这样便证明链表有环,退出遍历
①二叉树的每个节点最多有两个子节点,B树是M叉平衡的多路搜索树
②二叉树每个节点只存储一个关键字,B树每个节点存储M/2到M个关键字
区别 | B树 | B+树 |
1 | 搜索关键字不能重复存储 | 搜索关键字可以重复 |
2 | 数据可以存储在叶子节点和内部节点中 | 数据只能存储在叶子节点上 |
3 | 由于可以在内部节点和叶子节点上找到数据,所以搜索较慢 | 由于数据只能在叶节点上找到,所以搜索速度相对较快 |
4 | 删除内部节点既复杂又耗时 | 删除不复杂,因为元素从叶节点删除 |
5 | 叶子节点不能连接在一起 | 叶子节点连在一起,使搜索操作更加高效 |
线性探测再散列---放入元素,如果发生冲突,就往后找没有元素的位置
平方探测再散列---如果发生冲突,放到(冲突+1^2)的位置,如果还发生冲突,就放到(冲突-1^2)的位置;如果还有人就放到(冲突+2^2)的位置,以此类推,要是负数就倒序数。
②拉链法:如果发生冲突,就继续往前一个元素上链接,形成一个链表。类似于HashMap。
③再哈希:如果发生冲突,就用另一个方法计算hashcode,两次结果值如果不同就不会发生哈希冲突。
④建立公共溢出区:将哈希表分为基本表和溢出表两部分,范式和基本表发生冲突的元素,一律填入溢出表。
①AVL树是严格的平衡二叉树,红黑树是弱平衡二叉树;
②AVL树满足所有节点的左右子树高度相差不能超过1,红黑树确保最长路径不会超过最短路径的2倍;
③如果应用场景中插入删除不频繁,只是对查找要求较高,那么AVL树还是优于红黑树。
基于霍夫曼编码的压缩算法;数据压缩。
①数组随机访问性强,查找效率高;链表不能随机查找,必须从第一个开始遍历,查找效率低;
②数组插入和删除效率低,链表插入删除效率高;
③数组可能浪费内存;链表内存利用率高,不会浪费内存;
④数组长度固定,不能动态扩展;链表长度不固定,拓展很灵活;
思路一:使用堆排序,时间复杂度为O(nlogn),然后遍历一遍进行计数,输出前100条记录;
思路二:设计一个散列函数,将发生哈希冲突的数据放入一个Hashmap,根据冲突次数进行排序,得到出现次数最多的前一百条记录。
实现了List接口的所有集合:ArrayList,LinkedList
TreeSet:基于TreeMap实现
LinkedHashMap:在HashMap的基础上多了一个双向链表
TreeMap:基于红黑树实现
ConcurrentSkipListMap:基于跳表实现,跳表内所有元素都是有序的
首先要说一下JVM内存空间分为五部分,分别是:方法区、堆、Java虚拟机栈、本地方法栈、程序计数器。
方法区主要用来存放类信息、类的静态变量、常量、运行时常量池等,方法区的大小是可以动态扩展的。
堆主要存放的是数组、类的实例对象、字符串常量池等。
线程私有区
线程共享区
①引用计数算法(Reference counting)
每个对象在创建的时候,就给这个对象绑定一个计数器。每当有一个引用指向该对象时,计数器加一;每当有一个指向它的引用被删除时,计数器减一。这样,当没有引用指向该对象时,该对象死亡,计数器为0,这时就应该对这个对象进行垃圾回收操作。
②标记–清除算法(Mark-Sweep)
为每个对象存储一个标记位,记录对象的状态(活着或是死亡)。
分为两个阶段,一个是标记阶段,这个阶段内,为每个对象更新标记位,检查对象是否死亡;第二个阶段是清除阶段,该阶段对死亡的对象进行清除,执行 GC 操作。
③标记–整理算法
标记-整理法是标记-清除法的一个改进版。同样,在标记阶段,该算法也将所有对象标记为存活和死亡两种状态;不同的是,在第二个阶段,该算法并没有直接对死亡的对象进行清理,而是将所有存活的对象整理一下,放到另一处空间,然后把剩下的所有对象全部清除。这样就达到了标记-整理的目的。
④复制算法
该算法将内存平均分成两部分,然后每次只使用其中的一部分,当这部分内存满的时候,将内存中所有存活的对象复制到另一个内存中,然后将之前的内存清空,只使用这部分内存,循环下去。
这个算法与标记-整理算法的区别在于,该算法不是在同一个区域复制,而是将所有存活的对象复制到另一个区域内。
垃圾收集器:Serial收集器、ParNew收集器、CMS收集器、
G1简介:G1是一款非常优秀的垃圾收集器,不仅适合堆内存大的应用,同时也简化了调优的工作。通过主要的参数初始和最大堆空间、以及最大容忍的GC暂停目标,就能得到不错的性能;同时,我们也看到G1对内存空间的浪费较高,但通过**首先收集尽可能多的垃圾(Garbage First)**的设计原则,可以及时发现过期对象,从而让内存占用处于合理的水平。
(1) Synchronized(java自带的关键字)
(2) lock 可重入锁 (可重入锁这个包java.util.concurrent.locks 底下有两个接口,分别对应两个类实现了这个两个接口:
(a)lock接口, 实现的类为:ReentrantLock类 可重入锁;
(b)readwritelock接口,实现类为:ReentrantReadWriteLock 读写锁)
线程安全的集合:Vector、HashTable、StringBuffer、ConcurrentHashMap
区别:ConcurrentHashMap融合了HashTable和HashMap二者的优势。
HashTable每次同步执行的时候都要锁住整个结构,而ConcurrentHashMap锁的方式是稍微细粒度的,每次请求只需要锁住目标数据所在的桶;HashTable只能允许一个线程进入,而ConcurrentHashMap默认允许同时16个线程进入,并发性显著提高。
1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
5)Lock可以提高多个线程进行读操作的效率。
在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。
不可重入锁:实现简单,只判断这个锁有没有被锁上,只要被锁上申请锁的线程都会被要求等待。
可重入锁:实现相对复杂,不仅判断锁有没有被锁上,还会判断锁是谁锁上的,当锁是自己上的时候,那么他依旧可以再次访问临界资源,并把加锁次数加一。
设计了加锁次数,以在解锁的时候,可以确保所有加锁的过程都解锁了,其他线程才能访问。不然没有加锁的参考值,也就不知道什么时候解锁,解锁多少次,才能保证本线程已经访问完临界资源了可以唤醒其他线程访问了。
①corePoolSize 核心线程大小
线程池中最小的线程数量,即使处理空闲状态,也不会被销毁,除非设置了allowCoreThreadTimeOut。
②maximumPoolSize 线程池最大线程数量
一个任务被提交后,首先会被缓存到工作队列中,等工作队列满了,则会创建一个新线程,处理从工作队列中的取出一个任务。
③keepAliveTime 空闲线程存活时间
当线程数量大于corePoolSize时,一个处于空闲状态的线程,在指定的时间后会被销毁。
④unit 空间线程存活时间单位
keepAliveTime的计量单位
⑤workQueue 工作队列
新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。
jdk中提供了四种工作队列:
1)ArrayBlockingQueue
基于数组的有界阻塞队列,按FIFO排序。
2)LinkedBlockingQuene
基于链表的无界阻塞队列(其实最大容量为Interger.MAX),按照FIFO排序。
3)PriorityBlockingQueue
具有优先级的无界阻塞队列,优先级通过参数Comparator实现。workQueue 工作队列,jdk中提供了四种工作队列
新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。
⑥threadFactory 线程工厂
创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等等
⑦handler 拒绝策略
当工作队列中的任务已满并且线程池中的线程数量也达到最大,这时如果有新任务提交进来,拒绝策略就是解决这个问题的,jdk中提供了4中拒绝策略:
1)CallerRunsPolicy
该策略下,在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务。
2)AbortPolicy
该策略下,直接丢弃任务,并抛出RejectedExecutionException异常。
3)DiscardPolicy
该策略下,直接丢弃任务,什么都不做。
4)DiscardOldestPolicy
该策略下,抛弃最早进入队列的那个任务,然后尝试把这次拒绝的任务放入队列。
线程池的好处:
①线程池的重用
线程的创建和销毁的开销是巨大的,而通过线程池的重用大大减少了这些不必要的开销,当然既然少了这么多消费内存的开销,其线程执行速度也是突飞猛进的提升。
②控制线程池的并发数
控制线程池的并发数可以有效的避免大量的线程池争夺CPU资源而造成堵塞
③线程池可以对线程进行管理
线程池可以提供定时、定期、单线程、并发数控制等功能。比如通过ScheduledThreadPool线程池来执行S秒后,每隔N秒执行一次的任务。
CachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
FixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
ScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
SingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
1)加载
“加载”是“类加载”(Class Loading)过程的一个阶段。在加载阶段,虚拟机需要完成以下3件事情:
①通过一个类的全限定名来获取定义此类的二进制字节流。
②将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
③在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
2)验证
验证是链接阶段的第一步,这一步主要的目的是确保class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身安全。
验证阶段主要包括四个检验过程:文件格式验证、元数据验证、字节码验证和符号引用验证。
3)准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。这个阶段中有两个容易产生混淆的知识点,首先是这时候进行内存分配的仅包括类变量(static 修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在java堆中。
4)解析
解析阶段是虚拟机常量池内的符号引用替换为直接引用的过程。
符号引用:符号引用是一组符号来描述所引用的目标对象,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标对象并不一定已经加载到内存中。
直接引用:直接引用可以是直接指向目标对象的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机内存布局实现相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同,如果有了直接引用,那引用的目标必定已经在内存中存在。
5)初始化
类的初始化阶段是类加载过程的最后一步,在准备阶段,类变量已赋过一次系统要求的初始值,而在初始化阶段,则是根据程序员通过程序制定的主观计划去初始化类变量和其他资源,或者可以从另外一个角度来表达:初始化阶段是执行类构造器< clinit >()方法的过程。
原理:jvm通过字节码class文件,生成相应的对象。
应用:生成动态代理,面向切片编程(在调用方法的前后各加栈帧),SpringIOC等
1)自定义类加载器,重写loadClass方法;
2)使用线程上下文类加载器。
1)保持内存可见性
2)防止指令重排
①同步
②while轮询方式
③wait/notify方式
④管道通信
CAS操作包括了三个操作数,分别是需要读取的内存位置(V)、进行比较的预期值(A)和拟写入的新值(B),操作逻辑是,如果内存位置V的值等于预期值A,则将该位置更新为新值B,否则不进行操作。另外,许多CAS操作都是自旋的,意思就是,如果操作不成功,就会一直重试,直到操作成功为止。
可能产生的问题:ABA问题、高竞争下的开销问题、自身的功能限制。
①在Java程序中String类型是使用最多的,这就牵扯到大量的增删改查,每次增删改差之前其实jvm需要检查一下这个String对象的安全性,就是通过hashcode,当设计成不可变对象时候,就保证了每次增删改查的hashcode的唯一性,也就可以放心的操作。
③字符串值是被保留在常量池中的,也就是说如果字符串对象允许改变,那么将会导致各种逻辑错误。
1)以String作为HashMap的key,String的不可变保证了hash值的不可变。
2)String作为网络连接的参数,它的不可变性提供了安全性。
3)String不可变,所以线程安全。
4)字符串放在常量池中,当有多个引用指向同样的String字符串时,不需要额外创建对象
相同点:一旦执行方法,都可以使当前线程进入阻塞状态。
不同点:1)两个方法声明的位置不同:Thread类中声明sleep(),Object类中声明wait();
2)调用的要求不同:sleep()可以在任何需要的场景下调用,wait()必须使用在同步代码块或同步方法中;
3)如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁(即同步监视器),wait()会释放锁。
相同点:
不同点:
我理解的面向对象就是把现实世界中的事物都抽象为程序设计中的对象,其基本思想是一切皆对象,是一种自下而上的设计模式,先设计组件,再完成组装。
面向对象有三个特性:封装、继承和多态。
其有五个基本原则:单一职责原则、开放封闭原则、里氏替换原则、依赖倒置原则、接口分离原则。
面向对象的优点是易维护、易扩展、易复用,使用面向对象的思想可以设计出低耦合的系统。
其缺点是效率相比面向过程低。
附 五大基本原则的解释:
普通索引、唯一索引、聚集索引、主键索引、全文索引
数据库的底层索引是用B树和B+树实现的:索引本身也很大,因此索引往往是以索引文件的形式存储在磁盘上。所以,索引查找的过程就会产生磁盘的I/O操作,相比于内存存取,I/O存取消耗要高几个数量级,所以索引的优劣最重要的指标就是在查找过程中的磁盘I/O存取次数。
因为B+树的内节点去掉了data域,因此有更大的出度,而且它是根据索引关键字进行查找,不经过分支结点,而是沿着下一叶子的指针就可遍历所有的关键字。
选用B+树,原因:
1)利用Hash需要把数据全部加载到内存中,如果数据量大,是一件很消耗内存的事,而采用B+树,是基于按照节点分段加载,由此减少内存消耗。
2)和业务场景有段,对于唯一查找(查找一个值),Hash确实更快,但数据库中经常查询多条数据,这时候由于B+数据的有序性,与叶子节点又有链表相连,他的查询效率会比Hash快的多。
· MyISAM:默认的MySQL插件式存储引擎,它是在Web、数据仓储和其他应用环境下最常使用的存储引擎之一。注意,通过更改STORAGE_ENGINE配置变量,能够方便地更改MySQL服务器的默认存储引擎。
· InnoDB:用于事务处理应用程序,具有众多特性,包括ACID事务支持。(提供行级锁)
· BDB:可替代InnoDB的事务引擎,支持COMMIT、ROLLBACK和其他事务特性。
· Memory:将所有数据保存在RAM中,在需要快速查找引用和其他类似数据的环境下,可提供极快的访问。
· Merge:允许MySQL DBA或开发人员将一系列等同的MyISAM表以逻辑方式组合在一起,并作为1个对象引用它们。对于诸如数据仓储等VLDB环境十分适合。
· Archive:为大量很少引用的历史、归档、或安全审计信息的存储和检索提供了完美的解决方案。
· Federated:能够将多个分离的MySQL服务器链接起来,从多个物理服务器创建一个逻辑数据库。十分适合于分布式环境或数据集市环境。
· Cluster/NDB:MySQL的簇式数据库引擎,尤其适合于具有高性能查找要求的应用程序,这类查找需求还要求具有最高的正常工作时间和可用性。
· Other:其他存储引擎包括CSV(引用由逗号隔开的用作数据库表的文件),Blackhole(用于临时禁止对数据库的应用程序输入),以及Example引擎(可为快速创建定制的插件式存储引擎提供帮助)。
一般来说不使用事务的话,请使用MyISAM引擎,使用事务的话,一般使用InnoDB。
比较常用的引擎是MYISAM,InnoDB和Memory
InnoDB:支持事务处理,支持外键,支持崩溃修复能力和并发控制。如果需要对事务的完整性要求比较高(比如银行),要求实现并发控制(比如售票),那选择InnoDB有很大的优势。如果需要频繁的更新、删除操作的数据库,也可以选择InnoDB,因为支持事务的提交(commit)和回滚(rollback)。
MYISAM:插入数据快,空间和内存使用比较低。如果表主要是用于插入新记录和读出记录,那么选择MyISAM能实现处理高效率。如果应用的完整性、并发性要求比较低,也可以使用。
Memory:所有的数据都在内存中,数据的处理速度快,但是安全性不高。如果需要很快的读写速度,对数据的安全性要求较低,可以选择MEMOEY。它对表的大小有要求,不能建立太大的表。所以,这类数据库只使用在相对较小的数据库表。
使用explain+SQL语句命令查看语句的性能分析,然后再根据SQL语句选用合适的索引。
1)聚集索引一个表只能有一个,而非聚集索引一个表可以存在多个
2)聚集索引存储记录是物理上连续存在,而非聚集索引是逻辑上的连续,物理存储并不连续
3)聚集索引:物理存储按照索引排序;聚集索引是一种索引组织形式,索引的键值逻辑顺序决定了表数据行的物理存储顺序
4)非聚集索引:物理存储不按照索引排序;非聚集索引则就是普通索引了,仅仅只是对数据列创建相应的索引,不影响整个表的物理存储顺序.
5)索引是通过二叉树的数据结构来描述的,我们可以这么理解聚簇索引:索引的叶节点就是数据节点。而非聚簇索引的叶节点仍然是索引节点,只不过有一个指针指向对应的数据块。
MySQL数据库中的锁有共享锁、排他锁、行锁、表级锁、行级锁以及页面锁。
1)如果条件中有or,即使其中有条件带索引也不会使用(要尽量少用or);
PS:要想使用or,又想让索引生效,只能将or条件中的每个列都加上索引
2)对于多列索引,不是使用的第一部分,则不会使用索引;
3)like查询是以%开头;
4)如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引;
5)如果mysql估计使用全表扫描要比使用索引快,则不使用索引;
第一范式(1NF):要求数据库表的每一列都是不可分割的原子数据项。(满足原子性)
第二范式(2NF):在1NF的基础上,非码属性必须完全依赖于候选码(在1NF基础上消除非主属性对主码的部分函数依赖)
第二范式需要确保数据库表中的每一列都和主键相关,而不能只与主键的某一部分相关(主要针对联合主键而言)。
第三范式(3NF):在2NF基础上,任何非主属性不依赖于其它非主属性(在2NF基础上消除传递依赖)
第三范式需要确保数据表中的每一列数据都和主键直接相关,而不能间接相关。
原子性(Atomicity):原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚。失败回滚的操作事务,将不能对事物有任何影响。
一致性(Consistency):一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
隔离性(Isolation):隔离性是指当多个用户并发访问数据库时,比如同时访问一张表,数据库每一个用户开启的事务,不能被其他事务所做的操作干扰,多个并发事务之间,应当相互隔离。
持久性(Durability):持久性是指事务的操作,一旦提交,对于数据库中数据的改变是永久性的,即使数据库发生故障也不能丢失已提交事务所完成的改变。
1)set方法注入
2)构造器注入
3)静态工厂注入
4)实例工厂注入
1) 加载并且保存Spring配置文件路径信息然后保存到configLocation中
2)刷新Spring上下文环境
3)创建并且载入DefaultListableBeanFactory(即BeanFactory)
4)根据DefaultListableBeanFactory创建XMLBeanDefinitionReader,用于后面读取xml配置文件信息
5)创建BeanDefinitionDelegate代理类,用于解析xml配置信息
6)解析xml中配置的<import>、<bean>、<beans>、<alias>等不同的标签信息,以便于可以使用不同的解析器进行解析
7)通过XMLBeanDefinitionReader结合location路径信息读取Resources资源信息
8)使用BeanDefinitionDelegate代理类解析Bean元素并且依次进行实例化操作,实例化完毕之后将Bean信息注册(put)到BeanDefinitionMap中以便于可以下次继续使用。
一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态编织的方式,引入特定的语法创建“切面”,从而使得编译器可以在编译期间织入有关“切面”的代码,属于静态代理。
作用:
1)aop是面向切面编程,不同于java原始的oop是面向对象编程,使用aop可以实现不需要修改原功能代码,只需要修改一下配置,即可实现功能的扩展
2)aop采用的是横向抽取机制,取代了传统的纵向继承体系,减少了重复性代码
3)运行期间通过代理方式向目标类植入增强代码
4)经典的应用场景有事务的管理,安全检查,缓存,性能监控等等
1)配置xml文件
在xml文件里面,可以通过配置 lazy-init="true"来启用懒加载。如
<bean id="cart" class="cn.tedu.beans.Cart" lazy-init="true"></bean>
,这样,在容器启动的时候,就不会立即创建bean cart,直到第一次使用的时候才会创建。
2)注解配置(@Lazy)
在创建Bean的方法上添加@Lazy注解,使用@Lazy注解标注后,单实例bean对象只是在第一次从Spring容器中获取对象时创建,以后每次获取bean对象时,直接返回创建好的对象。
1)轮询(默认)
每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。
2)指定权重
指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况。
3)IP绑定 ip_hash
每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session的问题。
4)fair(第三方)
按后端服务器的响应时间来分配请求,响应时间短的优先分配。
5)url_hash(第三方)
按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,后端服务器为缓存时比较有效。
1)基于数据库实现分布式锁
2)基于缓存(redis,memcached,tair)实现分布式锁
3)基于Zookeeper实现分布式锁
CAP原则:指的是在一个分布式系统中,CAP三者不能同时实现,只能同时实现其中两个。CAP理论是NoSQL数据库的基础。
C(Consistency)一致性:在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)
A(Availablity)可用性:在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)
P(Partition Tolerance)分区容错性:以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。
BASE理论:BASE是Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)三个短语的简写,BASE是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的结论,是基于CAP定理逐步演化而来的,其核心思想是即使无法做到强一致性(Strong consistency),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)。
1)负载均衡服务器
用于接收请求并将请求均衡发送给应用服务器处理。
2)分布式消息队列服务器
用于多个应用之间的互相调用和通信(一般为异步)。
3)分布式缓存服务器
用于提供数据的频繁高速访问,减少直接访问DB的压力。
4)分布式数据存储服务器
用于数据的安全、快速存储。
其实想想也很简单,不管是大型分布式还是传统单体服务,不管怎么发展,对外提供的服务内容没有变化。只是为了应对海量大数据、高并发,部署形式发生了变化,需要考虑的方面无非以下几点:
1)消息如何接收和分发
2)应用之间如何互相调用通信
3)数据如何快速访问
4)数据如何安全快速存取
先查看注册中心,是不是某个服务down掉了,如果down掉了就重启那个服务模块。如果不能在注册中心找到问题所在,就去查看服务器日志,定位到问题所在,排查问题后重启服务。合理使用熔断、限流、降级、负载均衡等策略可以有效避免服务器宕机。