博主之前在郑州上班,3年开发经验,后来打算去帝都发展,但是这几年由于郑州所用技术和北京所要求的还是有差距的(二线城市技术相对来说要落后个3-5年吧),所以白天求生存晚上求发展,利用晚上和周末的时间去学习近几年比较前沿的技术。
经过1年左右的准备(博主是从java基础开始又复习了一遍)确实技术水平提升了不少,后来为了准备面试在网上搜索互联网相关的面试题,(我之前是做传统行业的开发,现在想往互联网方向发展)。终于找到一套知识点涵盖比较全的面试题,但是美中不足的就是这套面试题只有面试相关知识的提问点而没有对应的答案,无奈只能自己在网上找对应的答案。有时候一个面试题可能要花费博主将近2个小时的时间(先在网上找相关的博客,然后经过一番研究之后根据自己的理解把其中的精髓用自己的话简要的总结出来)。因此很多答案都是比较长的,因为可能一个答案会来自好几个人的帖子内容或自己的理解。因此这些答案比较适合去深入理解这个知识点,而不是作为死记硬背的内容。其实还是挺庆幸没有答案的,因为在自己总结整理答案的过程中,可以让自己对于这些知识点有更深一步的理解。
最后,博主终于在今年年初的时候如愿在帝都找到了一份互联网开发的工作(面试第二天就拿到了2个offer)。现在真的很感谢曾经努力的自己。现在博主把整理的这份知识点分享出来,希望可以帮助更多有需要的人,内容涵盖Java基础、常用框架、多线程、网络通信、Linux、数据库(MySql)、设计模式、算法、缓存、学习中遇到问题汇总十大部分。
这份文档最初是在word里边整理的,很多我认为重要的地方都加粗和字体标红了,但是当我整体复制到CSDN保存之后不知道为什么字体格式都变成黑白的了。由于内容太多我也就不一一加粗和字体标红了,毕竟这是根据博主自己对知识点的理解去加粗和字体标红的,毕竟每个人对同一个知识点的理解也都不一样(一千个人心中就有一千个哈姆雷特),所以就黑白将就吧(哈哈)。如果确实想要我标注的版本的话(重要的地方字体加粗和标红了),在本篇博客评论区留下您的邮箱地址,我会抽时间统一发送的!
本篇博客会不定期更新,后边我会陆续加入(spring boot、spring cloud、Zookeeper、Kafka、Elasticsearch 、RabbitMQ、Jenkins、Docker等技术)的相关知识点总结。另外欢迎大家转载,但是请注明出处,这样方便我给其他有需要的朋友发送word版本的资料。
下边还是放一张word版的效果图吧
一、Java基础
1.String类为什么是final的。
1.设计需求
java设计者不希望用户定义类去继承String类,所以定义为final类型。final修饰类时,类不可被继承;修饰变量,变量的值不可以被修改;修饰方法,方法不可被子类重写。做这样的规则规定,为了代码更严谨
String类中的成员属性也几乎都设计成了private final的,这样String就被设计成一个不变类,这样有助于共享,提高性能。可以将字符串对象保存在字符串常量池中以供与字面值相同字符串对象共享。如果String对象是可变的,那就不能这样共享,因为一旦对某一个String类型变量引用的对象值改变,将同时改变一起共享字符串对象的其他String类型变量所引用的对象的值。
Java设计出于安全性的考虑,不变的数据对于线程安全是有用的。
线程安全:基本类型传值对象传引用,记住这一点。既然传引用,两个变量就有可能指向同一个String。而在String可变情况下,我要是通过一个变量来更改String,那么另一个变量取到的String也就变了!为什么?因为两个变量指向同个String。试想在多线程里,这会是多么可怕的一件事情:一个线程正在处理一个String,发现自己处理的String竟然变了!
2.HashMap的源码,实现原理,底层结构。
HashMap实现原理
HashMap的主干是一个Entry数组。Entry是HashMap的基本组成单元,每一个Entry包含一个key-value键值对。
底层结构如下图:
简单来说,HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的。
如果定位到的数组位置不含链表(当前entry的next指向null),那么对于查找,添加等操作很快,仅需一次寻址即可;
如果定位到的数组包含链表,对于添加操作,其时间复杂度依然为O(1),因为最新的Entry会插入链表头部,急需要简单改变引用链即可,而对于查找操作来讲,此时就需要遍历链表,然后通过传入key与数组中key对象的hash值和equals方法逐一比对查找,同时成立时完成匹配(具体如下图红色标记)。所以,性能考虑,HashMap中的链表出现越少,性能才会越好。
remove删除数组下的链表时,只需改变链表对象entry内的next属性引用即可。
存储位置(哈希表的索引)的确定流程是这样的:
个人理解:hashmap中的put、get、remove方法其实本质都是通过key找到哈希表中的对应的对象entry,然后通过操作对象内的属性value来实现hashmap的操作。
为何HashMap的数组长度一定是2的次幂?
HashMap()一开始构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空 HashMap,当数组中的元素大于16*0.75=12个的时候,数组数量扩展为之前长度的2倍。之所以扩展2倍(只能扩展2倍)的原因是为了保证得到的新的数组索引和老数组索引一致(大大减少了之前已经散列良好的老数组的数据位置重新调换),个人理解。
3.反射中,Class.forName和classloader的区别
类的实例化与类的初始化是两个完全不同的概念:
· new实例化对象时如果类没有进行过初始化,则需要先对其进行初始化;
· 实例化:类的实例化是指创建一个类的实例(对象)的过程;
· 初始化:激活类的静态变量的初始化Java代码和静态Java代码块,并初始化程序员设置的变量值,是类生命周期中的一个阶段。
Java类装载过程
Class.forName(className)方法,内部调用的方法是 Class.forName(className,true,classloader);
第2个boolean参数表示类是否需要初始化, Class.forName(className)默认是需要初始化。
一旦初始化,就会触发目标对象的 static块代码执行,static参数也也会被再次初始化。
ClassLoader.loadClass(className)方法,内部调用方法ClassLoader.loadClass(className,false);
第2个 boolean参数,表示目标对象是否进行链接,false表示不进行链接,由上面介绍可以,不进行链接意味着不进行包括初始化等一些列步骤,那么静态块和静态对象就不会得到执行
一般情况下,这两个方法效果一样,都能装载Class。
但如果程序依赖于Class是否被初始化,就必须用Class.forName(name)了。
举例说明他们各自的使用方法:
java使用JDBC连接数据库时候,我们首先需要加载数据库驱动。
Class.forName("com.mysql.jdbc.Driver");//通过这种方式将驱动注册到驱动管理器上
Connection conn = DriverManager.getConnection("url","userName","password");//通过驱动管理器获得相应的连接
查看com.mysql.jdbc.Driver源码:
Class.forName("com.mysql.jdbc.Driver")方法以后,他会进行class的初始化,执行static代码块。
也就是说class初始化以后,就会将驱注册到DriverManageer上,之后才能通过DriverManager去获取相应的连接。
但是要是我们使用ClassLoader.loadClass(com.mysql.jdbc.Driver)的话,不会link,更也不会初始化class。
相应的就不会回将Driver注册到DriverManager上面,所以肯定不能通过DriverManager获取相应的连接。
4.session和cookie的区别和联系,session的生命周期,多个服务部署时session管理。
cookie 和session 的区别和联系:
1、cookie数据存放在客户的浏览器上,session数据放在服务器上(session不是保存在内存中,而是保存在文件或数据库中)
2、cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗
考虑到安全应当使用session。
3、session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能
考虑到减轻服务器性能方面,应当使用COOKIE。
4、单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。
5,session 的运行依赖 session id,而 session id 是存在 cookie 中的,也就是说,如果浏览器禁用了 cookie ,同时 session 也会失效(但是可以通过其它方式实现,比如在 url 中传递 session_id、表单隐藏域传递)
6、所以个人建议:
将登陆信息等重要信息存放为SESSION
其他信息如果需要保留,可以放在COOKIE中
session的生命周期:
创建:Sessinon在用户访问第一次访问服务器时创建,需要注意只有访问JSP、Servlet等程序时才会创建Session,只访问HTML、IMAGE等静态资源并不会创建Session,可调用request.getSession(true)强制生成Session。
Session什么时候失效?
1. 服务器会把长时间没有活动的Session从服务器内存中清除,此时Session便失效。Tomcat中Session的默认失效时间为30分钟。
2. 调用Session的invalidate方法。
Session对浏览器的要求:
虽然Session保存在服务器,对客户端是透明的,它的正常运行仍然需要客户端浏览器的支持。这是因为Session需要使用Cookie作为识别标志。HTTP协议是无状态的,Session不能依据HTTP连接来判断是否为同一客户,因此服务器向客户端浏览器发送一个名为JSESSIONID的Cookie,它的值为该Session的id(也就是HttpSession.getId()的返回值)。Session依据该Cookie来识别是否为同一用户。
对于不支持Cookie的解决方案:URL地址重写:
URL地址重写是对客户端不支持Cookie的解决方案。URL地址重写的原理是将该用户Session的id信息重写到URL地址中。服务器能够解析重写后的URL获取Session的id。这样即使客户端不支持Cookie,也可以使用Session来记录用户状态。HttpServletResponse类提供了encodeURL(String url)实现URL地址重写,该方法会自动判断客户端是否支持Cookie。如果客户端支持Cookie,会将URL原封不动地输出来。如果客户端不支持Cookie,则会将用户Session的id重写到URL中。
多个服务部署时session管理:
1.通过数据库mysql共享session
a.采用一台专门的mysql服务器来存储所有的session信息。
用户访问随机的web服务器时,会去这个专门的数据库服务器check一下session的情况,以达到session同步的目的。
缺点就是:依懒性太强,mysql服务器无法工作,影响整个系统;
2.通过cookie共享session
把用户访问页面产生的session放到cookie里面,就是以cookie为中转站。
当访问服务器A时,登录成功之后将产生的session信息存放在cookie中;当访问请求分配到服务器B时,服务器B先判断服务器有没有这个session,如果没有,在去看看客户端的cookie里面有没有这个session,如果cookie里面有,就把cookie里面的sessoin同步到web服务器B,这样就可以实现session的同步了。
缺点:cookie的安全性不高,容易伪造、客户端禁止使用cookie等都可能造成无法共享session。
3.通过memcache同步session
memcache可以做分布式,如果没有这功能,他也不能用来做session同步。他可以把web服务器中的内存组合起来,成为一个"内存池",不管是哪个服务器产生的sessoin都可以放到这个"内存池"中,其他的都可以使用。
优点:以这种方式来同步session,不会加大数据库的负担,并且安全性比用cookie大大的提高,把session放到内存里面,比从文件中读取要快很多。
缺点:memcache把内存分成很多种规格的存储块,有块就有大小,这种方式也就决定了,memcache不能完全利用内存,会产生内存碎片,如果存储块不足,还会产生内存溢出。
4.通过redis共享session
redis与memcache一样,都是将数据放在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。(支持持久化及key过期删除策略)
根据实际开发应用,一般选择使用memcache或redis方式来共享session.
5.Java中的队列都有哪些,有什么区别。
在并发编程中,一般推荐使用阻塞队列,这样实现可以尽量地避免程序出现意外的错误。
阻塞队列与普通队列的区别在于:当队列是空的时,从队列中获取元素的操作将会被阻塞,或者当队列是满时,往队列里添加元素的操作会被阻塞。试图从空的阻塞队列中获取元素的线程将会被阻塞,直到其他的线程往空的队列插入新的元素。同样,试图往已满的阻塞队列中添加新元素的线程同样也会被阻塞,直到其他的线程使队列重新变得空闲起来,如从队列中移除一个或者多个元素,或者完全清空队列。
非阻塞队列(ConcurrentLinkedQueue):线程安全
基于链接节点的、无界的、线程安全。此队列按照 FIFO(先进先出)原则对元素进行排序。队列的头部 是队列中时间最长的元素。队列的尾部 是队列中时间最短的元素。新的元素插入到队列的尾部,队列检索操作从队列头部获得元素。当许多线程共享访问一个公共 collection 时,ConcurrentLinkedQueue 是一个恰当的选择。此队列不允许 null 元素。
阻塞队列(LinkedBlockingQueue):线程安全
按 FIFO(先进先出)排序元素。队列的头部 是在队列中时间最长的元素。队列的尾部 是在队列中时间最短的元素。新元素插入到队列的尾部,并且队列检索操作会获得位于队列头部的元素。链接队列的吞吐量通常要高于基于数组的队列,但是在大多数并发应用程序中,其可预知的性能要低。
注意:
1、必须要使用take()方法在获取的时候达成阻塞结果
2、使用poll()方法将产生非阻塞效果
6.Java的内存模型以及GC算法
Java内存模型(简称JMM,JMM是隶属于JVM)决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。
JVM对Java内存模型的实现
对于一个对象的成员方法,这些方法中包含本地变量,仍需要存储在栈区,即使它们所属的对象在堆区。
对于一个对象的成员变量,不管它是原始类型还是包装类型,都会被存储到堆区。
Static类型的变量以及类本身相关信息都会随着类本身存储在堆区。
堆中的对象可以被多线程共享。如果一个线程获得一个对象的应用,它便可访问这个对象的成员变量。如果两个线程同时调用了同一个对象的同一个方法,那么这两个线程便可同时访问这个对象的成员变量,但是对于本地变量,每个线程都会拷贝一份到自己的线程栈中。
下图展示了上面描述的过程:
GC算法:
回收哪些对象?GC主要进行回收的内存是JVM中的方法区和堆
什么时候回收?已死的对象(主要用可达性分析判断一个对象是否还存在引用,如果该对象没有任何引用就应该被回收。);
怎么回收?
标记 -清除算法(老年代常用):如它的名字一样,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。(效率不高、产生内存碎片)
复制算法(新生代常用):它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。(简单高效,但是会将内存缩小为原来的一半)
标记-整理算法(老年代常用):标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存
分代收集算法:“分代收集”(Generational Collection)算法,把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或“标记-整理”算法来进行回收。
垃圾收集器(垃圾收集器就是内存回收GC算法的具体实现):
CMS收集器:CMS收集器是一种以获得最短停顿时间为目标的收集器。
G1收集器:从JDK1.7 Update 14之后的HotSpot虚拟机正式提供了商用的G1收集器,与其他收集器相比,它具有如下优点:并行与并发;分代收集;空间整合(不会产生内存碎片);可预测的停顿等。
7.Java7、Java8的新特性(baidu问的,好BT)
Java7 新特性:
1.switch中可以使用字符串了
2.运用List<String> tempList = new ArrayList<>();即泛型实例化类型自动推断
3.语法上支持集合,而不一定是数组
final List<Integer> list = [1,2,3,4,5,6]
4.新增一些取环境信息的工具方法
Java8新特性
1.Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可,这个特征又叫做扩展方法,示例如下:
2.lambda表达式
3.函数式接口
8.Java数组和链表两种结构的操作效率,在哪些情况下(从开头开始,从结尾开始,从中间开始),哪些操作(插入,查找,删除)的效率高
总结:
1、存取方式上,数组可以顺序存取或者随机存取,而链表只能顺序存取;
2、存储位置上,数组逻辑上相邻的元素在物理存储位置上也相邻,而链表不一定;
3、存储空间上,链表由于带有指针域,存储密度不如数组大;
4、按序号查找时,数组可以随机访问,时间复杂度为O(1),而链表不支持随机访问,平均需要O(n);
5、按值查找时,若数组无序,数组和链表时间复杂度均为O(1),但是当数组有序时,可以采用折半查找将时间复杂度降为O(logn);
6、插入和删除时,数组平均需要移动n/2个元素,而链表只需修改指针即可;
7、空间分配方面:
数组在静态存储分配情形下,存储元素数量受限制,动态存储分配情形下,虽然存储空间可以扩充,但需要移动大量元素,导致操作效率降低,而且如果内存中没有更大块连续存储空间将导致分配失败;
链表存储的节点空间只在需要的时候申请分配,只要内存中有空间就可以分配,操作比较灵活高效;
9.Java内存泄露的问题调查定位:jmap,jstack的使用等等
二、框架
1.struts1和struts2的区别
struts1是基于JSP和servlet的一个开源的Web应用框架,使用的是MVC的设计模式;
struts2是基于webwork技术的框架,是sun和webwork公司联手开发的一个功能非常齐全的框架,struts2和struts1没有任何关系,是一个全新的框架, 它吸收了struts1和webwork的优势。
2.struts2和springMVC的区别
3.spring框架中需要引用哪些jar包,以及这些jar包的用途
org.springframework.aop-3.0.6.RELEASE |
Spring的面向切面编程,提供AOP(面向切面编程)实现 |
org.springframework.aspects- 3.0.6.RELEASE |
Spring提供对AspectJ框架的整合 |
org.springframework.beans-3.0.6.RELEASE |
SpringIoC(依赖注入)的基础实现 |
org.springframework.core-3.0.6.RELEASE |
Spring3.0.6的核心工具包 |
org.springframework.expression-3.0.6.RELEASE |
Spring表达式语言 |
org.springframework.jdbc-3.0.6.RELEASE |
对JDBC的简单封装 |
org.springframework.orm-3.0.6.RELEASE |
整合第三方的ORM框架,如hibernate,ibatis,jdo,以及 spring的JPA实现 |
4.srpingMVC的原理
1. 客户端请求提交到DispatcherServlet
2. 由DispatcherServlet控制器查询一个或多个HandlerMapping,找到处理请求的Controller
3. DispatcherServlet将请求提交到Controller
4. Controller调用业务逻辑处理后,返回ModelAndView
5. DispatcherServlet查询一个或多个ViewResoler视图解析器,找到ModelAndView指定的视图
6. 视图负责将结果显示到客户端
5.springMVC注解的意思
@Controller
@Controller 负责注册一个bean 到spring 上下文中,bean 的ID 默认为
类名称开头字母小写,你也可以自己指定。
@RequestMapping
1.@RequestMapping用来定义访问的URL,你可以为整个类定义一个
@RequestMapping,或者为每个方法指定一个。
@PathVariable
1.@PathVariable用于方法中的参数,表示方法参数绑定到地址URL的模板
@ModelAttribute
1.应用于方法参数,参数可以在页面直接获取,相当于request.setAttribute(,)
2.应用于方法,将任何一个拥有返回值的方法标注上 @ModelAttribute,使其返回值将会进入到模型对象的属性列表中.
3.应用于方法参数时@ModelAttribute(“xx”),须关联到Object的数据类型,基本数据类型 如:int,String不起作用
@ResponseBody
这个注解可以直接放在方法上,表示返回类型将会直接作为HTTP响应字节流输出(不被放置在Model,也不被拦截为视图页面名称)。可以用于ajax。
@RequestParam
@RequestParam是一个可选参数,例如:@RequestParam(“id”) 注解,所以它将和URL所带参数 id进行绑定
defaultValue:String类型,参数没有传递时为参数默认指定的值
@SessionAttributes session管理
Spring 允许我们有选择地指定 ModelMap 中的哪些属性需要转存到 session 中,以便下一个请求属对应的 ModelMap 的属性列表中还能访问到这些属性。这一功能是通过类定义处标注 @SessionAttributes 注解来实现的。@SessionAttributes 只能声明在类上,而不能声明在方法上。
@CookieValue 获取cookie信息
@RequestHeader 获取请求的头部信息
6.spring中beanFactory和ApplicationContext的联系和区别
1. BeanFactory负责读取bean配置文档,管理bean的加载,实例化,维护bean之间的依赖关系,负责bean的生命周期。
2. ApplicationContext除了提供上述BeanFactory所能提供的功能之外,还提供了更完整的框架功能:
a. 国际化支持
b. 资源访问:Resource rs = ctx. getResource(“classpath:config.properties”), “file:c:/config.properties”
c. 事件传递:通过实现ApplicationContextAware接口
7.spring注入的几种方式
1. 接口注入(不推荐)
2. 属性注入getter,setter方式注入(比较常用)
3. 构造器注入(死的应用)
8.spring如何实现事物管理的
9.springIOC和AOP的原理
IoC(Inversion of Control):IoC就是应用本身不依赖对象的创建和维护而是交给外部容器(这里为spring),这要就把应用和对象之间解耦,控制权交给了外部容器。即Don’t call me ,I’ll call you!所以IoC也称DI(依赖注入)对象的创建和维护依赖于外部容器。
AOP(Aspect Oriented Programming):面向切面编程。就是把一些贯穿在各个模块之间相同的功能抽象出来,然后封装成一个面。
AOP一般都是通过代理来实现,利用代理就有目标对象是什么,拦截哪些点(方法),拦截后要做什么。
JoinPoint(连接点):被拦截到的点. Advice(通知):拦截JoinPoint之前与之后要做的事。
PointCut(切入点):对joinPoint进行拦截的定义。Target(目标对象):代理的目标对象。
对于异常处理,日志功能,权限的检查,事务等都是贯穿到各个模块之中,因此进行AOP.
代理技术有面向接口和生成子类.
10.hibernate中的1级和2级缓存的使用方式以及区别原理
Hibernate提供了两级缓存,第一级是Session的缓存是必需的。
第二级缓存是一个可插拔的的缓存插件,它是由SessionFactory负责管理。
11.spring中循环注入的方式
1. 接口注入(不推荐)
2. getter,setter方式注入(比较常用)
3. 构造器注入(死的应用)
三、多线程
1.Java创建线程之后,直接调用start()方法和run()的区别
start与run方法的主要区别在于当程序调用start方法一个新线程将会被创建,并且在run方法中的代码将会在新线程上运行,然而在你直接调用run方法的时候,程序并不会创建新线程,run方法内部的代码将在当前线程上运行。
2.常用的线程池模式以及不同线程池的使用场景
newCachedThreadPool(无界线程池,可以进行自动线程回收):
· 底层:返回ThreadPoolExecutor实例,corePoolSize为0;maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为60L;unit为TimeUnit.SECONDS;workQueue为SynchronousQueue(同步队列)
· 通俗:当有新任务到来,则插入到SynchronousQueue中,由于SynchronousQueue是同步队列,因此会在池中寻找可用线程来执行,若有可以线程则执行,若没有可用线程则创建一个线程来执行该任务;若池中线程空闲时间超过指定大小,则该线程会被销毁。
· 适用:执行很多短期异步的小程序或者负载较轻的服务器
newFixedThreadPool(固定大小线程池):
· 底层:返回ThreadPoolExecutor实例,接收参数为所设定线程数量nThread,corePoolSize为nThread,maximumPoolSize为nThread;keepAliveTime为0L(不限时);unit为:TimeUnit.MILLISECONDS;WorkQueue为:new LinkedBlockingQueue<Runnable>() 无界阻塞队列
· 通俗:创建可容纳固定数量线程的池子,每个线程的存活时间是无限的,当池子满了就不在添加线程了;如果池中的所有线程均在繁忙状态,对于新任务会进入阻塞队列中(无界的阻塞队列)
· 适用:执行长期的任务,性能好很多
newSingleThreadExecutor(单个后台线程):
· 底层:FinalizableDelegatedExecutorService包装的ThreadPoolExecutor实例,corePoolSize为1;maximumPoolSize为1;keepAliveTime为0L;unit为:TimeUnit.MILLISECONDS;workQueue为:new LinkedBlockingQueue<Runnable>() 无解阻塞队列
· 通俗:创建只有一个线程的线程池,且线程的存活时间是无限的;当该线程正繁忙时,对于新任务会进入阻塞队列中(无界的阻塞队列)
· 适用:一个任务一个任务执行的场景
NewScheduledThreadPool(延迟、定时或周期性执行任务线程):
· 底层:创建ScheduledThreadPoolExecutor实例,corePoolSize为传递来的参数,maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为0;unit为:TimeUnit.NANOSECONDS;workQueue为:new DelayedWorkQueue() 一个按超时时间升序排序的队列
· 通俗:创建一个固定大小的线程池,线程池内线程存活时间无限制,线程池可以支持定时及周期性任务执行,如果所有线程均处于繁忙状态,对于新任务会进入DelayedWorkQueue队列中,这是一种按照超时时间排序的队列结构
· 适用:周期性执行任务的场景
线程池任务执行流程:
1. 当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。
2. 当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行
3. 当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务
4. 当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理
5. 当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程
6. 当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭
备注:
一般如果线程池任务队列采用LinkedBlockingQueue队列的话,那么不会拒绝任何任务(因为队列大小没有限制),这种情况下,ThreadPoolExecutor最多仅会按照最小线程数来创建线程,也就是说线程池大小被忽略了。
如果线程池任务队列采用ArrayBlockingQueue队列的话,那么ThreadPoolExecutor将会采取一个非常负责的算法,比如假定线程池的最小线程数为4,最大为8所用的ArrayBlockingQueue最大为10。随着任务到达并被放到队列中,线程池中最多运行4个线程(即最小线程数)。即使队列完全填满,也就是说有10个处于等待状态的任务,ThreadPoolExecutor也只会利用4个线程。如果队列已满,而又有新任务进来,此时才会启动一个新线程,这里不会因为队列已满而拒接该任务,相反会启动一个新线程。新线程会运行队列中的第一个任务,为新来的任务腾出空间。
这个算法背后的理念是:该池大部分时间仅使用核心线程(4个),即使有适量的任务在队列中等待运行。这时线程池就可以用作节流阀。如果挤压的请求变得非常多,这时该池就会尝试运行更多的线程来清理;这时第二个节流阀—最大线程数就起作用了。
3.newFixedThreadPool此种线程池如果线程数达到最大值后会怎么办,底层原理。
· 底层:返回ThreadPoolExecutor实例,接收参数为所设定线程数量nThread,corePoolSize为nThread,maximumPoolSize为nThread;keepAliveTime为0L(不限时);unit为:TimeUnit.MILLISECONDS;WorkQueue为:new LinkedBlockingQueue<Runnable>() 无界阻塞队列
· 通俗:创建可容纳固定数量线程的池子,每个线程的存活时间是无限的,当池子满了就不在添加线程了;如果池中的所有线程均在繁忙状态,对于新任务会进入阻塞队列中(无界的阻塞队列)
· 适用:执行长期的任务,性能好很多
4.多线程之间通信的同步问题,synchronized锁的是对象,衍伸出和synchronized相关很多的具体问题,例如同一个类不同方法都有synchronized锁,一个对象是否可以同时访问。或者一个类的static构造方法加上synchronized之后的锁的影响。
问1:不同线程不能同时执行一个对象的不同synchronized方法
多个线程访问同一个类的synchronized方法时, 都是串行执行的 ! 就算有多个cpu也不例外 ! synchronized方法使用了类java的内置锁, 即锁住的是方法所属对象本身. 同一个锁某个时刻只能被一个执行线程所获取, 因此其他线程都得等待锁的释放. 因此就算你有多余的cpu可以执行, 但是你没有锁, 所以你还是不能进入synchronized方法执行, CPU因此而空闲. 如果某个线程长期持有一个竞争激烈的锁, 那么将导致其他线程都因等待所的释放而被挂起, 从而导致CPU无法得到利用, 系统吞吐量低下. 因此要尽量避免某个线程对锁的长期占有 !
问2:synchronized是对类的当前实例(当前对象)进行加锁,防止其他线程同时访问该类的该实例的所有synchronized块,注意这里是“类的当前实例”, 类的两个不同实例就没有这种约束了。
那么static synchronized恰好就是要控制类的所有实例的并发访问,static synchronized是限制多线程中该类的所有实例同时访问jvm中该类所对应的代码块。
其实总结起来很简单。
一个锁的是类对象,一个锁的是实例对象。
若类对象被lock,则类对象的所有同步方法全被lock;
若实例对象被lock,则该实例对象的所有同步方法全被lock。
5.了解可重入锁的含义,以及ReentrantLock 和synchronized的区别
可重入锁:从名字上理解,ReenTrantLock的字面意思就是再进入的锁,其实synchronized关键字所使用的锁也是可重入的,两者关于这个的区别不大。两者都是同一个线程每进入一次,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。
区别:
1. 锁的实现:Synchronized是依赖于JVM实现的,而ReenTrantLock是JDK实现的,有什么区别,说白了就类似于操作系统来控制实现和用户自己敲代码实现的区别。前者的实现是比较难见到的,后者有直接的源码可供阅读。
2. 性能的区别:自从Synchronized引入了偏向锁,轻量级锁(自旋锁)后,两者的性能就差不多了,在两种方法都可用的情况下,官方甚至建议使用synchronized
3. 便利性:便利性:很明显Synchronized的使用比较方便简洁,并且由编译器去保证锁的加锁和释放,而ReenTrantLock需要手工声明来加锁和释放锁,为了避免忘记手工释放锁造成死锁,所以最好在finally中声明释放锁。
4. 锁的细粒度和灵活度:很明显ReenTrantLock优于Synchronized
5.功能区别:ReentrantLock 拥有Synchronized相同的并发性和内存语义,此外还多了 锁投票,定时锁等候和中断锁等候; ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。
ReentrantLock使用场景:在确实需要一些 synchronized 所没有的特性的时候,比如时间锁等候、可中断锁等候、无块结构锁、多个条件变量或者锁投票。 ReentrantLock 还具有可伸缩性的好处,应当在高度争用的情况下使用它
6.同步的数据结构,例如concurrentHashMap的源码理解以及内部实现原理,为什么他是同步的且效率高
HashTable:将整个hashtable锁定,HashTable容器使用synchronized来保证线程安全,如线程1使用put进行添加元素,线程2不但不能使用put方法添加元素,并且也不能使用get方法来获取元素,所以竞争越激烈效率越低。
锁分段技术:一个ConcurrentHashMap由多个segment(段)组成,每一个segment都包含了一个HashEntry数组的hashtable, 每一个segment包含了对自己的hashtable的操作,比如get,put,replace等操作,这些操作发生的时候,对自己的hashtable进行锁定。由于每一个segment写操作只锁定自己的hashtable,所以可能存在多个线程同时写的情况,性能无疑好于只有一个hashtable锁定的情况。
7.atomicinteger和volatile等线程安全操作的关键字的理解和使用
atomicinteger(保证原子性)首先Atomic中的变量是申明为了volatile变量的,这样就保证的变量的存储和读取是一致的,都是来自同一个内存块,然后Atomic提供了getAndIncrement方法,该方法对变量的++(线程不安全的)操作进行了封装,并提供了compareAndSet(线程安全)方法,来完成对单个变量的加锁和解锁操作,从而保证原子操作。
使用场景:可以设置一个计数的变量,来统记线程被调用的次数。
Volatile(保证可见性,不能保证原子性)修饰的成员变量在每次被线程访问时,都强迫从共享内存重新读取该成员的值,而且,当成员变量值发生变化时,强迫将变化的值重新写入共享内存,这样两个不同的线程在访问同一个共享变量的值时,始终看到的是同一个值。
建议:当多个线程同时访问一个共享变量时,可以使用volatile,而当访问的变量已在synchronized代码块中时,不必使用。
缺点:使用volatile将使得VM优化失去作用,导致效率较低,所以要在必要的时候使用。
8.线程间通信,wait和notify
注意:wait(),notify(),notifyAll()都必须使用在同步中,因为要对持有监视器(锁)的线程操作。所以要使用在同步中,因为只有同步 才具有锁。使用场景:生产者与消费者模式。
为什么这些操作线程的方法要定义在object类中呢?
简单说:因为synchronized中的这把锁可以是任意对象,所以任意对象都可以调用wait()和notify();所以wait和notify属于Object。
专业说:因为这些方法在操作同步线程时,都必须要标识它们操作线程的锁,只有同一个锁上的被等待线程,可以被同一个锁上的notify唤醒,不可以对不同锁中的线程进行唤醒。
也就是说,等待和唤醒必须是同一个锁。而锁可以是任意对象,所以可以被任意对象调用的方法是定义在object类中。
注意:
1.notify( )方法只会通知等待队列中的第一个相关线程(不会通知优先级比较高的线程) 2.notifyAll( )通知所有等待该竞争资源的线程(也不会按照线程的优先级来执行)
3.假设有三个线程执行了obj.wait( ),那么obj.notifyAll( )则能全部唤醒tread1,thread2,thread3,但是要继续执行obj.wait()的下一条语句,必须获得obj锁,因此,tread1,thread2,thread3只有一个有机会获得锁继续执行,例如tread1,其余的需要等待thread1释放obj锁之后才能继续执行。
4.当调用obj.notify/notifyAll后,调用线程依旧持有obj锁,因此,thread1,thread2,thread3虽被唤醒,但是仍无法获得obj锁。直到调用线程退出synchronized块,释放obj锁后,thread1,thread2,thread3中的一个才有机会获得锁继续执行。
9.定时线程的使用
1.java.util.concurrent.DelayQueue(设计带有时间延迟特性的队列)这个设计的实质是定义了一个具有时间特性的线程任务列表,而且该列表可以是任意长度的。每次添加任务时指定启动时间即可。
2. Timer 、TimerTask:Timer是java.util包下的一个类,在JDK1.3的时候被引入,Timer只是充当了一个执行者的角色,真正的任务逻辑是通过一个叫做TimerTask的抽象类完成的,TimerTask也是java.util包下面的类,它是一个实现了Runnable接口的抽象类,包含一个抽象方法run( )方法,需要我们自己去提供具体的业务实现。
3.ScheduledThreadPoolExecutor(延迟、定时或周期性执行任务线程)(重点):在JDK1.5的时候在java.util.concurrent并发包下引入了ScheduledThreadPoolExecutor类,引入它的原因是因为Timer类创建的延迟周期性任务存在一些缺陷,ScheduledThreadPoolExecutor继承了ThreadPoolExecutor,并且实现了ScheduledExecutorService接口,ScheduledThreadPoolExecutor也是通过schedule方法执行Runnable任务的。
Timer和ScheduledThreadPoolExecutor的区别:
1. Timer类是通过单线程来执行所有的TimerTask任务的,如果一个任务的执行过程非常耗时,将会导致其他任务的时效性出现问题。而ScheduledThreadPoolExecutor是基于线程池的多线程执行任务,不会存在这样的问题。
2. Timer线程不捕获异常
3. Timer类的调度是基于绝对的时间的,而不是相对的时间,因此Timer类对系统时钟的变化是敏感的,举个例子,加入你希望任务1每个10秒执行一次,某个时刻,你将系统时间提前了6秒,那么任务1就会在4秒后执行,而不是10秒后。在ScheduledThreadPoolExecutor,任务的调度是基于相对时间的,原因是它在任务的内部存储了该任务距离下次调度还需要的时间(使用的是基于System#nanoTime实现的相对时间,不会因为系统时间改变而改变,如距离下次执行还有10秒,不会因为将系统时间调前6秒而变成4秒后执行)。
10.场景:在一个主线程中,要求有大量(很多很多)子线程执行完之后,主线程才执行完成。多种方式,考虑效率。
1.使用并发包下面的Future模式.(类似join系列方法)
(适用线程数为1或2个,线程多的话要调用很多的future的get方法)
Future是一个任务执行的结果, 他是一个将来时, 即一个任务执行, 立即异步返回一个Future对象, 等到任务结束的时候, 会把值返回给这个future对象里面. 我们可以使用 ExecutorService接口来提交一个线程.(注意:Future.get()为一个阻塞方法)。
2. CyclicBarrier(一组线程互相等待)默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。
实现原理:在CyclicBarrier的内部定义了一个Lock对象,每当一个线程调用CyclicBarrier的await方法时,将剩余拦截的线程数减1,然后判断剩余拦截数是否为0,如果不是,进入Lock对象的条件队列等待。如果是,执行barrierAction对象的Runnable方法,然后将锁的条件队列中的所有线程放入锁等待队列中,这些线程会依次的获取锁、释放锁,接着先从await方法返回,再从CyclicBarrier的await方法中返回。
3. CountDownLatch【重点】(倒计时锁,适用与多个线程的情况)
是一个倒数计数器, 给一个初始值(>=0), 然后每一次调用countDown就会减1, 这很符合等待多个子线程结束的场景: 一个线程结束的时候, countDown一次, 直到所有的线程都countDown(即countDown为0时)了 , 那么所有子线程就都结束了.。
CyclicBarrier和CountDownLatch的区别
· CountDownLatch的计数器只能使用一次。而CyclicBarrier的计数器可以使用reset() 方法重置。所以CyclicBarrier能处理更为复杂的业务场景,比如如果计算发生错误,可以重置计数器,并让线程们重新执行一次。
· CyclicBarrier主要用于一组线程之间的相互等待,而CountDownLatch一般用于一组线程等待另一组些线程。实际上可以通过CountDownLatch的countDown()和await()来实现CyclicBarrier的功能。即 CountDownLatch中的countDown()+await() = CyclicBarrier中的await()。注意:在一个线程中先调用countDown(),然后调用await()。
11.关于AbstractQueuedSynchronizer(AQS)(了解)
类如其名,抽象的队列式的同步器,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch...。
AQS定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)。
不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。
以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。
再以CountDownLatch以例,任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS减1。等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。
一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式,如ReentrantReadWriteLock。
四、网络通信
1.http是无状态通信,http的请求方式有哪些,可以自己定义新的请求方式么。
无状态:是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大(坏处)。另一方面,在服务器不需要先前信息时它的应答就较快(好处)。
HTTP请求的方法:
HTTP/1.1协议中共定义了八种方法(有时也叫“动作”),来表明Request-URL指定的资源不同的操作方式
1、OPTIONS
返回服务器针对特定资源所支持的HTTP请求方法,也可以利用向web服务器发送‘*’的请求来测试服务器的功能性
2、HEAD
向服务器索与GET请求相一致的响应,只不过响应体将不会被返回。这一方法可以再不必传输整个响应内容的情况下,就可以获取包含在响应小消息头中的元信息。
3、GET
向特定的资源发出请求。注意:GET方法不应当被用于产生“副作用”的操作中,例如在Web Application中,其中一个原因是GET可能会被网络蜘蛛等随意访问。Loadrunner中对应get请求函数:web_link和web_url
4、POST
向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。 Loadrunner中对应POST请求函数:web_submit_data,web_submit_form
5、PUT
向指定资源位置上传其最新内容
6、DELETE
请求服务器删除Request-URL所标识的资源
7、TRACE
回显服务器收到的请求,主要用于测试或诊断
8、CONNECT
HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
注意:
1)方法名称是区分大小写的,当某个请求所针对的资源不支持对应的请求方法的时候,服务器应当返回状态码405(Mothod Not Allowed);当服务器不认识或者不支持对应的请求方法时,应返回状态码501(Not Implemented)。
2)HTTP服务器至少应该实现GET/HEAD/POST方法,其他方法都是可选的,此外除上述方法,特定的HTTP服务器支持扩展自定义的方法。
WebCollector框架可以自定义http请求,自定义http请求只要override它们的getResponse方法即可。
2.socket通信,以及长连接,分包,连接异常断开的处理。
如果中途,被远程主机强制断开,可增加一个计时器,判断socket状态,如果不是连接状态,隔一段时间自动重连。
3.socket通信模型的使用,AIO和NIO。
目前为止,Java共支持3种网络编程模型:BIO、NIO、AIO(基本原理):
1. Java BIO : 同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
2. Java NIO : 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。具体如下图:
3. Java AIO(NIO.2) : 异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS(操作系统)先完成了再通知服务器应用去启动线程进行处理。
BIO、NIO、AIO适用场景分析:
1. BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
2. NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
3. AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS(操作系统)参与并发操作,编程比较复杂,JDK7开始支持。
4.socket框架netty的使用,以及NIO的实现原理,为什么是异步非阻塞。
基本原理:Java AIO(NIO.2) : 异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS(操作系统)先完成了再通知服务器应用去启动线程进行处理。
1.异步AIO是在数据读取或者写入调用已经完成的时候,再通知调用者;
同步NIO则是在有数据就绪,可以读写的时候通知调用者,读写仍然是由调用者执行并且是阻塞的。
2. AIO主要是针对进程在调用IO获取外部数据时,是否阻塞调用进程而言的。一个进程的IO调用步骤大致如下:
1、进程向操作系统请求数据
2、操作系统把外部数据加载到内核的缓冲区中,
3、操作系统把内核的缓冲区拷贝到进程的缓冲区 (以块的形式去获取数据,所以非阻塞)
4、进程获得数据完成自己的功能
5.同步和异步,阻塞和非阻塞。
同步IO和异步IO的区别就在于:数据访问的时候进程是否阻塞!
阻塞IO和非阻塞IO的区别就在于:应用程序的调用是否立即返回!
同步/异步主要针对C端:
同步:
所谓同步,就是在c端发出一个功能调用时,在没有得到结果之前,该调用就不返回。也就是必须一件一件事做,等前一件做完了才能做下一件事。
例如普通B/S模式(同步):提交请求->等待服务器处理->处理完毕返回 ,这个期间客户端浏览器不能干任何事
异步:
异步的概念和同步相对。当c端一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。
例如 ajax请求(异步): 请求通过事件触发->服务器处理(这是浏览器仍然可以作其他事情)->处理完毕
阻塞/非阻塞主要针对S端:
阻塞
阻塞调用是指调用结果返回之前,当前线程会被挂起(线程进入非可执行状态,在这个状态下,cpu不会给线程分配时间片,即线程暂停运行)。函数只有在得到结果之后才会返回。
非阻塞
非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。
同步/异步主要针对C端, 但是跟S端不是完全没有关系,同步/异步机制必须S端配合才能实现.同步/异步是由c端自己控制,但是S端是否阻塞/非阻塞, C端完全不需要关心.
五、Linux
1.常用的linux下的命令
ifconfig:查看网络配置
ls:列出目录内容 语法:ls 目录名
cd:切换目录 语法:cd 目录名
pwd:显示当前的所在目录。返回绝对路径
mkdir:创建目录语法:mkdir -p文件夹名称
cat:快捷查看当前文件的内容。
cp:需要复制的文件复制的位置。语法:cp被复制的文件路径 将要复制到的路径
find:查找文件。 语法:find 目录名 -name '需要查找的字符串'
grep:搜索、过滤信息。语法:grep -i需要搜索的字符串搜索的文件
more:分页显示。
rm:删除文件或目录。语法:删除文件rm 文件名;删除目录rm -rf目录名
who:显示登录用户信息。
mv:需要移动的文件移动的位置
ps:查看系统进程 语法:ps -ef|grep -i vim(查看进程中和 vim相关的进程)
kill:强制杀死某个进程:kill -9 pid号
tar:
压缩:(参数顺序不变)
tar -zcvf 压缩包名字.tar.gz需要压缩的内容
例如:tar -zcvf hehe.tar.gz * 将当前目录下所有内容进行打包压缩,文件名 hehe.tar.gz
解压:(参数顺序不变)
tar -zxvf 需要解压的压缩包名称解压到当前目录
tar -zxvf 需要解压的压缩包名称 -C指定压缩路径解压到指定目录中
vim:调用vi文本编辑器。
2.大的log文件中,统计异常出现的次数、排序,或者指定输出多少行多少列的内容。(主要考察awk)
3.linux下的调查问题思路:内存、CPU、句柄数、过滤、查找、模拟POST和GET请求等等场景
4.shell脚本中#!的作用
六、数据库MySql
1.MySql的存储引擎的不同
从MySQL5.5.5以后,InnoDB是默认引擎
(1)清空整个表时,InnoDB是一行一行的删除,效率非常慢。MyISAM则会重建表
(2)InnoDB支持行锁(些情况下还是锁整表,如 update table set a=1 where user like '%lee%)
选择何种存储引擎,视具体应用而定:
1)如果你的应用程序一定要使用事务或者以大批量的insert/update/delete为主的应用,毫无疑问你要选择INNODB引擎。但要注意,INNODB的行级锁是有条件的。在where条件没有使用主键(索引列)时,照样会锁全表。比如DELETE FROM mytable这样的删除语句。
2)如果你的应用程序对查询性能要求较高或者以select为主的应用,就要使用MYISAM了。MYISAM索引和数据是分开的,而且其索引是压缩的,可以更好地利用内存。所以它的查询性能明显优于INNODB。压缩后的索引也能节约一些磁盘空间。MYISAM拥有全文索引的功能,这可以极大地优化LIKE查询的效率。
2.单值索引、主键索引、联合索引
Mysql各种索引区别:
单值索引:即一个索引只包含单个列,一个表可以有多个单列索引。
唯一索引:与"普通索引"类似,不同的就是:索引列的值必须唯一,但允许有空值。
主键索引:它 是一种特殊的唯一索引,不允许有空值。
组合索引:为了更多的提高mysql效率可建立组合索引,遵循”最左前缀“原则。
全文索引:仅可用于 MyISAM 表,针对较大的数据,生成全文索引很耗时好空间。
3.Mysql怎么分表,以及分表后如果想按条件分页查询怎么办?(如果不是按分表字段来查询的话,几乎效率低下,无解)
一致性哈希和哈希区别:最关键的区别就是,对节点(存储服务器)和数据,都做一次哈希运算,然后比较节点和数据的哈希值,数据取和节点最相近的节点做为存放节点。这样就保证当节点增加或者减少的时候,影响的数据最少。
分表,水平切分:比如分256张表,事先建立好user_tab0~user_tab255此命名等256张表,根据user_id取模,user_id%256=[0~255],依据取模后的值调配存储到对应的user_tab[模值] 中。
分库,垂直切分:可以事先建立好user_db[0~19]_tab[0~255]这样命名规范的表,这个例子代表分20个库到每个库中的256张表。
分页查询:
1.如果只是为了分页,可以考虑这种分表,就是表的id是范围性的,且id是连续的,比如第一张表id是1到10万,第二张是10万到20万,这样分页应该没什么问题。
2.如果是其他的分表方式,建议用sphinx(基于SQL的全文检索引擎)先建索引,然后查询分页,我们公司现在就是这样干的。
4.分表之后想让一个id多个表是自增的,效率实现。
水平拆分后,同一张表的数据放在不同的库上,无法再依赖数据库本身的auto_increment实现ID的唯一性,多个库之间产生的ID会造成冲突,因此ID不应该由数据库来分配。
1. 通过MySQL表生成ID:创建一个全局表来维护每个表的当前最新id;(插入时需先获取最新id进行增加1后再插入数据,高并发会造成线程阻塞,效率不高)
2. 通过redis生成ID:通过redis的incr产生自增序列值,插入的时候指定id的值;
3. 队列方式redis:将一定量的ID预分配在一个队列里,每次插入操作,先从队列中获取一个ID,若插入失败的话,将该ID再次添加到队列中,同时监控队列数量,当小于阀值时,自动向队列中添加元素;
4. redis和db结合:使用redis直接操作内存,可能性能会好些。但是如果redis死掉后,如何处理呢?把两种结合一下稳定性会更好;
5.MySql的主从实时备份同步的配置,以及原理(从库读主库的binlog)及读写分离
主从复制:
读写分离:
如果你的后台结构是spring+mybatis,可以通过spring的AbstractRoutingDataSource和mybatis Plugin拦截器实现非常友好的读写分离,原有代码不需要任何改变。
6.写SQL语句。。。
面试官很喜欢给出一堆where子句,考索引、复合索引什么情况下起作用
7.索引的数据结构,B+树
索引:简单来说是排好序的快速查找的数据结构,一般指的是B树(多路搜索树)结构组织的索引。
二叉树:二叉树,每个结点只存储一个关键字,等于则命中,小于走左结点,大于
走右结点,所有关键字在整颗树中出现,且只出现一次,非叶子结点可以命中;
B+树:在B-树基础上,为叶子结点增加链表指针,所有关键字都在叶子结点
中出现,非叶子结点作为叶子结点的索引;B+树总是到叶子结点才命中;
b+树的查找过程
如上图,是一颗b+树,关于b+树的定义可以参见B+树,这里只说一些重点,浅蓝色的块我们称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(深蓝色所示)和指针(黄色所示),如磁盘块1包含数据项17和35,包含指针P1、P2、P3,P1表示小于17的磁盘块,P2表示在17和35之间的磁盘块,P3表示大于35的磁盘块。真实的数据存在于叶子节点即3、5、9、10、13、15、28、29、36、60、75、79、90、99。非叶子节点只不存储真实的
数据,只存储指引搜索方向的数据项,如17、35并不真实存在于数据表中。
b+树性质
1. 通过上面的分析,我们知道IO次数取决于b+数的高度h,假设当前数据表的数据为N,每个磁盘块的数据项的数量是m,则有h=㏒(m+1)N,当数据量N一定的情况下,m越大,h越小;而m = 磁盘块的大小 / 数据项的大小,磁盘块的大小也就是一个数据页的大小,是固定的,如果数据项占的空间越小,数据项的数量越多,树的高度越低。这就是为什么每个数据项,即索引字段要尽量的小,比如int占4字节,要比bigint8字节少一半。这也是为什么b+树要求把真实的数据放到叶子节点而不是内层节点,一旦放到内层节点,磁盘块的数据项会大幅度下降,导致树增高。当数据项等于1时将会退化成线性表。
2. 当b+树的数据项是复合的数据结构,比如(name,age,sex)的时候,b+数是按照从左到右的顺序来建立搜索树的,比如当(张三,20,F)这样的数据来检索的时候,b+树会优先比较name来确定下一步的所搜方向,如果name相同再依次比较age和sex,最后得到检索的数据;但当(20,F)这样的没有name的数据来的时候,b+树就不知道下一步该查哪个节点,因为建立搜索树的时候name就是第一个比较因子,必须要先根据name来搜索才能知道下一步去哪里查询。比如当(张三,F)这样的数据来检索时,b+树可以用name来指定搜索方向,但下一个字段age的缺失,所以只能把名字等于张三的数据都找到,然后再匹配性别是F的数据了, 这个是非常重要的性质,即索引的最左匹配特性。
8.事物的四个特性,以及各自的特点(原子、隔离)等等,项目怎么解决这些问题
9.行锁、表锁与索引的关系(自己加的)。
以mysql为例,有索引并且使用了该索引当条件的时候就是行锁,没有索引的时候就是表锁。innodb 的行锁是在有索引的情况下,没有索引的操作是锁定全表的。
原因:没有建立索引的话我们在进行数据选取或者定位的时候是通过全表扫描的形式来进行的,因此会锁定整张表。
10.项目中读写分离后事物的控制、数据延迟等的处理?
事物:因为事物只支持单库,所以需要先切换数据源再开启事物;
数据延迟:针对只是读的操作访问从库slave,而针对写和读的操作访问写库master;
七、设计模式(写代码)
1.单例模式:懒汉、饿汉、以及饿汉中的延迟加载(静态内部类式)、枚举式
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决:一个全局使用的类频繁地创建与销毁。
何时使用:当您想控制实例数目,节省系统资源的时候。
2.工厂模式(简单工程模式常用)
意图:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
主要解决:主要解决接口选择的问题。
何时使用:我们明确地计划不同条件下创建不同实例时。
3.原型模式(浅层clone、深层clone)
意图:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
主要解决:在运行期建立和删除原型。
4.适配器模式
意图:将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
主要解决:主要解决在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的。
5.代理模式(静态代理、动态代理)
静态代理:
动态代理:
意图:为其他对象提供一种代理以控制对这个对象的访问。
6.装饰者模式
意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
何时使用:在不想增加很多子类的情况下扩展类。
7.观察者模式
意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。
8.享元模式(线程池、连接池)
享元模式主要用于减少创建对象的数量,以减少内存占用和提高性能。它提供了减少对象数量从而改善应用所需的对象结构的方式。
意图:运用共享技术有效地支持大量细粒度的对象。
八、算法
1.一个数组的倒序(冒泡)
2.递归计算阶乘
3.反射机制
4. 线程的生产者消费者
Provider(生产者):
Consumer(消费者):
测试main函数:
九、缓存
1.为什么用缓存,用过哪些缓存,redis和memcache的区别
为什么要用缓存:为了缓解服务器频繁读数据库带来的内存资源消耗,redis将需要和数据库交互的信息暂存,当下次同样的http请求,就能直接读取redis里面的内容,而不用读数据库。这样减少了数据库压力又能提高服务器响应时间。
redis和memcache的区别
1、存储方式:memecache 把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小; Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。(持久化有快照和AOF日志两种持久化方式,在实际应用的时候,要特别注意配置文件快照参数,要不就很有可能服务器频繁满载做dump)。
2、数据支持类型:Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
3. Redis支持数据的备份,即master-slave模式的数据备份。
2.redis的数据结构
redis使用的是键值对保存数据。(map)
key:全部都是字符串
value:有五种数据类型
这里的数据结构指的是key-value中value的数据结构,分别为string、hash、list、set、有序set。
3.redis的持久化方式,以及项目中用的哪种,为什么
RDB:是redis的默认持久化机制。
RDB相当于照快照。保存的是一种状态。
20G数据----à几kb快照
优点:①快照保存数据速度极快,还原数据速度极快
②适用于灾难备份
缺点:①小内存机器不适合使用。
RDB机制符合要求就会照快照。(随时随地启动),会占用一部分系统资源(突然的),很可能内存不足直接宕机。(宕机后,服务器会关闭,非正常关闭) 服务器正常关闭时照快照 Key满足一定条件,照快照
适用于:内存比较充裕的计算机。
AOF:使用日志功能保存数据操作。
默认AOF机制关闭的。
每秒同步(默认):每秒进行一次AOF保存数据。安全性低,比较节省系统资源
每修改同步:只要有key变化语句,就进行AOF保存数据。比较安全,但是极为浪费效率
不同步:不进行任何持久化操作不安全
AOF操作:只会保存导致key变化的语句
优点:①持续性占用极少量的内存资源
缺点:①日志文件会特别大,不适用于灾难恢复
②恢复效率远远低于RDB
适用于:内存比较小的计算机
总结一下RDB和AOF
①、 如果非常在意数据,又希望快速的恢复数据,可以简单的使用RDB。
②、RDB持久化方式能够在指定的时间间隔内对你的数据进行快照存储。
③、AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候回重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾。Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。
④、只做缓存:如果只希望你的数据在服务器运行的时候存在,你也可以不适用任何持久化方式。
⑤、同时开启两种持久化方式:I、同时开启优先载入AOF文件来恢复原始的数据,因为通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。II、RDB的数据不实时,同时使用两者时服务器重启也只会找AOF文件。那要不要只使用AOF呢?建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份),快速重启,而且不会有AOF可能潜在的bug,留着作为一个万一的手段。
4.redis集群的理解,怎么动态增加或者删除一个节点,而保证数据不丢失。(一致性哈希问题)
缓存的本质是一个内存Hash表,网站应用中,数据缓存以一对Key、Value的形式存储在内存Hash表中。
普通哈希:求留余数法
求留余数法,是最简单的一种计算策略。它的计算过程,就是使用Hash表数据长度对HashCode求余数,将余数作为索引,使用该余数,直接设置或者访问缓存。
一致性Hash算法
一致性Hash算法通过一个叫做一致性Hash环的数据结构,实现KEY到缓存服务器的Hash映射。
当缓存服务器集群需要扩容的时候,只需要将新加入的节点的HashCode,放入一致性Hash环中,由于Key是顺时针查找距离最近的节点,因此,新加入的节点只影响整个环中的一小段。
请参见上图,如果我们新加入的服务器节点Node3,在Node1和Node2之间,如下图:
那么受影响的区域,只是Node2到Node3之间(顺时针)的缓存,此区间的缓存数据,加入节点之前是缓存在Node1上的,现在需要重新缓存到Node3上面。
具体应用中,这个长度为232的整数环,通常使用二叉查找树实现,Hash查找过程实际上是在二叉树中查找不小于查找树的最小值。当然,这个二叉树的最右边的叶子节点和最左边的叶子节点相连接,构成环。
一致性Hash算法的进化版
上述问题是,一致性Hash算法带来的负载均衡不均衡。我们可以通过增加虚拟层来实现。
我们将每台缓存服务器,虚拟为一组虚拟缓存服务器,将虚拟服务器的Hash值,放置在Hash环上,Key在环上先找到虚拟服务器节点,再得到物理服务器的信息。如下图:
这样,一台服务器节点,被环中多个虚拟节点所代表,且多个节点均匀分配。很显然,每个物理节点对应的虚拟节点越多,各个物理节点之间的负载越均衡,新加入物理服务器对原有的物理服务器的影响越保持一致。
十、项目中遇到的问题汇总:
关于逆向工程(2017-12-16):
1.逆向工程生成指定文件的包路径要与实际项目包路径一致!否则复制到实际项目应用时,mapper.xml映射的mapper(接口)、po(实体类)与实 际项目路径不符;
2.如果存在数据库多个用户有相同名字的表,映射的表名记得指定schema属性, 否则会重复加载表对象,导致启动tomcat的时候会报错
如<table schema="xxlims" tableName="departmentinfo"></table>
关于数据绑定(2017-12-17):
带enctype="multipart/form-data"属性的表单,默认以流的方式传参;
解决办法:需要配置multipartResolver来解析带enctype="multipart/form-data"属性的表单
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="utf-8"></property>
<property name="maxUploadSize" value="10485760000"></property>
<property name="maxInMemorySize" value="40960"></property>
</bean>
关于json转换jackson类库(2017-12-18):
问题原因:
org.springframework.http.converter.json.MappingJacksonHttpMessageConverter
Spring3升级到Spring4时, 运行时出现找不到MappingJacksonHttpMessageConverter的情况
原因是Spring 3.x 和4.X处理JSON的一个类不一样,而这个东西又配置在xml文件中,所以编译时又无法发现
解决方案:
spring3.x是org.springframework.http.converter.json.MappingJacksonHttpMessageConverter
spring4.x是org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
替换最新的jackson相关jar包(jackson-core-2.9.3.jar、jackson-databind-2.9.3.jar、jackson-annotations-2.9.3.jar)
关于mybatis分页插件PageHelper(2017-12-19):
//调用mybatis分页插件PageHelper进行分页
PageHelper.startPage(pageNum, 5);
List<UserinfoCustom> userinfoCustomsList=userinfoService.findUserList(userinfoQueryVo);
//userinfoCustomsList中的分页属性如pages必须用用PageInfo类包装后才能取出。
PageInfo userPageInfo=new PageInfo(userinfoCustomsList);
maven相关:
将ojdbc14-10.2.0.4.0.jar打包到个人仓库
mvn install:install-file -DgroupId=com.oracle -DartifactId=ojdbc14 -Dversion=10.2.0.4.0 -Dpackaging=jar -Dfile=C:\app\yangzong\product\11.2.0\dbhome_1\jdbc\lib\ojdbc14-10.2.0.4.0.jar
并发编程:
java.util.concurrent包下常用类:
ConcurrentHashMap、ReentrantReadWriteLock(读写锁)、Condition(锁操作,依赖于Lock接口)、线程池、Barrier(一组线程互相等待)、CountDownLatch(倒计时锁)、Callable(带
返回值的线程,配合Future使用)、BlockingQueue
【1】 无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。
【2】 每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。
【6】实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
【4】wait(等待)释放锁、notify(通知)不释放锁;都必须结合synchronized才可以生效。
【5】volatile关键字使多个线程的变量可见,但不能保证其原子性;原子性可以用atomic关键字来保证。
volatile与加锁机制的主要区别是:加锁机制既可以确保可见性又可以确保原子性,而volatile变量只有确保可见性。
【6】技术都是解决问题的,消息队列解决的是将突发大量请求转换为后端能承受的队列请求,比如你的服务器一秒能处理100个订单,但秒杀活动1秒进来1000个订单,持续10秒,在后端能力无法增加的情况下,你可以用消息队列将总共10000个请求压在队列里,后台consumer按原有能力处理,100秒后处理完所有请求(而不是直接宕机丢失订单数据)
同步容器(1.5之前:老的,性能低):vecter、hashtable
并发容器(1.5之后):
ConcurrentHashMap、queue等等
CopyOnWrite容器:CopyOnWriteArrayList、CopyOnWriteArraySet 读写分离的思想
并发队列queue:非阻塞:ConcurrentLinkedQueue实现类 阻塞:BlockingQueue接口为代表的实现类