面试题:
http://www.importnew.com/22056.html
http://marlonyao.iteye.com/blog/344876
http://cache.baiducontent.com/c?m=9f65cb4a8c8507ed4fece763104687270e54f72864879b5468d4e419ce3b46454762e0b82c3510738983233915ea141cbcff2102471453b08cb98b5daec885295f9f564267688c5613a30edfbd5151c337e150fed96af0bb806ac0ea81c4de2444bb52120d84e7fa291762cc78f1642692d78e3f15&p=9a769a4786cc41af59a6e6285c07cf&newp=8067c64ad4934eaf58eccb3861648b231610db2151d6d0106b82c825d7331b001c3bbfb423241200d7c07a6c00ac4e56ecf73d78350025a3dda5c91d9fb4c57479&user=baidu&fm=sc&query=%BB%A5%C1%AA%CD%F8%BD%F0%C8%DAjava%C3%E6%CA%D4%CC%E2&qid=d6cd91620001a7e2&p1=1
http://blog.csdn.net/luanlouis/article/details/40422941
http://ms.csdn.net/geek/79519
http://blog.csdn.net/u010154380/article/details/53557507
http://blog.csdn.net/qq_35124535/article/details/64129964
http://blog.csdn.net/pistolove/article/details/46753275
Redis并发注意点:http://qsalg.com/?p=423
RPC原理:http://www.cnblogs.com/LBSer/p/4853234.html
Java相关原理和深入学习:http://wiki.jikexueyuan.com/list/java/
微服务的一种开源实现方式——dubbo+zookeeper http://blog.csdn.net/zhdd99/article/details/52263609
RPC框架:
http://www.cnblogs.com/luxiaoxun/p/5272384.html
https://my.oschina.net/huangyong/blog/361751
JDK集合框架原理:http://yikun.github.io/tags/Java/
牛逼的博客:http://www.cnblogs.com/LBSer/p/4753112.html
编程狂人周刊:http://www.tuicool.com/mags
码农周刊:http://weekly.manong.io/issues/
http://blog.csdn.net/zhangliangzi/article/details/52526125
成为一个Java的架构师要学习哪些知识? 搜索知乎
soa和微服务架构的区别?搜索知乎
美团点评技术: http://tech.meituan.com/
http://blog.csdn.net/zhangliangzi/article/details/50995326
Java精华:
http://blog.csdn.net/kobejayandy/article/category/1216487
Java并发集合的实现原理:http://www.cnblogs.com/luxiaoxun/p/4638748.html
Java并发原理:http://blog.csdn.net/column/details/14531.html
数据库索引原理和B+树算法分析!!!数据库索引的实现(B+树介绍、和B树、R树区别)
分布式的幂等性和分布式CAP原理!!!
多读书:
JVM公众号推荐:你假笨
《core Java》多线程部分 《effective java》《深入理解Java虚拟机》《Java并发编程实战》
《Java多线程编程核心技术》《实战java高并发程序设计》《Java并发编程实战》《jcip》《深入理解Java虚拟机:JVM高级特性与最佳实践》《深入分析Java Web技术内幕》《Spring源码深度解析》
《大型网站技术架构 核心原理与案例分析》《大型网站系统与Java中间件实践》《高性能MySQL》
《重构 改善既有代码的设计》《企业应用架构模式》《从Paxos到ZooKeeper 分布式一致性原理与实践》
《分布式系统常用技术及案例分析》《精通Spring 4.x》
《高性能网站构建实战》《实用负载均衡技术:网站性能优化攻略》
书籍推荐:http://blog.jobbole.com/106093/
http://www.importnew.com/7099.html
阿里面试:http://www.importnew.com/22056.html
http://www.importnew.com/22637.html
http://yemengying.com/2016/06/05/interview/
面试必看:https://github.com/kdn251/interviews/blob/master/README-zh-cn.md
https://github.com/it-interview/easy-job
http://taoxiaoran.top/pages/tags.html#Java
https://github.com/hadyang/interview
数据结构:
动画版展示数据结构: http://zh.visualgo.net/
架构师技能:http://www.zhihu.com/question/29031276
AOP必看博客,架构分析+优化
http://blog.csdn.net/xvshu/article/details/46288953
http://blog.csdn.net/xvshu/article/category/2110821/2
http://blog.csdn.net/chenleixing/article/details/47099725
http://www.cnblogs.com/java-zhao/category/776210.html
http://www.iteye.com/topic/1122859
http://www.cnblogs.com/davidwang456/p/4213652.html
http://blog.csdn.net/z69183787/article/category/2175689/2
http://www.cnblogs.com/java-zhao/p/5106189.html
http://blog.csdn.net/xyw591238/article/category/6083265
https://github.com/jcalaz/jcalaBlog
Java并发容器源码分析:http://blog.csdn.net/Architect0719/article/category/6193805
书籍推荐:
http://blog.csdn.net/u013256816/article/details/52091850
读书:
深入理解计算机系统、Java并发编程实战、深入理解Java虚拟机第二版、tcp/ip详解 卷一、二、三,Java数据结构与算法
几个流行Java框架:
Spring MVC/Spring Boot
Spark
Dropwizard
Ninja framework
ZK
Ratpack 用于构建现代化HTTP程序的Java库
Jodd
jHipster
分布式协调框架:zookeeper etcd
Java 应用监控 https://github.com/stagemonitor/stagemonitor
http://wsmajunfeng.iteye.com/blog/1744587
7个监控项目: http://www.codeceo.com/article/7-monitor-tools.html
Swagger学习:
http://blog.csdn.net/u010827436/article/details/44417637
Java博客推荐:http://www.tuicool.com/articles/jUrQ7r6
http://www.cnblogs.com/swiftma/category/816241.html
http://www.cnblogs.com/skywang12345/category/489072.html
Java常见类底层实现分析:http://blog.csdn.net/column/details/chenssy-javaimpr.html
开发者头条、简书、码农周刊、推酷、http://www.importnew.com/ 、极客头条、stackoverflow、java world
深入Java:http://www.codeceo.com/article/tag/java 、http://hugnew.com/?cat=17 、 http://www.hollischuang.com/archives/1001 、
http://www.ibm.com/developerworks/cn/java/ 、 http://www.programcreek.com/ 、http://www.open-open.com/lib/tag/Spring 、
http://blog.csdn.net/pkueecser/article/details/50670601 、猿天地 http://cxytiandi.com/blog 、技术干货:http://www.primeton.com/pr/tech.php
国外技术趋势网站:
https://www.infoq.com
https://seroter.wordpress.com/category/aws/
http://www.theserverside.com/
https://dzone.com
https://www.javacodegeeks.com/2015/12/profile-successful-java-developer-2016.html?spm=5176.100239.blogcont54071.5.YXZzqC
http://ifeve.com/tech-related-sites/
Chrome插件:
掘金:http://gold.xitu.io/extension/
Postman:
需要熟练使用版本控制工具 Git(阅读:《Git 权威指南》),以及项目构建工具 Maven(阅读:《Maven实战》)。另外,在这个阶段可以尝试 TDD 开发。
进阶(2-6 个月)
目标:独立负责某个服务端项目。
技能:
掌握 web 开发最佳实践,掌握 Restful API 设计,理解 Spring 原理。推荐阅读《Spring 揭秘》。掌握项目分层、子模块划分。推荐阅读:《J2EE 核心模式》。
掌握 web 架构设计。包括 Http 反向代理,数据缓存,负载均衡,水平扩展和垂直扩展。推荐阅读:《分布式Java应用:基础与实践》。
掌握关系型数据库。包括设计 MySQL 表结构,根据业务特点分表分库,基于执行计划的 SQL 分析优化,以及数据库容量规划。推荐阅读:《MySQL 必知必会》、《高性能 MySQL》。
了解 NoSQL。我们大规模使用 Hadoop、HBase、Hive,同时部分项目使用 Redis、Storm。你需要学会这些工具最基本的使用。
学习 web 安全知识。了解 web 前端安全问题。设计安全 web 服务,包括加解密、防伪造、防重放攻击等。
掌握 Http(推荐阅读:《图解 Http》、《Http 权威指南》)、Thrift 等协议。
掌握服务容量规划,性能调优,可靠性保证,以及故障处理。学习容量规划和性能调优知识,梳理业务监控点,熟练使用我们的监控报警系统。推荐阅读:《深入理解 Java 虚拟机》。
其他。设计模式:从项目中学习,有时间可以看看《深入浅出设计模式》、《JDK 里的设计模式》。学习Java Socket 编程与多线程知识,可以看看《Java 并发编程实战》,并翻翻并发编程网的文章。
深入(6 个月-)
目标:分布式系统和中间件开发。
构建知识体系:《大型网站系统与 Java 中间件实践》、《大型网站技术架构:核心原理与案例分析》。
原理与设计:《大规模存储式系统》、《UNIX 网络编程 卷1:套接字联网 API》、《How Tomcat Works》。
学习开源项目:Apache Thrift、Zipkin、Netty、Rose、Jade、淘宝 RPC 系统 Dubbo 等。分析项目中的设计思路。比如,同样是RPC框架,Finagle 和 Dubbo 有什么异同。
其他。根据参与的项目加深学习吧。比如,如果需要写 DSL,可以读一下《领域特定语言》,对 Redis 感兴趣推荐读一下:《Redis 设计与实现》。有两本书,无论做什么项目,都推荐读:《Unix 编程艺术》、《UNIX 环境高级编程(第3版)》。
HashMap、TreeMap、数据库索引 原理,及常见的平衡二叉树,红黑树原理和实现,redis的几种数据结构原理和优缺点(Hash结构的ziplist和quicklist原理),Hash算法原理和实现
https://github.com/spotify/apollo
http://blog.csdn.net/column/details/java-vitual-machine.html
http://blog.csdn.net/column/details/yrp-java-algorithm.html?&page=2
http://blog.csdn.net/column/details/datastructureinjava.html
http://blog.csdn.net/yannanying/article/details/46956355
http://blog.csdn.net/column/details/datastructure-phn.html
http://blog.csdn.net/column/details/zhonghua.html
http://blog.csdn.net/column/details/datastructure.html
https://github.com/shekhargulati/52-technologies-in-2016
十二个程序员必备的优质资源推荐: http://blog.csdn.net/proginn/article/details/51614131
美团点评技术团队:http://tech.meituan.com/
阿里中间件团队:http://jm.taobao.org/
BAT 技术团队博客:http://blog.csdn.net/tengdazhang770960436/article/details/49963983
技术博客推荐:http://www.cnblogs.com/newpanderking/p/4366174.html
NB的技术社区:https://yq.aliyun.com/tags/type_blog-tagid_41/?spm=5176.100239.rightarea.8.4qenGu
Spring-session源码解析:https://yq.aliyun.com/articles/57425?spm=5176.100239.blogrightarea58510.22.DTpSqZ
Java上线用到的项目:
JMeter 测试
FindBugs SparkJava
JProfiler 约束内存泄漏和修复线程的问题。
Takipi
消息中间件:
Nats --- 速度超快 每秒能处理千万级别消息,占用CPU少,但不支持离线
Kafka --- 速度快,每秒处理百万级消息,能存储,适合大数据
推荐需要看的几本书:
《轻量级微服务架构(上册)》
<<Web Scalability for Startup Engineers--互联网创业核心技术:构建可伸缩的web应用>>
《Spring源码深度解析》《大型网站技术架构 核心原理与案例分析》《大型网站系统与Java中间件实践》《Effective Java中文版》《HotSpot实战》
《从Paxos到ZooKeeper 分布式一致性原理与实践》《深入分析Java Web技术内幕》《java多线程编程核心技术》《实战Java高并发程序设计》
《深入Java虚拟机第2版》《重构 改善既有代码的设计》 《高性能MySQL第3版》 《Java编程思想第4版》 《HTTP权威指南》 《精通正则表达式必知必会》
《Java解惑》 《Java并发编程实践》 《鸟哥的Linux私房菜》《How Tomcat Works(中英文版)》 《Maven权威指南》
《Java并发编程实战》
要求:
其次掌握的技能树主要有三个方面:
第一个是基础,比如对集合类,并发包,IO/NIO,JVM,内存模型,泛型,异常,反射,等有深入了解,最好是看过源码了解底层的设计。比如一般面试都会问ConcurrentHashMap,CopyOnWrite,线程池,CAS,AQS,虚拟机优化等知识点,因为这些对互联网的企业是绝对重要的。而且一般人这关都过不了,还发闹骚说这些没什么用,为什么要面试。举一例子,在使用线程池时,因为使用了无界队列,在远程服务异常情况下导致内层飙升,怎么去解决?你要是连线程池都不清楚,你怎么去玩?再举一例,由于对ThreadLocal理解出错,使用它做线程安全的控制,导致没能实现真的线程安全,你怪我哦?所以作为一个拿两万的JAVA程序员这点基础是必须的。
第二你需要有全面的互联网技术相关知识。从底层说起,你起码得深入了解mysql,redis,mongodb,nginx,tomcat,rpc,jms等方面的知识。你要问需要了解到什么程度,我可以给你说个大慨。首先对于MySQL,你要知道常见的参数设置,存储引擎怎么去选择,还需要了解常见的索引引擎,知道怎么去选择。知道怎么去设计表,怎么优化sql,怎么根据执行计划去调优。高级的你需要去做分库分表的设计和优化,一般互联网企业的数据库都是读写分离,还会垂直与水平拆分,所以这个也有经验的成分在里面。然后redis,mongodb都是需要了解原理,需要会调整参数的,而nginx和tomcat几乎都是JAVA互联网方面必配,其实很阿里的技术栈选择有点关系。至于rpc相关的就多的去,必须各种网络协议,序列化技术,SOA等等,你要有一个深入的理解。现在应用比较广的rpc框架,在国内就是dubbo了,可以自行搜索。至于jms相关的起码得了解原理吧,一般情况下不是专门开发中间件系统和支撑系统的不需要了解太多细节,国内企业常用的主要是activeMQ和kafka。你能对我说的都研究的比较深入,阿里p6我觉得是没问题的,当然这个还需要看你的架构能力方面的面试表现了。
第三就是编程能力,编程思想,算法能力,架构能力的考量。首先2W程序员对算法的要求我觉得还是比较低,再高级也最多红黑树吧,但是排序和查询的基本算法得会。编程思想是必须的,问你个AOP和IOC你起码的清清楚楚,设计模式不说每种都用过,但是也能深入理解个十四五种。编程能力这个我觉得不好去评价,但是拿一个2000W用户根据姓名年龄排序这种题目也能信手拈来。最后就是架构能力,这种不是说要你设计个多牛逼多高并发的系统,起码让你做一个秒杀系统,防重请求的设计能快速搞定而没有坑吧。
#深入理解Java虚拟机第2版
#Java并发编程实战
#MongoDB权威指南
#Netty权威指南
Netty Mina框架源码
2016新兴互联网公司前300:http://www.askci.com/news/hlw/20160425/941447185.shtml
面试:
1.数据传入的安全性解决方案?认证 SSL HTTPS 原理(eg:游戏中数据传输给服务器,如何保证数据安全和完整,防止外挂?)
2.如何使用多线程处理同一个大的任务?
3.蹲坑算法(数据量大的时候,根据内存空间的有序性,为每个数找各自对应的内存空间地址)
4.如何防止内存被击穿,最大并发限制,降级策略?(eg:游戏服务器,并发最多5000,超过就回击穿服务器内存,如果玩家>5000,如何处理?)
百万级访问量网站架构:http://www.biaodianfu.com/thinking-before-building-site.html
http://www.wtoutiao.com/p/12aqbAi.html
初期架构一般比较简单,web负载均衡+数据库主从+缓存+分布式存储+队列。
大方向上也确实就这几样东西,细节上也无数文章都重复过了,按照将来会有N多WEB,N多主从关系,N多缓存,N多xxx设计就行,基本方案都是现成的,
只是您比其他人厉害之处就在于设计上考虑到缓存失效时的雪崩效应、主从同步的数据一致性和时间差、队列的稳定性和失败后的重试策略、文件存储的效率和备份方式等等意外情况。
缓存总有一天会失效,数据库复制总有一天会断掉,队列总有一天会写不进去,电源总有一天会烧坏。根据墨菲定律,如果不考虑这些,网站早晚会成为茶几
需要加强学习的东西:
网络: Netty,mina,NIO,REST,OAuth
Web:Tapestry,DWR,GWT,WebX
搜索: ElasticSearch,Solr
缓存/DB: mongoDB、HBASE,Cassandra,Redis
中间件:RPC,Dubbo,Thrift,Zookeeper,ActiveMQ, Kafka
技能: Shell编程、性能调优、MAT、救火、故障排查
跨语言: shell、Go、NodeJS
大数据: hadoop、hbase、storm、hive、pig、spark
其他框架:Spring Boot、Spring Cloud、 Disruptor(Disruptor是一个用于在线程间通信的高效低延时的消息组件,它像个增强的队列)、Guava、Trove(高性能集合框架)
多线程推荐:
http://www.cnblogs.com/dolphin0520/category/602384.html
http://blog.csdn.net/qilixiang012/article/category/2857487
http://blog.csdn.net/column/details/java-dxc.html
1.Eclipse安装Activiti插件 http://activiti.org/designer/update/
2.Spring MVC 输出页面乱码 在mvc.xml中配置 StringHttpMessageConverter 编码格式
<mvc:message-converters>
<beans:bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"></beans:bean>
<beans:bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"></beans:bean>
<beans:bean class="org.springframework.http.converter.StringHttpMessageConverter">
<beans:property name="supportedMediaTypes">
<beans:list>
<beans:value>text/plain;charset=UTF-8</beans:value>
<beans:value>text/html;charset=UTF-8</beans:value>
</beans:list>
</beans:property>
</beans:bean>
<beans:bean class="org.springframework.http.converter.ResourceHttpMessageConverter"></beans:bean>
<beans:bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter"></beans:bean>
<beans:bean class="org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter"></beans:bean>
</mvc:message-converters>
3.Spring MVC注解式统一异常处理:
--- 可根据异常,返回数据或指定页面
BaseController中添加方法处理异常
@ExceptionHandler(RuntimeException.class)
public @ResponseBody ResponseVo runtimeExceptionHandler(RuntimeException ex) {
ResponseVo responseVo = new ResponseVo();
responseVo.setSuccess(false);
responseVo.setMsg(ex.getMessage());
responseVo.setData(ex);
return responseVo;
}
4.几个最常用的Eclipse快捷键
1. ctrl+shift+r:打开资源,在workspace中快速按文件名查找
2. ctrl+o:快速outline,查看代码结构,显示类中方法和属性,能快速定位类的方法和属性
3. ctrl+e:快速转换编辑器
4. ctrl+2,L:为本地变量赋值
5. alt+shift+r:重命名
6. Alt+左右方向键
我们经常会遇到看代码时Ctrl+左键,层层跟踪,然后迷失在代码中的情况,
这时只需要按“Alt+左方向键”就可以退回到上次阅读的位置,
同理,按“Alt+右方向键”会前进到刚才退回的阅读位置
7.ctrl+shift+x和ctrl+shift+y:英文字母大小写的转换
8.ctrl+shift+f:格式化代码
9.ctrl+m:当前编辑页面窗口最大/小化
10.ctrl+shift+o:自动引入包和删除无用包
11.Ctrl+T 快速显示当前类的继承结构
12.Ctrl+W 关闭当前窗口
13.Alt+Shift+R 重命名
14.Alt+Shift+M 抽取方法
15.【Ct rl+K】、【Ct rl++Shift +K】 快速向下和向上查找选定的内容,从此不再需要用鼠标单击查找对话框
16.Ctrl+Shift+G 查找类、方法和属性的引用
17.Alt+Shift+w 查找当前文件所在项目中的路径
18.Ctrl+Shift+w 关闭所有文件
最实用的:
【Alt + ← 】 查看代码时,返回上次查看位置 --- 后退上次代码记录
【Alt + → 】 查看代码时,跟踪,回到下次浏览位置
点中类名+F4 查看类的继承关系
5.常见的内存移除的3种情况
1.JVM Heap(堆)溢出 java.lang.OutOfMemoryError:Java heap space
JVM在启动的时候会自动设置JVM Heap的值, 可以利用JVM提供的-Xmn -Xms -Xmx等选项可进行设置。
Heap的大小是Young Generation 和Tenured Generaion 之和。在JVM中如果98%的时间是用于GC,且可用的Heap size 不足2%的时候将抛出此异常信息
解决方法:
设置JVM Heap(堆)大小
即:-Xmn -Xms -Xmx等选项,以及 年轻代与年老代的比例 ratio等参数
2.PermGen space溢出 java.lang.OutOfMemoryError:PermGen space
永久带溢出 --- 一般发生在程序启动阶段
永久带被JVM存放Class和Meta信息,如果载入很多Class,可能出现PermGen space溢出
解决方法:
通过-XX:PermSize和-XX:MaxPermSize设置永久代大小即可
3.栈溢出 java.lang.StackOverflowError : Thread Stack space
可能原因:递归层次太多,导致栈溢出
解决方法:
1.修改程序
2.通过 -Xss 设置每个线程的Stack大小
Server容器启动时,需要设置的几个JVM参数:
-Xms:java Heap 堆初始大小, 默认是物理内存的1/64。
-Xmx:java Heap 堆最大值,不可超过物理内存。
-Xmn:young generation的heap堆大小,一般设置为Xmx的3、4分之一 。增大年轻代后,将会减小年老代大小,可以根据监控合理设置。
-Xss:每个线程的Stack大小,而最佳值应该是128K,默认值好像是512k。
-XX:PermSize:设定内存的永久保存区初始大小,缺省值为64M。
-XX:MaxPermSize:设定内存的永久保存区最大大小,缺省值为64M。
-XX:SurvivorRatio:Eden区与Survivor区的大小比值,设置为8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10
-XX:+UseParallelGC:F年轻代使用并发收集,而年老代仍旧使用串行收集.
-XX:+UseParNewGC:设置年轻代为并行收集,JDK5.0以上,JVM会根据系统配置自行设置,所无需再设置此值。
-XX:ParallelGCThreads:并行收集器的线程数,值最好配置与处理器数目相等 同样适用于CMS。
-XX:+UseParallelOldGC:年老代垃圾收集方式为并行收集(Parallel Compacting)。
-XX:MaxGCPauseMillis:每次年轻代垃圾回收的最长时间(最大暂停时间),如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值。
-XX:+ScavengeBeforeFullGC:Full GC前调用YGC,默认是true。
实例如:JAVA_OPTS=”-Xms4g -Xmx4g -Xmn1024m -XX:PermSize=320M -XX:MaxPermSize=320m -XX:SurvivorRatio=6″
6.Service或Dao层获取request和IP方法?
1.获取IP InetAddress.getLocalHost().getHostAddress()
2.获取request对象 http://my.oschina.net/u/2007041/blog/420956
7.JDK6在Linux下的安装
第一:用linux 的命令运行它: sh jdk-6u2-linux-i586-rpm.bin
第二:按多次回车后出现
Do you agree to the above license terms? [yes or no]
输入yes
第三:编辑环境变量
$gedit ~/.bashrc
加入如下五行:
JAVA_HOME=/usr/java/jdk1.6.0_02
JAVA_BIN=/usr/java/jdk1.6.0_02/bin
PATH=$PATH:$JAVA_HOME/bin
CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
export JAVA_HOME JAVA_BIN PATH CLASSPATH
第四步是必须的,不然它总是调用FC6自带的jdk1.4
第四:创建链接
#cd /usr/bin
#ln -s -f /usr/local/jdk1.5.0_05/jre/bin/java
#ln -s -f /usr/local/jdk1.5.0_05/bin/javac
或 环境变量配置 vi /etc/profile
#for java
export JAVA_HOME=/home/hetiewei/software/java/jdk1.8.0_40
export PATH=$JAVA_HOME/bin:$PATH
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
#for go
export GOROOT=/home/hetiewei/go/soft/go
export PATH=$GOROOT/bin:$PATH
export GOPATH=/home/hetiewei/go/soft/go/pkg
#for node
export NODE_HOME=/home/hetiewei/node/software/node-v6.2.0-linux-x64
export PATH=$PATH:$NODE_HOME/bin
export NODE_PATH=$NODE_HOME/lib/node_modules
PATH=/usr/local/ssl/bin:/sbin/:$PATH:/usr/sbin
export PATH
#for hadoop
export HADOOP_HOME=/home/hetiewei/software/bigdata/hadoop/hadoop-2.6.4
export PATH=.:$HADOOP_HOME/bin:$PATH
source /etc/profile
8.Tomcat启动时,在initialing Spring root context 卡死:
解决:
1.查看数据库连接
2.清除Tomcat下的work目录
9.Maven install时出现编码异常解决方法:
mvn clean install -Dmaven.javadoc.skip=true
maven 安装时跳过 测试用例:
mvn clean install -Dmaven.test.skip=true
10.Spring和Mybatis整合时无法读取properties的处理方案:
方法一:
修改<property name="sqlSessionFactory" ref="sqlSessionFactory"/>为<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
原理:使用sqlSessionFactoryBeanName注入,不会立即初始化sqlSessionFactory, 所以不会引发提前初始化问题。
方法二:
直接删掉<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
注意:在没有配置这一行时,必须配置一个以sqlSessionFactory命名的org.mybatis.spring.SqlSessionFactoryBean。
11.token,Session的区别?
服务器在生成表单的时候同时生成一个CSRF token,插入到表单的一个hidden field里面,并且把这个token记录在服务器端,通常是用户的Session数据里面。
客户端啥都不用干,照常提交表单。当表单被提交的时候,服务端检查一个表单里面的token跟自己之前记录下来的是否匹配,匹配才继续处理。
CSRF劫持的请求也会带上网站的cookie的,所以光验证session并不能避免CSRF
token的关键是在于你在发送请求的时候一定要保证你是读取了这个页面的。。 而不是凭空就发送请求
对于HTTP协议来说,就是url,header,body
对于WEB页面来说,就是url,form/post body,cookie
好了,现在有的就是这么多东西,要怎么用呢?
首先是第一个问题,HTTP请求是无状态的,我怎么知道谁是谁?
解:让用户每次访问的时候告诉你它叫什么,怎么告诉?url,form/post body,cookie
然后是第二个问题,用户访问的时候说他自己是张三,他骗你怎么办?
解:在服务器端保存张三的信息,给他一个id,让他下次用id访问。id保存在url,form/post body,cookie中。这叫做session
现在是第三个问题,用户提交了一笔订单,你怎么保证他是在你的订单页面提交的?(referer可能是一个办法)
解:在你订单页面中放入一个加密的信息,只有真正打开了订单页才能知道,提交的时候将这个信息返回回来。这个东西,可以被叫做token
Token实现防止表单重复提交?
表单重复提交的两种情形:
1.通过浏览器回退功能,回到原来页面重复提交表单,服务器端应避免用户重复注册
2.提交完成后,单击浏览器的“刷新”按钮,浏览器会弹出对话框,询问是否重新提交数据。单击“是”,浏览器会重新提交数据。
如何防止?
每次请求都生产一个token标识
1.表单提交后,先匹配(使用Aop做)token,判断当前用户会话中token令牌值与当前请求参数的token令牌值是否一致
2.每次请求都创建一个新的token令牌,将其保存在当前会话(session)范围内
3.token在服务器端匹配后,就把session中的toke令牌值删除
12.Java线程安全的本质:线程中并没有存放任何对象数据,而是在执行时,去主内存(堆)中去同步数据,所有的对象数据都存在JVM的堆中,因此需要对资源进行共享锁!!!
堆 --- JVM的核心数据存储区 --- 线程共享的主内存
堆中为JVM的所有对象分配了内存空间用以存储和维护变量值等
栈 --- 线程私有的内存区,由值栈(线程栈)组成,存放8中基本数据类型和对象引用
每个线程都会生成一个自有的线程栈,线程栈中用存储了该线程的基本数据常量,变量值,以及对象长变量的引用
每个线程执行时,根据代码顺序,压栈 值栈(栈内存)
对象变量在线程执行时的过程:!!! --- 由JVM内存模型决定
1.线程根据栈中的引用去堆上同步该对象数据下来,然后在线程自己的内存中进行操作
2.操作之后再将线程栈撒花姑娘的运算结果同步到堆(主内存)中
3.多线程时,因为每个线程都操作自己从主内存(JVM堆)中同步过来的数据,如果不加锁,会导致线程安全问题(数据提交到主内存时不一致)
13.堆 --- JVM中所有对象的内存空间 分为: Young Gen, Old Gen
Young Gen 又分为:Eden区和两个大小相同的Survivor区(from 和 to)
Eden和Survivor默认比例 8:1 由 -XX:SurvivorRation设置
堆大小 -Xmx -Xms 设置
Young Gen -Xmn 设置
-XX:NewSize和-XX:MaxNewSize
用于设置年轻代的大小,建议设为整个堆大小的1/3或者1/4,两个值设为一样大。
Minor GC --- 发生在新生代的垃圾回收,Java 对象大多都具备朝生夕灭的特性,所以 Minor GC 非常频繁,一般回收速度也比较快,
年轻代的GC使用复制算法(将内存分为两块,每次只用其中一块,当这块内存用完,就将还活着的对象复制到另外一块上面,复制算法不会产生内存碎片)
Full GC --- 发生在年老代的GC, Full GC比较慢,尽量避免
新创建对象都会被分配到Eden区(一些大对象特殊处理),当Eden区满则进行Minor GC,
这些对象经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区, 对象在Survivor区中每熬过一次Minor GC,年龄增加1岁,
当年龄增加到一定程度后(默认15岁),会被移动到年老代中,
当年老代满时,经常Full GC
线程Stack 每个线程独有的操作数栈局部变量表方法入口 -Xss 设置
方法区 -XX:PermSize和-XX:MaxPermSize设置
JVM中的类加载机制: http://www.cnblogs.com/ITtangtang/p/3978102.html
14.Go,MongoDB,redis,node.js在Linux下的安全和配置
1.go
下载tar.gz后,
解压:tar -zxvf go1.6.2.linux-amd64.tar.gz
环境变量:
vi /etc/profile
在profile中添加一下内容:
GOPATH和GOROOT修改为你的路径即可!!!
export GOROOT=/home/forward/tools/go
export PATH=$GOROOT/bin:$PATH
export GOPATH=/home/forward/tools/gopkg
刷新环境变量:
source /etc/profile
验证:
go version
编译Go程序:
go build xx.go
直接运行Go程序:
go run xx.go
2.mongodb
解压:
tar -zxvf mongodb-linux-i686-3.2.6.tgz
环境变量:
--- (只当前用户有效)
export PATH=<mongodb-install-directory>/bin:$PATH
或
--- (全局有效)
vi /etc/profile
在profile中添加一下内容:
export PATH=<mongodb-install-directory>/bin:$PATH
刷新环境变量:
source /etc/profile
3.node.js
下载解压:
wget --no-check-certificate https://nodejs.org/dist/v6.2.0/node-v6.2.0-linux-x64.tar.gz
环境变量:
NODE_HOME 是node.js安全目录
export NODE_HOME=/home/hetiewei/node/software/node-v6.2.0-linux-x64
export PATH=$PATH:$NODE_HOME/bin
export NODE_PATH=$NODE_HOME/lib/node_modules
刷新环境变量: 让环境变量生效
source /etc/profile
验证:
命令行输入:node -v,查看node.js的版本
4.redis
参考官方源码安装说明
Redis Cluster集群的搭建与实践 http://lib.csdn.net/article/redis/60796
15.通过Spring在工具类中获取HttpServletRequest对象:
/**
* 获取当前Request
* @return
*/
private HttpServletRequest getRequest() {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
return requestAttributes.getRequest();
}
16.Spring mvc在redirect请求中传递数据 http://shmilyaw-hotmail-com.iteye.com/blog/2246344
3种方式:1.Session 2.url template 3. flash attribute
17.logback与ActiveMQ的slf4j jar包冲突解决??? --- MQ使用指定版本, 不使用用activemq-all
<!-- Spring 整合ActiveMQ -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<!-- activeMQ begin -->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-core</artifactId>
<version>5.7.0</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-pool</artifactId>
<version>5.7.0</version>
</dependency>
18.logback配置打印MyBatis执行的sql?
在logback.xml中配置 : dao所在的包名
<logger name="com.tuniu.plat.config.dao" level="DEBUG"/>
19.Spring MVC接收不到前端的参数???
试试: data:JSON.stringify(formData)
20.File文件目录中文无法解析的问题:
String path = Config.class.getClassLoader().getResource("").toURI().getPath();
21.Spring MVC 转发和重定向的传参问题?
1.转发 forward
1. this.getServletContext().getRequestDispatcher("/rentHouse.htm?method=display").forward(request,response);
return null;
2. return new ModelAndView("forward:/xxx.htm", map);
或
ModelAndView mv = new ModelAndView("forward:/xxx.htm");
mv.addAtrribute("param", value)
return mv;
2.重定向 redirect
1.无参数
return new ModelAndView("redirect:/toList");
return "redirect:/ toList ";
2.有参数
1.手动拼url
new ModelAndView("redirect:/toList?param1="+value1+"¶m2="+value2);
这样有个弊端,就是传中文可能会有乱码问题。
2.用RedirectAttributes
redirectAttributes.addFlashAttribute("message", "保存用户成功!");//使用addFlashAttribute,参数不会出现在url地址栏中
public String save(@ModelAttribute("form") Bean form,RedirectAttributes attr)
throws Exception {
String code = service.save(form);
if(code.equals("000")){
attr.addFlashAttribute("name", form.getName());
attr.addFlashAttribute("success", "添加成功!");
return "redirect:/index";
}else{
attr.addAttribute("projectName", form.getProjectName());
attr.addAttribute("enviroment", form.getEnviroment());
attr.addFlashAttribute("msg", "添加出错!错误码为:"+rsp.getCode().getCode()+",错误为:"+rsp.getCode().getName());
return "redirect:/maintenance/toAddConfigCenter";
}
}
注意:
1.使用RedirectAttributes的addAttribute方法传递参数会跟随在URL后面,如上代码即为http:/index.action?a=a
2.使用addFlashAttribute不会跟随在URL后面,会把该参数值暂时保存于session,待重定向url获取该参数后从session中移除,
这里的redirect必须是方法映射路径,jsp无效。你会发现redirect后的jsp页面中b只会出现一次,刷新后b再也不会出现了,
这验证了上面说的,b被访问后就会从session中移除。对于重复提交可以使用此来完成.
22.QQ 网页上的登陆模块(全程HTTP/GET请求) --- 前端数据的安全
QQ 在登陆时,对用户输入的密码加密的JS代码为:
function getEncryption(password, uin, vcode, isMd5) {
var str1 = hexchar2bin(isMd5 ? password : md5(password));
var str2 = md5(str1 + uin);
var str3 = md5(str2 + vcode.toUpperCase());
return str3
}
白话就是: md5(md5(md5(密码) + 用户的QQ号) + 验证码)
验证码是一次性的, 所以,在你在网络层拿到本次的请求之后,无法做 重放攻击, 因为验证码是不正确的.
23.git常用命令:
git init 将当前目录初始化为git仓库
git status 查看仓库状态
git add xx 添加文件,该文件等待提交
git add -A 或 git add . 添加当前目录下所有文件
git commit -m "备注" 提价到本地git仓库
git remote add origin https://github.com/jayfeihe/xx.git 为本地仓库指定远程仓库
git remote rm origin 断开远程仓库
git remote -v 查看远程仓库
git pull origin master 从远程仓库拉取文件
git push origin master 向远程仓库推送已提交内容
1,如何在提交代码前看看我的代码中不同文件的状态?
git status
2,如何把别人的代码拉取下来?
git clone url
url:一般都是在github上的仓库地址
执行这个命令后,仓库就会被下载到你指定的目录
3,如何把新的文件加入到git的索引中?
git add file1 file2 file3
这么增加很多文件的话一定很烦,那么请使用git add . 把所有文件加入到git索引中
git索引:代表了你的文件已经被git管理
4,如何看我将要提交到远程仓库的文件?
git diff --cached
5,如何给我的提交增加备注说明?
git commit -m “xxxx说明”这个命令是提交代码必须的
6,如何通过图形化的界面查看该项目的所有历史提交记录?
gitk
7,如何查看项目的日志?也就是你的提交记录
git log
8,如何合并git add 和git commit 命令的效果?
git commit -a -m“xxx说明”
注意,这会把所有文件add到git索引中,可能你会有不想被git管理的文件,所以你需要事先通过忽略文件来控制。
9,如何创建一个分支?
git branch a
a就是新分支,然后使用git checkout a来切换到a分支,创建分支的意义是,你可以在自己的分支下开发,在开发完成后和主版本master合并,尤其在团队中尤为重要
10,如何合并分支到主分支?
git merge a master
11,如何删除已经合并的分支?
git branch -D a
12,如何暂时保存我们的工作记录,去看一个例如修复版本bug的事情?
git stash “xxxxx” 暂时记录你的工作状态
进行你的修复工作
git commit -a -m “xxx提交你的修复”
git stash apply 回到你的工作
13,如何搞定远程分支和本地主版本的合并?
git branch --track [远程分支的名称] origin/[远程分支的名称]
例如:git branch --track a origin/a
git pull a
pull:这个命令同时做了下载远程a分支,并合并到本地master的动作。如果有冲突是会合并失败的,但是不会造成下载a分支失败。
14,如何根据关键字搜索其在代码中出现的位置?
git grep -n [key]
15,如果我想重置我的版本如何做?
git reset --hard HEAD
这个命令会把你做过的所有未提交(git commit -m)的内容清空
16,如果我只想重置一个文件怎么做?
git checkout --file
这时只会check出一个未修改过的文件
17,如何修复一个已经提交文件中的错误?
虽然有2种做法,创建一个新提交和checkout出老提交并修复,但是建议通过创建新提交去做。因为git对于历史内容被改动会出现不能合并的情况
24.ActiveMQ整合Spring,监听队列
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:jms="http://www.springframework.org/schema/jms"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms-3.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd ">
<!-- ActiveMQ连接工厂 -->
<bean id="amqConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL"
value="failover:(tcp://mq.master.jd.tuniu-cie.org:61616)" />
</bean>
<!-- <bean id="productDetailListener" class="com.tuniu.plat.service.mq.ProductDetailListener"/> -->
<!--方式一:只监听一个队列 -->
<!-- 监听器队列 -->
<!--
<bean id="productDetailDestination" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg index="0" value="${product_mq_queue}" />
-->
</bean>
<!-- 消息监听容器(Queue),配置连接工厂,监听的队列,监听器是:mq的产品消息监听器 -->
<!--
<bean id="jmsContainer"
class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="amqConnectionFactory" />
<property name="destination" ref="productDetailDestination" />
<property name="messageListener" ref="productDetailListener" />
</bean>
-->
<!--方式二:监听多个队列 -->
<jms:listener-container destination-type="queue" container-type="default" connection-factory="amqConnectionFactory" acknowledge="auto">
<jms:listener destination="${product_mq_queue}" ref="productDetailListener"/>
</jms:listener-container>
<!-- Spring JmsTemplate 的消息生产者 start -->
<!-- 定义JmsTemplate的Queue类型 -->
<bean id="jmsQueueTemplate" class="org.springframework.jms.core.JmsTemplate">
<!-- 这个connectionFactory对应的是我们定义的Spring提供的那个ConnectionFactory对象 -->
<constructor-arg ref="amqConnectionFactory" />
<!-- 非pub/sub模型(发布/订阅),即队列模式 -->
<property name="pubSubDomain" value="false" />
</bean>
<!-- 定义JmsTemplate的Topic类型 -->
<bean id="jmsTopicTemplate" class="org.springframework.jms.core.JmsTemplate">
<!-- 这个connectionFactory对应的是我们定义的Spring提供的那个ConnectionFactory对象 -->
<constructor-arg ref="amqConnectionFactory" />
<!-- pub/sub模型(发布/订阅) -->
<property name="pubSubDomain" value="true" />
</bean>
<!--Spring JmsTemplate 的消息生产者 end -->
</beans>
25.JSP中无法解析后端传递的数据(jstl,el表达式不生效)?
1.web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
</web-app>
2.jsp页面
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ page isELIgnored="false" %>
26.关于LinkedBlockQueue
阻塞的线程安全队列,底层采用链表实现,LinkedBlockingQueue不接受null
添加元素: 都是向队尾添加元素
put 向队尾添加元素,如果队列满了会发生阻塞,一直等待空间,以加入元素
add 添加元素时,超出队列长度会直接抛异常
offer 添加元素时,如果队列已满,直接返回false
移除元素: 都是从队头移除元素
poll 队列为空,返回null
remove 队列为空,抛出NoSuchElementException异常
take 队列为空,发送阻塞,等到有元素
BlockingQueue阻塞队列的4个实现类:
ArrayBlockingQueue:规定大小的BlockingQueue,其构造函数必须带一个int参数来指明其大小.其所含的对象是以FIFO(先入先出)顺序排序的
LinkedBlockingQueue:大小不定的BlockingQueue,若其构造函数带一个规定大小的参数,生成的BlockingQueue有大小限制,若不带大小参数,所生成的BlockingQueue的大小由Integer.MAX_VALUE来决定.其所含的对象是以FIFO(先入先出)顺序排序的 ,不允许放null
PriotityBlockingQueue:类似于LinkedBlockQueue,但其所含对象的排序不是FIFO,而是依据对象的自然排序顺序或者是构造函数的Comparator决定的顺序
SynchronousQueue:对其的操作必须是放和取交替完成的
LinkedBlockingQueue的数据吞吐量要大于ArrayBlockingQueue,但在线程数量很大时其性能的可预见性低于ArrayBlockingQueue
27.MyBatis批量更新参考:
http://my.oschina.net/zouqun/blog/405424
http://blog.csdn.net/tolcf/article/details/39213217
28.Spring 中配置的@Aspect 不起作用:
1.添加注解:@Component
2.在Spring MVC的配置文件中添加 <aop:aspectj-autoproxy proxy-target-class="true"/>
29.Spring Boot 遇到的问题?
Spring Boot Mapper无法注入:启动类上添加注解 @MapperScan("com.jay.spring.boot.demo10.multidb.dao")
30.Maven指定打包环境:http://haohaoxuexi.iteye.com/blog/1900568
3个地方可以指定profile环境:
web.xml
maven的setting.xml --- 默认是:dev
项目的pom.xml
eg:pom.xml
<profiles>
<profile>
<id>development</id>
<properties>
<profile.path>config/dev</profile.path>
</properties>
</profile>
<profile>
<id>sit</id>
<properties>
<profile.path>config/sit</profile.path>
</properties>
</profile>
<profile>
<id>product</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<profile.path>config/prd</profile.path>
</properties>
</profile>
</profiles>
1.通过下面配置指定默认环境:
<activation>
<activeByDefault>true</activeByDefault>
</activation>
或
2.通过命令指定环境:(说明:-P profile_id)
mvn package –P product
31.Spring中的几个Listener监听器和类 http://www.cnblogs.com/damowang/p/4305153.html
1.ServletContextListener接口 --- Web容器启动时执行,此时Bean还未初始化,不能在里面获取依赖的bean
--- 适合做一些容器初始化工作
Why ServletContextListener接口无法获取Spring中定义的Bean?
eg:
public class ConfigListener implements ServletContextListener {
@Autowired
private ConfigService configService;
@Override
public void contextInitialized(ServletContextEvent sce) {
configService.initConfig(); //这里会报空指针异常,无法获取注入的configService
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
要理解这个问题,首先要区分Listener的生命周期和spring管理的bean的生命周期
(1)Listener的生命周期是由servlet容器(例如tomcat)管理的,项目启动时上例中的ConfigListener是由servlet容器实例化并调用其contextInitialized方法,而servlet容器并不认得@Autowired注解,因此导致ConfigService实例注入失败。
(2)而spring容器中的bean的生命周期是由spring容器管理的。
(3)即:此时Spring管理的Bean还未初始化完成
修改:
@Override
public void contextInitialized(ServletContextEvent sce) {
ConfigService configService = WebApplicationContextUtils.getWebApplicationContext(sce.getServletContext()).getBean(ConfigService.class);
configService.initConfig();
}
特别注意:
以上代码有一个前提,那就是servlet容器在实例化ConfigListener并调用其方法之前,要确保spring容器已经初始化完毕!而spring容器的初始化也是由Listener(ContextLoaderListener)完成,因此只需在web.xml中先配置初始化spring容器的Listener,然后在配置自己的Listener,配置如下
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>example.ConfigListener</listener-class>
</listener>
2.ApplicationListener<T>接口 --- Spring容器初始化完成,Bean加载完成后执行, 可获取所依赖的Bean,
---- 适合完成一些项目的初始化工作(eg:加载或清空过期缓存,执行初始化sql等)
T 表示事件:
ApplicationEvent,每次请求都会执行
ContextRefreshedEvent, Spring容器启动后执行
参考:http://www.cnblogs.com/rollenholt/p/3612440.html
3.项目加载完毕后,执行一些初始化工作,eg:数据库初始化或查询数据,缓存初始化等 --- 3 种 方式
1.实现BeanPostProcessor接口
接口有两个方法
(1)postProcessBeforeInitialization方法,在spring中定义的bean初始化前调用这个方法;
(2)postProcessAfterInitialization方法,在spring中定义的bean初始化后调用这个方法;
2.实现ApplicationListener接口
实现其onApplicationEvent()方法
Spring容器初始化完成,Bean加载完成后执行, 可获取所依赖的Bean
特别注意:
ApplicationListener接口中的onApplicationEvent被调用多次的问题?
解决:
onApplicationEvent事件参数,添加final 即:final ContextRefreshedEvent event
eg:
@Component
public class NatsInitListener implements ApplicationListener<ContextRefreshedEvent> {
@Autowired
private NatsService natsService;
/**
* 容器启动时,添加Nats的消息监听
* @param contextRefreshedEvent
*/
@Override
public void onApplicationEvent(final ContextRefreshedEvent contextRefreshedEvent) {
try {
natsService.getMsgAsync("foo");
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.实现InitializingBean接口
实现afterPropertiesSet()方法
项目在加载完毕后立刻执行afterPropertiesSet 方法 ,并且可以使用spring 注入好的bean
32.Map的遍历
entrySet()和 keySet() 方式, 前者快,后者满
entrySet() 是map的一个节点,是横向的,生成键和映射关系的视图 不需要再get一次。所以效率明显快
keySet() 是map中所有键的集合,是纵向的,先获取出map的key,然后再通过key,get出value
keySet是键的集合,Set里面的类型即key的类型
entrySet是 键-值 对的集合,Set里面的类型是Map.Entry
Map<String, String> maps = new HashMap<String, String>();
//方法一: 用entrySet()
Iterator<Entry<String,String>> it = maps.entrySet().iterator();
while(it.hasNext()){
Map.Entry<String,String> m = it.next();
String key = m.getKey();
String value= m.getValue();
}
// 方法二:jdk1.5支持,用entrySet()和For-Each循环()
for (Map.Entry<String, String> m : maps.entrySet()) {
String key = m.getKey();
String value= m.getValue();
}
// 方法三:用keySet()
Iterator<String> it2 = maps.keySet().iterator();
while (it2.hasNext()){
String key = it2.next();
String value= maps.get(key);
}
// 方法四:jdk1.5支持,用keySet()和For-Each循环
for(String m: maps.keySet()){
String key = m;
String value= maps.get(m);
}
33.解决CORS跨域问题的3种方式?
1.Tomcat配置
1.下载cors-filter-1.7.jar,java-property-utils-1.9.jar这两个库文件,放到lib目录下
2.项目的web.xml中添加如下配置
<filter>
<filter-name>CORS</filter-name>
<filter-class>com.thetransactioncompany.cors.CORSFilter</filter-class>
<init-param>
<param-name>cors.allowOrigin</param-name>
<param-value>*</param-value>
</init-param>
<init-param>
<param-name>cors.supportedMethods</param-name>
<param-value>GET, POST, HEAD, PUT, DELETE</param-value>
</init-param>
<init-param>
<param-name>cors.supportedHeaders</param-name>
<param-value>Accept, Origin, X-Requested-With, Content-Type, Last-Modified</param-value>
</init-param>
<init-param>
<param-name>cors.exposedHeaders</param-name>
<param-value>Set-Cookie</param-value>
</init-param>
<init-param>
<param-name>cors.supportsCredentials</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CORS</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
2.拦截器设置响应头 --- Spring MVC 4.2以下版本推荐方式
<!-- API 接口跨域配置 -->
<mvc:cors>
<mvc:mapping path="/api/**"
allowed-origins="*"
allowed-methods="POST, GET, OPTIONS, DELETE, PUT"
allowed-headers="Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With"
allow-credentials="true" />
</mvc:cors>
3.使用Spring MVC 4.2+以上版本的
在对应的接口上添加注解: @CrossOrigin(origins="http://xxx", maxAge = 3600)
34.HttpSessionListener和HttpSessionAttributeListener接口的用法?
1.HttpSessionListener --- 2个方法
sessionCreated() --- 新建一个会话时触发,可以说是客户端第一次和服务器交互时触发
sessionDestroyed() --- 销毁会话时, 按钮触发进行销毁或配置定时销毁会话(关闭浏览器时不会销毁)
2.HttpSessionAttributeListener --- 3个方法
attributeAdded() --- 在session中添加对象时触发此操作,即:调用setAttribute这个方法时候会触发
attributeRemoved() --- 修改、删除session中添加对象时触发,即:调用 removeAttribute这个方法时候会触发
attributeReplaced() --- 在Session属性被重新设置时触发
eg:
统计在线会话数的功能,并让超时的自动销毁
web.xml
<listener>
<listener-class>
org.xiosu.listener.onlineListener (实现session监听器接口的类的名字,包也要写上)
</listener-class>
</listener>
<!--默认的会话超时时间间隔,以分钟为单位 -->
<session-config>
<session-timeout>1</session-timeout>
</session-config>
OnlineListener.java
public class onlineListener implements HttpSessionListener,
HttpSessionAttributeListener {
// 参数
ServletContext sc;
ArrayList list = new ArrayList();
// 新建一个session时触发此操作
public void sessionCreated(HttpSessionEvent se) {
sc = se.getSession().getServletContext();
System.out.println("新建一个session");
}
// 销毁一个session时触发此操作
public void sessionDestroyed(HttpSessionEvent se) {
System.out.println("销毁一个session");
if (!list.isEmpty()) {
list.remove((String) se.getSession().getAttribute("userName"));
sc.setAttribute("list", list);
}
}
// 在session中添加对象时触发此操作,在list中添加一个对象
public void attributeAdded(HttpSessionBindingEvent sbe) {
list.add((String) sbe.getValue());
System.out.println(sbe.getValue());
sc.setAttribute("list", list);
}
// 修改、删除session中添加对象时触发此操作
public void attributeRemoved(HttpSessionBindingEvent arg0) {
System.out.println("5555555");
}
public void attributeReplaced(HttpSessionBindingEvent arg0) {
System.out.println("77777777");
}
}
eg:会话计数器
https://github.com/appfuse/appfuse/blob/master/web/common/src/main/java/org/appfuse/webapp/listener/UserCounterListener.java
35.4种提交表单的方式 http://www.aikaiyuan.com/6324.html
base64与byte[]相互转换:
import sun.misc.BASE64Decoder;//将base64转换为byte[]
import sun.misc.BASE64Encoder;//转byet[]换为base64
eg:
// 定义一个BASE64Encoder
BASE64Encoder encode = new BASE64Encoder();
// 将byte[]转换为base64
String base64 = encode.encode("五笔字型电子计算机".getBytes());
// 输出base64
System.out.println(base64);
// 新建一个BASE64Decoder
BASE64Decoder decode = new BASE64Decoder();
// 将base64转换为byte[]
byte[] b = decode.decodeBuffer(base64);
// 输出转换后的byte[]
System.out.println(new String(b));
36.失败重试
public class Test1{
public static void main(String[] args) {
new Test1().comparePrice(6, 3);
}
/*
* 3次尝试,如果有一次成功,则不再尝试,如果有异常,会重试3次
*
*/
public String comparePrice(int m, int n) {
int i=3;
while(i-->0){
System.out.println("第"+(3-i)+"次");
try {
int a = m/n;
return "success";
} catch (Exception e) {
System.out.println("出现异常");
}
}
return "error";
}
}
37.关于有返回值的多线程应用问题:
Future --- Callable
FutureTask --- Callable
eg;
对list遍历,每个item都要请求一次网络,可通过Future--Callable,用线程池方式,list遍历中多线程调用
注意:
1.线程池可以JDK自带的,也可用Spring的线程池
2.先通过线程池提交Callable任务,然后将返回的Future或FutureTask存放到一个List集合中
3.遍历Future任务,通过调用future.get()得到每个线程的返回值,将多个线程的返回值组装即可
public List<JdProductWare> getSkuWareDetail(List<JdProductWare> list) throws JdException {
//创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(50);
List<JdProductWare> result = new ArrayList<>();
List<Future<JdProductWare>> furuteList = new ArrayList<Future<JdProductWare>>();
try{
//遍历List,每个item都用一个线程执行,同时将线程的Future存入一个List集合
for(JdProductWare jdProductWare:list){
Future<JdProductWare> future = executorService.submit(new SkuDetailTask(url,accesstoken, appkey, appsecret, jdProductWare));
furuteList.add(future);
}
//遍历Future集合,得到每个线程的返回值,并将返回值存入指定结果集
for(Future<JdProductWare> future:furuteList){
JdProductWare ware = future.get();
result.add(ware);
}
//销毁线程池
executorService.shutdown();
}catch(Exception e){
LOG.info("获取Sku的商品title和sku子标题出错,【{}】", e.getMessage());
}
return result;
}
eg:
/*
* 景点操作控制器
*/
@Controller
@RequestMapping("/menpiao/")
public class MenPiaoScenicController {
private final static Logger LOG = LoggerFactory.getLogger(MenPiaoScenicController.class);
@Autowired
private MenPiaoSecnicService secnicService;
/*
* 推送京东景点信息
*/
@RequestMapping(value = "/send/{holidayId}", method = RequestMethod.GET)
public MenPiaoScenicInfo sendScenicToJd(@PathVariable("holidayId") String holidayId) throws JdException {
MenPiaoScenicInfo info = secnicService.sendSenicInfo(holidayId);
return info;
}
/*
* 使用异步多线程方式推送京东景点信息 holidayIds:多个holidayId 用 , 分隔
*/
@RequestMapping(value = "/send/batch/{holidayIds}", method = RequestMethod.GET)
public List<MenPiaoScenicInfo> sendScenicBatch(@PathVariable("holidayIds") String holidayIds) {
String ids[] = holidayIds != null ? holidayIds.split(",") : null;
List<MenPiaoScenicInfo> list = new ArrayList<>();
List<Future<MenPiaoScenicInfo>> futureList = new ArrayList<>();
try{
// 创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(50);
for (String id : ids) {
futureList.add(executorService.submit(new sendScenicTask(secnicService, id)));
}
//遍历Future集合,得到每个线程的返回值,并将返回值存入指定结果集
for(Future<MenPiaoScenicInfo> future:futureList){
MenPiaoScenicInfo info = future.get();
list.add(info);
}
//销毁线程池
executorService.shutdown();
}catch(Exception e){
LOG.info("获取Sku的商品title和sku子标题出错,【{}】", e.getMessage());
}
return list;
}
}
class sendScenicTask implements Callable<MenPiaoScenicInfo> {
private MenPiaoSecnicService scenicService;
private String holidayId;
public sendScenicTask() {
super();
}
public sendScenicTask(MenPiaoSecnicService scenicService, String holidayId) {
super();
this.scenicService = scenicService;
this.holidayId = holidayId;
}
public MenPiaoSecnicService getScenicService() {
return scenicService;
}
public void setScenicService(MenPiaoSecnicService scenicService) {
this.scenicService = scenicService;
}
public String getHolidayId() {
return holidayId;
}
public void setHolidayId(String holidayId) {
this.holidayId = holidayId;
}
@Override
public MenPiaoScenicInfo call() throws Exception {
return scenicService.sendSenicInfo(holidayId);
}
}
38.MyBatis传入多个参数(太多了可以封装成Vo,少于3个的情况),3种方式?
1.Map传递
Dao层:
TicketOrderCoupon findOrderCoupon(Map<String, Long> map);
Mapper.xml:
<select id="findOrderCoupon" resultMap="BaseResultMap" parameterType="java.util.Map">
select
<include refid="Base_Column_List" />
from ticket_order_coupon
where sku_id = #{skuId,jdbcType=BIGINT}
and order_id = #{orderId,jdbcType=BIGINT}
</select>
Service层:
Map<String, Long> map = new HashMap<String, Long>();
map.put("skuId", item.getOutSkuId());
map.put("orderId", item.getOutOrderId());
TicketOrderCoupon orderCoupon = orderCouponMapper.findOrderCoupon(map);
2. 参数占位符
DAO层:
Public User selectUser(String name,String area);
Mapper.xml:
<select id="selectUser" resultMap="BaseResultMap">
select * from user_user_t where user_name = #{0} and user_area=#{1}
</select>
其中:
#{0}代表接收的是dao层中的第一个参数,#{1}代表dao层中第二参数,更多参数一致往后加即可
3.参数标识
Dao层:
List<TicketOrderLog> selectLogList(@Param("type") String type, @Param("status") Integer status);
Mapper.xml:
<select id="selectLogList" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List" />
FROM ticket_order_log
WHERE
<![CDATA[ retry < 3 ]]>
and status = #{status,jdbcType=TINYINT}
<if test="type != null">
and type = #{type,jdbcType=VARCHAR}
</if>
</select>
注意:
@Param注解 org.apache.ibatis.annotations.Param
39.MyBatis批量操作和缓存,表关联
批量操作参考:http://blog.csdn.net/u012562943/article/details/50425747
缓存参考:http://blog.csdn.net/u012562943/article/details/50403163
表关联:http://blog.csdn.net/u012562943/article/details/50403144
40.Redis配置主从复制 + Spring Boot 整合Redis集群
主Master:redis.conf
port 6379
maxmemory-policy noeviction
appendonly yes
appendfilename "appendonly.aof"
requirepass mypass
#cluster-enabled yes
#cluster-config-file nodes.conf
#cluster-node-timeout 5000
#appendonly yes
从Slave1:redis.conf
port 6380
slaveof 127.0.0.1 6379 #主redis的IP port(注意:用IP不要用localhost)
slave-read-only no #slave是否只读
masterauth mypass
#cluster-enabled yes
#cluster-config-file nodes.conf
#cluster-node-timeout 5000
#appendonly yes
Spring Boot 连接Redis集群
1.Ubuntu下创建redis集群
1.源码安装redis,设置redis.conf中的cluster-enabled yes ,并拷贝3份分别为redis1, redis2, redis3
# redis.conf
bind node1 # for node1
cluster-enabled yes
cluster-node-timeout 5000
2.启动3个redis节点:
./redis-server ../redis.conf --port 6379
./redis-server ../redis.conf --port 6380
./redis-server ../redis.conf --port 6381
说明:
1.调试信息最重要的一行:
No cluster configuration found, I'm a1eec932d923b55e23a5fe6a488ed7a97e27c826
这表示我们的redis服务器正在运行在cluster mode
3.连接启动的3个节点
node1 6379
node1 6380
node1 6381
说明:
它们都处于失联状态,我们现在开始配置将它们彼此连接起来,Redis有一个连接节点的工具称为redis-trib.rb.
它是一个ruby文件,需要 redis gem被安装
1.安装ruby
sudo apt-get install ruby
sudo apt-get install rubygems #如果可以执行gem命令,则不需要此步骤
2.安装redis
gem install redis
3.执行创建集群命令
./redis-trib.rb create 127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6381
每个节点负责数据的1/3,键入 'yes'
4.连接其中的一个节点,查看redis集群状态
src/redis-cli -h node2 cluster nodes
./redis-cli -p 6379
>cluster info
5.关于Redis集群节点的管理:
参考:blog.51yip.com/nosql/1726.html
2.Spring Boot + Redis集群
1.添加pom依赖
<!-- Spring data redis, 通过配置使用Redis集群 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.添加配置文件
application.yml 或 application.properties中添加redis集群的配置
spring:
redis:
cluster:
nodes:
- 127.0.0.1:6379
- 127.0.0.1:6380
- 127.0.0.1:6381
3.注入RedisTemplate,操作redis集群
@Autowire
private RedisTemplate redisTemplate;
4.启动Spring Boot项目,验证数据
41.关于MyBatis的接口注解实现:
1.insert方法,返回操作对象保存后的id --- 即:这里的 User 在执行save(user)方法后,user中的id会被自动赋值
/* 下面的注解,可以在操作完save方法后,得到保存后的id
* @SelectKey(before=false,keyProperty="id",resultType=Long.class,statementType=StatementType.STATEMENT,statement="SELECT LAST_INSERT_ID() AS id")
*/
@Insert("insert into micro_user(name, username, password, salt) values(#{name},#{username}, #{password}, #{salt})")
@SelectKey(before=false,keyProperty="id",resultType=Long.class,statementType=StatementType.STATEMENT,statement="SELECT LAST_INSERT_ID() AS id")
int save(User user);
2.注解版批量保存
/*
* 批量添加 --- 注解版
*/
@InsertProvider(type=UserMapperProvider.class, method = "batchInsert")
Integer batchSave(List<User> list);
public static class UserMapperProvider{
public String batchInsert(Map<String, List<User>> map) {
List<User> list = map.get("list");
StringBuilder stringBuilder = new StringBuilder(256);
stringBuilder.append("insert into micro_user(name, username, password, salt) values ");
MessageFormat messageFormat = new MessageFormat("(#'{'list[{0}].name},#'{'list[{0}].username},#'{'list[{0}].password},#'{'list[{0}].salt})");
for (int i = 0; i < list.size(); i++) {
stringBuilder.append(messageFormat.format(new Integer[]{i}));
stringBuilder.append(",");
}
stringBuilder.setLength(stringBuilder.length() - 1);
return stringBuilder.toString();
}
}
3.注解版查询
@Select("select * from micro_user where del_flag = 0")
List<User> findAll();
@Results(
{
@Result(id = true, column = "id", property = "id"),
@Result(column = "name", property = "name"),
@Result(column = "username", property = "username"),
@Result(column = "password", property = "password"),
@Result(column = "salt", property = "salt"),
@Result(column = "create_time", property = "createTime"),
@Result(column = "login_time", property = "loginTime")
})
@Select("select * from micro_user where username = #{username}")
User findByUsername(String username);
42.Jenkins相关:
http://10.10.30.116:8888/jenkins/ 登录名,密码:admin admin
1.配置文件路径:/root/.jenkins/
2.初始密码:/root/.jenkins/secrets/initialAdminPassword
3.安装参考:http://www.cnblogs.com/h--d/p/5673085.html
http://m.blog.csdn.net/article/details?id=50518959
http://www.cnblogs.com/zz0412/p/jenkins02.html
http://blog.csdn.net/galen2016/article/details/53418708
Jenkins配置和使用: http://www.cnblogs.com/h--d/p/5682030.html
jenkins+github持续集成:
参考:
http://www.jianshu.com/p/22b7860b4e81
http://www.jianshu.com/p/b2ed4d23a3a9
4.Jenkins无法下载插件的解决方法:http://blog.csdn.net/russ44/article/details/52266953
常见的无法安装插件下载地址:
http://updates.jenkins-ci.org/download/plugins/
步骤:
从http://10.10.30.116:8888/jenkins/updateCenter/ 中查看为安装成功的插件,进行手动安装即可
安装顺序:
1.credentials --> plain-credentials --> credentials-binding
2.ssh-credentials --> ssh-slaves
3.git-client --> git-server --> git
4.pam-auth
5.build-pipeline-plugin --> workflow-cps -->workflow-multibranch --> pipeline-multibranch-defaults -->
pipeline-graph-analysis --> pipeline-rest-api
6.subversion
7.github --> github-oauth --> groovy --> github-branch-source -->github-api
8.deploy
9.email-ext
5.常用插件的手动安装: 系统管理 --> Global Tool Configuration
1.JDK配置: 新增JDK --> 取消自动安装
别名: JDK1.8
JAVA_HOME:/home/hetiewei/software/java/jdk1.8.0_40
2.Maven配置:新增Maven --> 取消自动安装
别名: maven-3.3.9
MAVEN_HOME:/home/hetiewei/software/maven/apache-maven-3.3.9
6.Jenkins构建svn项目
1.General : 填写项目名称和描述
2.源码管理:
选 Subversion
Modules:
Repository URL:
http://boy.tuniu.com/svn/JDB/PPLA/test/jd-plat
Credentials: 填svn的账号密码
3.构建:
Invoke top-level Maven targets
Maven Version: maven-3.3.9 (Maven配置)
Goals:
clean package -Dmaven.test.skip=true (先clean再打包)
7.Jenkins构建git项目(github) --- 配置参考:http://10.10.30.116:8888/jenkins/job/cloudDemo1/
1.General : 填写项目名称和描述
2.源码管理:
选Git
Repositories:
Repository URL:https://github.com/jayfeihe/spring-cloud-demo.git
Credentials: 填Github的账号密码
Branches : 填写分支(默认:*/master)
源码库浏览器: githubweb
URL: https://github.com/jayfeihe/spring-cloud-demo/
3.构建触发器: Build when a change is pushed to GitHub
4.构建:
Invoke top-level Maven targets
Maven Version: maven-3.3.9 (Maven配置)
Goals:
clean package -Dmaven.test.skip=true (先clean再打包)
点击高级:
pom: workspace/CloudShopEurekaServer (如果git下有多个项目,可以选择基于哪一个项目下的pom构建)
8.配置Tomcat登录名和密码 Tomcat --- > conf --> tomcat-users.xml
添加登录名和密码: deploy tomcat
<role rolename="tomcat"/>
<role rolename="role1"/>
<role rolename="manager-gui" />
<role rolename="manager-script" />
<role rolename="manager-status" />
<user username="tomcat" password="tomcat" roles="tomcat"/>
<user username="both" password="tomcat" roles="tomcat,role1"/>
<user username="role1" password="tomcat" roles="role1"/>
<user username="deploy" password="tomcat" roles="manager-gui,manager-script,manager-status" />
42.WebStorm开发Node.js启用代码提示:
File -> Settings... -> Languages&Frameworks -> Node.js and NPM 页
Code Assistatant启用Node.js库的代码提示即可(点击Enable按钮)
43.WebStorm/Idea 与 Eclipse 快捷键对比大全
http://blog.csdn.net/quincylk/article/details/18256697
默认配置-Eclipse的常用快捷键对照表
查找/代替
Webstorm快捷键
Eclipse快捷键
说明
ctrl+shift+N
ctrl+shift+R
通过文件名快速查找工程内的文件(必记)
ctrl+shift+alt+N
ctrl+shift+alt+N
通过一个字符快速查找位置(必记)
ctrl+F
ctrl+F
在文件内快速查找代码
F3
ctrl+K
查找下一个
shift+F3
ctrl+shift+K
查找上一个
ctrl+R
ctrl+F
文件内代码替换
ctrl+shift+R
指定目录内代码批量替换
ctrl+shift+F
ctrl+H
指定目录内代码批量查找
界面操作
Webstorm快捷键
Eclipse快捷键
说明
ctrl+shift+A
ctrl+shift+A
快速查找并使用编辑器所有功能(必记)
alt+[0-9]
alt+[0-9]
快速拆合功能界面模块
ctrl+shift+F12
ctrl+shift+F12
最大区域显示代码(会隐藏其他的功能界面模块)
alt+shift+F
alt+shift+F
将当前文件加入收藏夹
ctrl+alt+s
ctrl+alt+s
打开配置窗口
ctrl+tab
ctrl+tab
切换代码选项卡(还要进行此选择,效率差些)
alt+←/→
alt+←/→
切换代码选项卡
ctrl+F4
ctrl+F4
关闭当前代码选项卡
代码编辑
Webstorm快捷键
Eclipse快捷键
说明
ctrl+D
ctrl+shift+↑
复制当前行
ctrl+W
alt+shift+↑
选中单词
ctrl+←/→
ctrl+←/→
以单词作为边界跳光标位置
alt+insert
alt+insert
新建一个文件或其他
ctrl+alt+L
ctrl+alt+L
格式化代码
shift+tab/tab
shift+tab/tab
减少/扩大缩进(可以在代码中减少行缩进)
ctrl+Y
ctrl+D
删除一行
shift+enter
shift+enter
重新开始一行(无论光标在哪个位置)
导航
Webstorm快捷键
Eclipse快捷键
说明
esc
esc
进入代码编辑区域
alt+F1
alt+F1
查找代码在其他界面模块的位置,颇为有用
ctrl+G
ctrl+L
到指定行的代码
ctrl+]/[
ctrl+]/[
光标到代码块的前面或后面
alt+up/down
ctrl+shift+up/down
上一个/下一个方法
44.新的架构模式:
前后端分离: 参考:http://developer.51cto.com/art/201404/435984.htm
前端:负责View和Controller层
后端:负责Model层、业务处理、数据等
前后端的接口
前端: (Front UI View展示) (Node中间层 Model + Controller处理) (Restful、SOAP)
展示(Angular、React)
<--- 数据获取(Node)
<--- 后端数据接口
后端:
数据接口 <--- 业务逻辑 <--- 缓存+DB
45.Filebeat5+Kafka+ELK Docker搭建日志系统: http://www.jianshu.com/p/9dfac37885cb
Docker笔记:http://www.jianshu.com/users/110149e3a887/latest_articles
46.分隔List集合,按指定大小,将集合分成多个
/**
* 常用工具类
* @author hetiewei(贺铁伟)
*
*/
public class JayCommonUtil {
/**
* 按指定大小,分隔集合,将集合按规定个数分为n个部分
*
* @param list
* @param len
* @return
*/
public static List<List<?>> splitList(List<?> list, int len) {
if (list == null || list.size() == 0 || len < 1) {
return null;
}
List<List<?>> result = new ArrayList<List<?>>();
int size = list.size();
int count = (size + len - 1) / len;
for (int i = 0; i < count; i++) {
List<?> subList = list.subList(i * len, ((i + 1) * len > size ? size : len * (i + 1)));
result.add(subList);
}
return result;
}
}
47.Spring Data MongoDB注解
@Id - 文档的唯一标识,在mongodb中为ObjectId,它是唯一的,通过时间戳+机器标识+进程ID+自增计数器(确保同一秒内产生的Id不会冲突)构成。
@Document - 把一个java类声明为mongodb的文档,可以通过collection参数指定这个类对应的文档。
@DBRef - 声明类似于关系数据库的关联关系。ps:暂不支持级联的保存功能,当你在本实例中修改了DERef对象里面的值时,单独保存本实例并不能保存DERef引用的对象,它要另外保存,如下面例子的Person和Account。
@Indexed - 声明该字段需要索引,建索引可以大大的提高查询效率。
@CompoundIndex - 复合索引的声明,建复合索引可以有效地提高多字段的查询效率。
@GeoSpatialIndexed - 声明该字段为地理信息的索引。
@Transient - 映射忽略的字段,该字段不会保存到mongodb。
@PersistenceConstructor - 声明构造函数,作用是把从数据库取出的数据实例化为对象。该构造函数传入的值为从DBObject中取出的数据
eg:
@Document(collection="person")
@CompoundIndexes({
@CompoundIndex(name = "age_idx", def = "{'lastName': 1, 'age': -1}")
})
public class Person<T extends Address> {
@Id
private String id;
@Indexed(unique = true)
private Integer ssn;
private String firstName;
@Indexed
private String lastName;
private Integer age;
@Transient
private Integer accountTotal;
@DBRef
private List<Account> accounts;
private T address;
public Person(Integer ssn) {
this.ssn = ssn;
}
@PersistenceConstructor
public Person(Integer ssn, String firstName, String lastName, Integer age, T address) {
this.ssn = ssn;
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.address = address;
}
}
48. MongoDB根据已有数据文档,批量造大数据,(指数级成倍增长)
原理:
1.将已有文件放到游标
2.遍历游标得到文档数组
3.批量插入文档数组
> var cur = db.ticket_order_detail.find({},{_id:0,id:1,outOrderId:1,fullName:1,phone:1,address:1,orderRemark:1,venderRemark:1,bossRemark:1});
> var data =[];
> for(var i=0;i<cur.size();i++){ data.push(cur[i]); }
> db.ticket_order_detail.insert(data);
MongoDB数据备份与还原:
一、集合备份
mongoexport -h 集合所在服务器地址 -d 数据库 -c 集合 -o 本地文件存储位置
注:文件格式可选json、cvs等
官方参数说明:mongoexport --help
二、集合还原
mongoimport --host 目标IP --port 目标端口 --db 目标数据库 --collection 目标集合 --file 备份文件所在位置
官方参数说明:mongoimport --help
49.MongoDB内存使用过大的问题,如果不设置默认会占用系统总内存的50%~80%,
内存问题官方参考:https://docs.mongodb.com/manual/faq/storage/
设置 --wiredTigerCacheSizeGB 参数可防止占用过多内存
eg:最多占用2G内存
mongod -dbpath=../db --wiredTigerCacheSizeGB 2
说明:
1.如果是在Docker容器中运行,该参数不能超过容器分配的内存,建议:1或2G
解决MongoDB占用内存过duo的几个方法:!!!
1.启动时,调整占用内存大小(默认占用主机内存的50%)
mongod -dbpath=../db --wiredTigerCacheSizeGB 1
2.压缩集合,减少内存占用
//进入集合所在的db
use db
//执行命令,压缩指定集合
db.runCommand({compact:'collection_name'})
3.Linux下执行切换日志命令 --- MongoDB内存有很大一部分跟日志有关
use admin
db.runCommand("logRotate");
通过配置文件方式启动MongoDB
./mongod --config /data/mongodb3/mongo.conf
monggo.conf内容:
storageEngine = wiredTiger
wiredTigerCacheSizeGB = 2
syncdelay = 30
wiredTigerCollectionBlockCompressor = snappy
port=38019
dbpath=/data/mongodb30/db
oplogSize=2048
logpath=/data/mongodb30/logs/mongodb.log
logappend=true
fork=true
rest=true
journal = true
解析:
storageEngine 是设置存储引擎;wiredTigerCacheSizeGB 是设置mongodb存储引擎所用的内容,默认为系统内存的50%;
syncdelay 是设置从内存同步到硬盘的时间间隔,默认为60秒,可以设置的少一些,在mongodb故障时,丢失的日志会少一些;
wiredTigerCollectionBlockCompressor 是设定压缩策略 snappy 是一种压缩速度非常快的压缩策略
50.使用MongoExpress操作MongoDB数据库 --- Web页面操作MongoDB
1.安装mongo-express
npm install -g mongo-express
2.以管理员账号启动
mongo-express -a -u username -p password
连接到指定的数据库
mongo-express -u username -p password -d database
连接到远程库
mongo-express -u username -p password -d database -H mongoDBHost -P mongoDBPort
3.操作数据库
localhost:8081
4.Docker使用
$ docker run -it --rm -p 8081:8081 --link YOUR_MONGODB_CONTAINER:mongo mongo-express
51.使用elasticsearch-HQ 管理ElasticSearch
1.启动ElasticSearch,并开启跨域支持 --- elasticsearch.yml中http模块下配置:
#是否支持跨域,默认false
http.cors.enabled: true
#支持跨域的路径
http.cors.allow-origin: http://localhost
2.将elasticsearch-HQ解压到指定路径:eg: D:\new_tech\ELK\royrusso-elasticsearch-HQ-6a0f138
3.使用Nginx访问elasticsearch-HQ,配置Nginx如下:
location / {
#访问的静态资源根路径
root D:/new_tech/ELK/royrusso-elasticsearch-HQ-6a0f138;
#默认页面
index index.html index.htm;
#允许跨域访问
add_header 'Access-Control-Allow-Origin' '*';
}
4.访问 http://localhost/即可
5.连接到指定的ElasticSearch,按提示,在左上角中填写 http://localhost:9200,连接
52.Spring MVC 配置各种模板解析引擎
http://blog.csdn.net/zhuzhoulin/article/details/52371530
53.Spring Boot中处理静态资源(自定义资源映射)
1.Spring Boot默认使用resources下的静态资源进行映射
2.自定义资源映射的2种方式:
1.实现类继承 WebMvcConfigurerAdapter 并重写方法 addResourceHandlers
2.
eg:
以 /myres/* 映射到 classpath:/myres/* 为例的代码处理为
方式1:
@Configuration
public class MyWebAppConfigurer
extends WebMvcConfigurerAdapter {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/myres/**").addResourceLocations("classpath:/myres/");
super.addResourceHandlers(registry);
}
}
解析:
1.addResourceLocations 的参数是动参,可以这样写 addResourceLocations(“classpath:/img1/”, “classpath:/img2/”, “classpath:/img3/”);
2.如果我们要指定一个绝对路径的文件夹(如 D:/data/api_files ),则只需要使用 addResourceLocations 指定即可
3.可以直接使用addResourceLocations 指定磁盘绝对路径,同样可以配置多个位置,注意路径写法需要加上file:
registry.addResourceHandler("/api_files/**").addResourceLocations("file:D:/data/api_files");
方式2:
#Spring MVC对静态文件的配置
spring.resources.static-locations=classpath:/static/
54.Spring Boot 中,添加入参和出参解析器
@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {
/*
* 配置简易的View, 默认页面
* /, /index都指向 index页面
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("/index");
registry.addViewController("/index").setViewName("/index");
registry.addViewController("/login").setViewName("/user/login");
}
/**
* 配置mvc请求入参解析器
* @param argumentResolvers
*/
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
super.addArgumentResolvers(argumentResolvers);
//添加入参Base64解码
argumentResolvers.add(new JayRequestBase64Resolver());
//添加入参解密
// argumentResolvers.add(new JayRequestDecryptResovler());
}
/**
* 配置mvc参数返回解析器
* @param returnValueHandlers
*/
@Override
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
super.addReturnValueHandlers(returnValueHandlers);
//添加出参Base64编码
returnValueHandlers.add(new JayResponseBase64Resovler());
//添加出参加密
// returnValueHandlers.add(new JayResponseEncryptResovler());
}
}
55.Java中创建对象的5种方式
1、用new语句创建对象,这是最常见的创建对象的方法。
2、通过工厂方法返回对象,如:String str = String.valueOf(23);
3、运用反射手段,调用java.lang.Class或者java.lang.reflect.Constructor类的newInstance()实例方法。如:Object obj = Class.forName("java.lang.Object").newInstance();
4、调用对象的clone()方法。 implements Cloneable
5、通过I/O流(包括反序列化),如运用反序列化手段,调用java.io.ObjectInputStream对象的 readObject()方法。
56.MyBatis返回数字类型结果:
将resultMap改为resultType=Java对应的数字类型即可
eg:
<select id="selectByHolidayId" resultType="java.lang.Integer"
parameterType="java.lang.Integer">
select count(id) from t_menpiao_product_info
where holiday_id=#{holidayId,jdbcType=INTEGER}
</select>
57.JDK动态代理和cglib字节码技术代理的区别?
1.JDK动态代理:
1.静态代理 --- 代理对象和目标对象实现了相同的接口,目标对象作为代理对象的一个属性,
具体接口实现中,可以调用目标对象相应方法前后加上其他业务处理逻辑
2.JDK动态代理只能针对实现了接口的类生成代理
2.CGLIB代理 --- 通过字节码技术,为目标对象生成一个与其功能一样的子类
1.针对类实现代理
2.主要是对指定的类生产一个子类,覆盖其中的所有方法
3.被代理类或方法不能声明为final
3.区别:
1.JDK动态代理只能对实现了接口的类生成代理, 动态代理只能对于接口进行代理
2.cglib针对类实现代理,主要是对指定的类生成一个子类,覆盖中的方法,因为是继承,所以该类或方法最好不要声明成final ,final可以阻止继承和多态
3.Spring实现中,如果有接口,默认使用JDK动态代理,如果目标对象没有实现接口,使用cglib代理,
如果目标对象实现了接口,可以强制使用CGLIB实现代理(添加CGLIB库,并在spring配置中加入<aop:aspectj-autoproxy proxy-target-class="true"/>)。
4.动态代理的应用
AOP(Aspect-OrientedProgramming,面向切面编程),AOP包括切面(aspect)、通知(advice)、连接点(joinpoint),实现方式就是通过对目标对象的代理在连接点前后加入通知,完成统一的切面操作。
实现AOP的技术,主要分为两大类:
一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;
二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。
Spring提供了两种方式来生成代理对象: JDKProxy和Cglib,具体使用哪种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定。
默认的策略是如果目标类是接口,则使用JDK动态代理技术,如果目标对象没有实现接口,则默认会采用CGLIB代理。
如果目标对象实现了接口,可以强制使用CGLIB实现代理(添加CGLIB库,并在spring配置中加入<aop:aspectj-autoproxy proxy-target-class="true"/>)。
5.参考:http://www.cnblogs.com/linghu-java/p/5714769.html
58.Spring boot + redis 实现消息队列 --- 可用于分布式系统
PK Spring的事件监听器 --- 适合单系统(非分布式)
1.依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.redis作为消息队列的配置
@Configuration
public class RedisConfig {
private Logger logger = LoggerFactory.getLogger(getClass());
/**
* 定义redis作为消息队列时的容器
* @param connectionFactory
* @param listenerAdapter
* @return
*/
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,MessageListenerAdapter listenerAdapter){
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.addMessageListener(listenerAdapter, new PatternTopic("queue1"));
return container;
}
//定义一个队列消息监听器,指定监听器类和监听方法
@Bean
MessageListenerAdapter listenerAdapter(Receiver receiver){
return new MessageListenerAdapter(receiver, "receiveMessage");
}
//定义一个监听器对象
@Bean
Receiver receiver(CountDownLatch latch){
return new Receiver(latch);
}
@Bean
CountDownLatch latch() {
return new CountDownLatch(1);
}
}
3.消息监听器
/**
* 在任何一个基于消息的应用中,都有消息发布者和消息接收者(或者称为消息订阅者)。
* 创建消息的接收者,我们只需一个普通POJO,在POJO中定义一个接收消息的方法即可
* Created by hetiewei on 2017/3/9.
*/
/*
该Receiver类被注册为一个消息监听者,处理消息的方法可以任意命名,
给Receiver的构造函数通过@AutoWired标注注入了一个CountDownLatch实例,当接收到消息时,调用countDown()方法
*/
public class Receiver {
private Logger logger = LoggerFactory.getLogger(getClass());
private CountDownLatch latch;
@Autowired
public Receiver(CountDownLatch latch){
this.latch = latch;
}
public void receiveMessage(String message){
logger.info("Received <"+message+">");
latch.countDown();
}
}
4.发送消息
@RestController
@RequestMapping("/redis/queue")
public class RedisQueueController {
@Autowired
private CountDownLatch latch;
@Autowired
private StringRedisTemplate template;
/**
* 发送消息
* @param msg
* @return
*/
@GetMapping("/send/{msg}")
public String sendMsg(@PathVariable("msg") String msg) throws InterruptedException {
template.convertAndSend("queue1", msg);
latch.await();
return msg;
}
@GetMapping("/batch/send")
public String batchSend() throws InterruptedException {
for (int i=0;i<1000;i++){
template.convertAndSend("queue1", "msg"+i);
latch.await();
}
return "success";
}
}
59.关于Spring 声明式事务的原理
参考:http://yemengying.com/2016/11/14/something-about-spring-transaction/
Spring的声明式事务:
1.JavaConfig方法 --- 在需要管理事务的类或方法上添加 @Transactional注解,然后在配置类上添加 @EnableTransactionManagement注解
2.Xml方式 --- 添加 <tx:annotation-driven />
Spring会利用Aop在相关方法调用的前后进行事务管理
问题:
public class JayServiceImpl implements JayService {
public void A(List<Giraffe> giraffes) {
for (Giraffe giraffe : giraffes) {
B(giraffe);
}
}
@Transactional("transactionManager")
public void B(Giraffe giraffe) {
// Step 1: update something
// Step 2: insert something
// Step 3: update something
}
}
说明:
Service中A方法调用B方法,方法A没有事务管理,方法B采用声明式事务,通过在方法上声明 @Transactional注解来做事务管理
问题:
Junit 测试方法 A 的时候发现方法 B 的事务并没有开启, 而直接调用方法 B 事务是正常开启的???
// 没有开启事务
@Test
public void testA() {
giraffeService.A();
}
// 正常开启事务
@Test
public void testB() {
giraffeService.B();
}
}
原理分析:
Spring在加载目标Bean时,会为声明了@Transactional的Bean创建一个代理类,而目标类本身并不能感知到代理类的存在,
调用通过Spring上下文注入的Bean的方法,而不是直接调用目标类的方法
即:
先调用代理类的方法,代理类再调用目标类的方法
Calling Code
--call--> Proxy --->foo()
---> Pojo --> pojo.foo()
对于加了@Transactional注解的方法,在调用代理类方法时,会先通过拦截器 TransactionInterceptor开启事务,
然后再调用目标类的方法,最后在调用结束后, TransactionInterceptor会提交或回滚事务
问题解析:
对于第一段的代码,我在方法 A 中调用方法 B,实际上是通过“this”的引用,也就是直接调用了目标类的方法,而非通过 Spring 上下文获得的代理类,所以。。。事务是不会开启滴
解决方法:
通过实现ApplicationContextAware接口获得 Spring 的上下文,(或自动注入Context对象),然后获得目标类的代理类,通过代理类的对象,调用方法 B,即可
public class GiraffeServiceImpl implements GiraffeService,ApplicationContextAware{
@Setter
private ApplicationContext applicationContext;
public void A(List<Giraffe> giraffes) {
GiraffeService service = applicationContext.getBean(GiraffeService.class);
for (Giraffe giraffe : giraffes) {
service.B(giraffe);
}
}
@Transactional("transactionManager")
public void B(Giraffe giraffe) {
// Step 1: update something
// Step 2: insert something
// Step 3: update something
}
}
60.Java类加载机制
装载 ---> 链接(验证 --> 准备 --> 解析) ---> 初始化
1.JVM类加载机制:
装载:
1.找到该类型的class文件,产生一个该类型的class文件二进制数据流(ClassLoader需要实现的loadClassData()方法)
2.解析该二进制数据流为方法区内的数据结构
3.创建一个该类型的java.lang.Class实例
最终:通过defineClass()创建一个Java类型对象(Class对象)
找到二进制字节码,并加载到JVM中
JVM通过类全限定名(包名.类名) + 类加载器 完成类的加载,生成类对应的Class对象
链接:
验证:
负责对二进制字节码进行校验、类信息是否符合JVM规范,有没有安全问题、对class文件长度和类型进行检查
参考:http://www.importnew.com/17105.html
准备:
初始化类中静态变量、并将其初始化为默认值 --- 只初始化静态变量默认值 !!!,给其类变量赋值发生在初始化阶段!!!
对于final类型的变量,准备阶段直接赋初始值
该内存分配发生在方法区
解析:
解析类中调用的接口、类、字段、方法的符号引用,把虚拟机常量池中的符号引用转换为直接引用
初始化:
1.对static类变量指定初始值!!!(2种方式:一种是通过类变量的初始化语句,一种是静态初始化语句)
2.一个类的初始化需要先初始化其父类,并递归初始化其祖先类
2.JVM必须在每个类或接口主动使用时进行初始化:
主动使用的情况:
1.创建类的实例(无论是new、还是反射、克隆、序列化创建的)
2.使用某个类的静态方法
3.访问某个类或即可的静态字段
4.调用Java API中的某些反射方法
5.初始化某个类的子类(先初始化其父类)
6.启动某个标明为启动类的类(含main()方法)
主动使用会导致类的初始化,其超类均将在该类的初始化之前被初始化,但通过子类访问父类的静态字段或方法时,对于子类(或子接口、接口的实现类)来说,这种访问就是被动访问,或者说访问了该类(接口)中的不在该类(接口)中声明的静态成员
3.创建对象时,类中各成员的执行顺序:
父静态块 <-- 子静态块 <-- 父普通代码块 <-- 父构造器 <-- 子普通代码块 <-- 子构造器
1.父类静态成员和静态初始化快,按在代码中出现的顺序依次执行。
2.子类静态成员和静态初始化块,按在代码中出现的顺序依次执行。
3. 父类的实例成员和实例初始化块,按在代码中出现的顺序依次执行。
4.执行父类的构造方法。
5.子类实例成员和实例初始化块,按在代码中出现的顺序依次执行。
6.执行子类的构造方法。
eg:
public class Test {
public static void main(String[] args) {
Son s = new Son();
}
}
class Parent{
{
System.out.println("parent中的初始化块");
}
static{
System.out.println("parent中static初始化块");
}
public Parent(){
System.out.println("parent构造方法");
}
}
class Son extends Parent{
{
System.out.println("son中的初始化块");
}
static{
System.out.println("son中的static初始化块");
}
public Son(){
System.out.println("son构造方法");
}
}
结果:
parent中static初始化块
son中的static初始化块
parent中的初始化块
parent构造方法
son中的初始化块
son构造方法
61.MySQL性能优化
参考:
MySQL性能优化总结:http://www.cnblogs.com/luxiaoxun/p/4694144.html
http://blog.chinaunix.net/uid-29435603-id-4275475.html
1.存储引擎选择
参考:http://www.jb51.net/article/38178.htm
MyISAM:
不支持事务处理,为每个表创建3个文件,分别存储不同内容
支持表级锁,表的写操作会阻塞其他用户对同一个表的读和写操作,并发度低
1.myISAM表的读操作,不会阻塞其他用户对同一个表的读请求,但会阻塞对同一个表的写请求。
2.myISAM表的写操作,会阻塞其他用户对同一个表的读和写操作。
3.myISAM表的读、写操作之间、以及写操作之间是串行的
eg:
tb_Demo表,那么就会生成以下三个文件:
1.tb_demo.frm,存储表定义;
2.tb_demo.MYD,存储数据;
3.tb_demo.MYI,存储索引
适合场景:
1.选择密集型表 --- MyISAM引擎在筛选大量数据时非常迅速 --- 查询快
2.插入密集型表 --- 并发插入特性允许同时选择和插入数据,适合管理:邮件或Web服务器日志数据
总结:
1.适合做count的计算 (注意:不含where条件的统计,因为MyISAM会记录表的行数)
2.插入不频繁,查询非常频繁
3.没有事务需求
InnoDB: 默认引擎
支持事务处理
引入了行级锁(并发高)和外键约束
不支持全文索引
适合场景:
1.更新密集型表 --- 特别适合处理多重并发的更新请求
2.事务
3.自动灾难恢复 --- InnoDB表能够自动从灾难中恢复
4.外键约束 --- MySQL支持外键的存储引擎只有InnoDB
5.支持自动增加列 Auto_INCREMNET属性
6.InnoDB是为处理巨大数据量时的最大性能设计
总结:
可靠性要求高,需要事务支持,并有较高的并发读取频率,适合InnoDB
行锁机制必然决定了写入时的更多性能开销,而它的强项在于多线程的并发处理
表更新和查询都相当的频繁,并且表锁定的机会比较大的情况指定数据引擎的创建
细节和具体实现的差别:
1.InnoDB不支持FULLTEXT类型的索引。
2.InnoDB 中不保存表的具体行数,也就是说,执行select count(*) from table时,InnoDB要扫描一遍整个表来计算有多少行,但是MyISAM只要简单的读出保存好的行数即可。注意的是,当count(*)语句包含 where条件时,两种表的操作是一样的。
3.对于AUTO_INCREMENT类型的字段,InnoDB中必须包含只有该字段的索引,但是在MyISAM表中,可以和其他字段一起建立联合索引。
4.DELETE FROM table时,InnoDB不会重新建立表,而是一行一行的删除。
5.LOAD TABLE FROM MASTER操作对InnoDB是不起作用的,解决方法是首先把InnoDB表改成MyISAM表,导入数据后再改成InnoDB表,但是对于使用的额外的InnoDB特性(例如外键)的表不适用。
另外,InnoDB表的行锁也不是绝对的,如果在执行一个SQL语句时MySQL不能确定要扫描的范围,InnoDB表同样会锁全表,例如update table set num=1 where name like “%aaa%”
任何一种表都不是万能的,只用恰当的针对业务类型来选择合适的表类型,才能最大的发挥MySQL的性能优势。
存储引擎选择依据?
是否需要支持事务;
是否需要使用热备;
崩溃恢复:能否接受崩溃;
是否需要外键支持;
是否需要全文索引
经常使用什么样的查询模式
数据量大小
eg:
需要事务和外键约束 -- InnoDB
需要全文索引 -- MyISAM
数据量大,倾向于InnoDB,因为它支持事务处理和故障恢复,InnoDB可以利用事务日志进行数据恢复,这会比较快。而MyISAM可能会需要几个小时甚至几天来干这些事,InnoDB只需要几分钟
操作数据表的习惯,也会影响性能
eg:
COUNT() 在 MyISAM 表中会非常快,而在InnoDB 表下可能会很痛苦(
因为InnoDB不保存表的行数,即:执行select count(*) from table时,InnoDB要扫描一遍整个表来计算有多少行,但是MyISAM只要简单的读出保存好的行数即可,
注意的是,当count(*)语句包含 where条件时,两种表的操作是一样的)
主键查询在InnoDB下非常快,但如果主键太长也会导致性能问题
大批的inserts语句在MyISAM下回快一些,但updates语句在InnoDB下更快(尤其在并发量大的时候)
提示InnoDB性能的方法:
InnoDB支持事务,存储过程,视图和行级锁,在高并发下,表现比MyISAM强很多
影响性能的配置:
innodb_flush_log_at_trx_commit 这个选项,如果设置为1的话,那么每次插入数据的时候都会自动提交,导致性能急剧下降,应该是跟刷新日志有关系,设置为0效率能够看到明显提升
当然,同 样你可以SQL中提交“SET AUTOCOMMIT = 0”来设置达到好的性能
设置innodb_buffer_pool_size能够提升InnoDB的性能
设置查询缓存
2.配置文件my.ini参数优化
1.max_connections --- 最大并发连接数,允许的同时客户连接数, 默认100, 建议根据需求设定,eg:1024
2.query_cache_size=0 --- 查询缓存,用于缓存select 查询结果,如果有许多返回相同查询结果的SELECT查询,并且很少改变表,可以设置query_cache_size大于0,可以极大改善查询效率。而如果表数据频繁变化,就不要使用这个,会适得其反
3.table_cache=256
4.thread_cache_size --- 缓存的最大线程数
5.sort_buffer --- 每个需要进行排序的线程分配该大小的一个缓冲区
6.wait_timeout --- 默认是28800秒,也就是说一个connection空闲超过8个小时,Mysql将自动断开该connection,通俗的讲就是一个连接在8小时内没有活动,就会自动断开该连接。 不要设置太长,建议 7200
7.default-storage-engine=INNODB # 创建新表时将使用的默认存储引擎
配置示例,2G内存,针对站多,抗压型的设置,最佳:
table_cache=1024 物理内存越大,设置就越大.默认为2402,调到512-1024最佳
innodb_additional_mem_pool_size=4M 默认为2M
innodb_flush_log_at_trx_commit=1
(设置为0就是等到innodb_log_buffer_size列队满后再统一储存,默认为1)
innodb_log_buffer_size=2M 默认为1M
innodb_thread_concurrency=8 你的服务器CPU有几个就设置为几,建议用默认一般为8
key_buffer_size=256M 默认为218 调到128最佳
tmp_table_size=64M 默认为16M 调到64-256最挂
read_buffer_size=4M 默认为64K
read_rnd_buffer_size=16M 默认为256K
sort_buffer_size=32M 默认为256K
max_connections=1024 默认为1210
thread_cache_size=120 默认为60
query_cache_size=64M
一般:
table_cache=512
innodb_additional_mem_pool_size=8M
innodb_flush_log_at_trx_commit=0
innodb_log_buffer_size=4M
innodb_thread_concurrency=8
key_buffer_size=128M
tmp_table_size=128M
read_buffer_size=4M
read_rnd_buffer_size=16M
sort_buffer_size=32M
max_connections=1024
更多参考:
http://www.cnblogs.com/adolfmc/p/6056392.html
3.Query查询优化
1.explain sql 查看执行效率,定位优化对象的性能瓶颈
2.永远用小结果驱动大的结果集
3.尽可能在索引中完成排序
4.只取出自己需要的column,而不是*
5.使用最有效的过滤条件
6.用表连接代替子查询
7.当只要一行数据时,使用limit 1
8.为搜索字段建立索引
9.千万不要ORDER BY RAND(),避免select *
10.尽可能使用NOT NULL
11.开启查询缓存,并为查询缓存优化查询语句
eg:
select username from user where add_time >= now()
注意:
1.这样的语句不会使用查询缓存,
2.像NOW()和RAND()或是其它的诸如此类的SQL函数都不会开启查询缓存,因为这些函数的返回是会不定的易变的。所以,你所需要的就是用一个变量来代替MySQL的函数,从而开启缓存
3.修改, 对now()进行处理,只取年月日 yyyy-MM-dd,变为一个不衣变的值
62.Java常见的锁类型有哪些?请简述其特点。
1、synchronized对象同步锁:synchronized是对对象加锁,可作用于对象、方法(相当于对this对象加锁)、静态方法(相当于对Class实例对象加锁,锁住的该类的所有对象)以保证并发环境的线程安全。同一时刻只有一个线程可以获得锁。
其底层实现是通过使用对象监视器Monitor,每个对象都有一个监视器,当线程试图获取Synchronized锁定的对象时,就会去请求对象监视器(Monitor.Enter()方法),如果监视器空闲,则请求成功,会获取执行锁定代码的权利;如果监视器已被其他线程持有,线程进入同步队列等待。
2、Lock同步锁:与synchronized功能类似,可从Lock与synchronized区别进行分析:
1、Lock可以通过tryLock()方法非阻塞地获取锁而。如果获取了锁即立刻返回true,否则立刻返回false。这个方法还有加上定时等待的重载方法tryLock(long time, TimeUnit unit)方法,在定时期间内,如果获取了锁立刻返回true,否则在定时结束后返回false。在定时等待期间可以被中断,抛出InterruptException异常。而Synchronized在获得锁的过程中是不可被中断的。
2、Lock可以通过lockInterrupt()方法可中断的获取锁,与lock()方法不同的是等待时可以响应中断,抛出InterruptException异常。
3、Synchronized是隐式的加锁解锁,而Lock必须显示的加锁解锁,而且解锁应放到finnally中,保证一定会被解锁,而Synchronized在出现异常时也会自动解锁。但也因为这样,Lock更加灵活。
4、Synchronized是JVM层面上的设计,对对象加锁,基于对象监视器。Lock是代码实现的。
3、可重入锁:ReentrantLock与Synchronized都是可重入锁。可重入意味着,获得锁的线程可递归的再次获取锁。当所有锁释放后,其他线程才可以获取锁。
4、公平锁与非公平锁:“公平性”是指是否等待最久的线程就会获得资源。如果获得锁的顺序是顺序的,那么就是公平的。不公平锁一般效率高于公平锁。ReentrantLock可以通过构造函数参数控制锁是否公平。
5、ReentrantReadWriteLock读写锁:是一种非排它锁, 一般的锁都是排他锁,就是同一时刻只有一个线程可以访问,比如Synchronized和Lock。读写锁就多个线程可以同时获取读锁读资源,当有写操作的时候,获取写锁,写操作之后的读写操作都将被阻塞,直到写锁释放。读写锁适合写操作较多的场景,效率较高。
6、乐观锁与悲观锁:在Java中的实际应用类并不多,大多用在数据库锁上,可参看:http://blog.csdn.net/sdyy321/article/details/6183412
7、死锁:是当两个线程互相等待获取对方的对象监视器时就会发生死锁。一旦出现死锁,整个程序既不会出现异常也不会有提示,但所有线程都处于阻塞状态。死锁一般出现于多个同步监视器的情况。
63.volatile与automicInteger是什么?如何使用?
在并发环境中有三个因素需要慎重考量,原子性、可见性、有序性。
voatile 保证了有序性(防止指令冲排序)和变量的内存可见性(每次都强制取主存数据),每次取到volatile变量一定是最新的
volatile主要用于解决可见性,它修饰变量,相当于对当前语句前后加上了“内存栅栏”。使当前代码之前的代码不会被重排到当前代码之后,当前代码之后的指令不会被重排到当前代码之前,一定程度保证了有序性。而volatile最主要的作用是使修改volatile修饰的变量值时会使所有线程中的缓存失效,并强制写入公共主存,保证了各个线程的一致。可以看做是轻量级的Synchronized。详情可参看:http://www.cnblogs.com/dolphin0520/p/3920373.html。
automicXXX主要用于解决原子性,有一个很经典的问题:i++是原子性的操作码?答案是不是,它其实是两步操作,一步是取i的值,一步是++。在取值之后如果有另外的线程去修改这个值,那么当前线程的i值就是旧数据,会影响最后的运算结果。使用automicXXX就可以非阻塞、保证原子性的对数据进行增减操作。详情可参看:http://ifeve.com/java-atomic/
volatile原理:
1.volatile可以保证线程可见性,且提供了一定的有序性,但无法保证原子性。
1.保证可见性,不保证原子性
2.禁止指令重排序
2.JVM底层,volatile采用 "内存屏障" 来实现
可见性实现:
可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值
synchronize和锁都可以保证可见性。
线程本身并不直接与主存进行数据交换,而是通过线程的工作内存来完成相应的操作 --- 线程间数据不可见的根本原因!!!
volatile实现可见性,直接从这方面入手,
1.修改volatile变量时,会强制将修改后的值刷新到主内存中
2.修改volatile变量后,会导致其他线程工作内存中对应的变量值失效,再读取该变量值时,要重新从主内存中读取
有序性实现:
关于重排序:
编译器重排序:不改变单线程语义的前提下,可以重新安排语句的执行顺序
处理器重排序:不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序
指令重排序对单线程无影响,但会影响多线程的正确性,
JVM如何禁止重排序?
happens-before 原则:保证程序的有序性
参考:http://www.cnblogs.com/chenssy/p/6393321.html
注:在此列举的只是Java多线程最基础的知识,也是面试官最常问到的,先打牢基础,再去探讨底层原理或者高级用法,除了这十个问题,在此再推荐一些其他的资料:
JVM底层又是如何实现synchronized的:http://www.open-open.com/lib/view/open1352431526366.html
Java线程池详解:http://blog.csdn.net/zhangliangzi/article/details/52389766
Java线程池深度解析:http://www.cnblogs.com/dolphin0520/p/3932921.html
ConcurrentHashMap原理分析:http://www.cnblogs.com/ITtangtang/p/3948786.html
Java阻塞队列详解:http://ifeve.com/java-blocking-queue/
64.MyBatis中timestamp时间类型,在Spring MVC出参时无法转为正确的时间类型?
在xml中配置: javaType为 java.sql.Timestamp
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" javaType="java.sql.Timestamp"/>
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime" javaType="java.sql.Timestamp"/>
65.Datatable自定义搜索
//自定义搜索,每次只能根据一个维度进行搜索(按渠道或产品类型)
$("#channel_select,#brand_select").change(function(){
var tsval = $(this).val()
table.search(tsval, false, false).draw();
});
66.synchronized和Lock的底层实现原理?
参考:http://www.open-open.com/lib/view/open1352431526366.html
锁原理:http://blog.csdn.net/Luxia_24/article/details/52403033
synchronized 在软件层面依赖JVM
Lock 在硬件层面依赖特殊的CPU指令 --- CAS + JNI调用CPU指令来实现
synchronized 可以吧任何一个非 null 对象作为 "锁",
作用于方法上时,锁住的是对象实例this,
作用于静态方法,锁住的是对象对应的Class实例,因为Class数据存储在永久带,因此静态方法锁相当于该类的全局锁,
作用于某个对象实例,锁住的是对应的代码块
HotSpot JVM中,锁 --- 对象监视器(对象来监视线程的互斥) --- synchronized的实现原理
对象监视器,设置几种状态来区分请求的线程:
Contention Set: 所有请求锁的线程,被首先放置到该竞争队列 --- 先进后出的虚拟队列,会被线程并发访问
Entry Set:
等待获取锁的线程(来自Contention Set)排队队列
--- 等待获取对象锁运行
Wait Set:
获取锁后,调用wait()方法,被阻塞的线程队列
--- 等待再次获取对象锁
OnDeck: 任何时刻最多只能有一个线程正在竞争锁,该线程称为OnDeck
Owner: 获得锁的线程称为Owner
!Owner: 释放锁的线程
说明:
1.Entry Set 和Contention Set 同属等待队列,
2.Contention Set会被线程并发访问,为了降低对Contention Set队尾的争用(为了减少加入与取出两个线程对于contentionList的竞争),而建立Entry Set,如果Entry Set为空,则从Contention Set队尾取出节点
3.Owner线程在unlock时,会从Contention Set中迁移线程到Entry Set,并会指定Entry Set中的某个线程(一般为Head)为Read(OnDeck)线程
4.Owner线程并不是把锁传递给OnDeck线程,只是把竞争锁的权利交给OnDeck,OnDeck线程需要重新竞争锁
5.OnDeck线程获得锁喉变为Owner线程,无法获得锁的线程依然留在Entry Set中
6.如果Owner线程被wait()方法阻塞,则转移后WaitSet中,如果某个时刻被notify/notifyAll唤醒,则再次转移到EntrySet
线程的互斥,其实是线程对同一个对象的监视器monitor的操作:
每个对象都有一个监视器(monitor)!,当monitor被占就会处于锁定状态,线程执行monitorentry指令时尝试获取monitor的所有权
1.如果monitor的进入数为0,则线程进入monitor,然后将进入数设置为1,线程即为monitor的所有者
2.如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数 +1 --- 锁可重入 --- ReentrantLock 和synchronized 都是 可重入锁
3.其他线程已经占用了monitor,则该线程进入阻塞状态,知道monitor的进入数为0,再尝试获取monitor的所有权
4.线程调用一次unlock()释放锁,monitor的进入数就 -1 (只有monitor进入数为0,才能被其他线程抢占)
5.一个线程获取多少次锁,就必须释放多少次锁,对于synchronized内置锁 ,每一次进入和离开synchronized方法(代码块),就是一个完整的锁获取和释放
sleep()不会释放锁,等待指定时间后继续运行
wait()会释放锁,进入对象监视器的 Wait Set队列,等待被唤醒,被唤醒后,需要重新获取锁
wait()和notify/notifyAll必须成对出现,而且必须放在synchronized中,
yield() 不会释放锁, 只是让当前线程让出CPU占用权
Synchronized底层优化 --- 偏向锁、轻量级锁
参考:http://www.cnblogs.com/paddix/p/5405678.html
http://www.jianshu.com/p/5dbb07c8d5d5
Synchronized效率低的原因?
Synchronized是通过对象内部的对象监视器锁(monitor)来实现的,monitor本质是依赖于底层的操作系统的Mutex Lock(互斥锁)来实现,
操作系统实现线程间切换需要从用户态转到内核态(JVM转到操作系统内核),这个成本非常高,状态转换需要相对比较长的时间,这就是为什么Synchronized效率低的原因
Synchronized底层优化:
1.锁的4种状态:
无锁状态、偏向锁、轻量级锁、重量级锁
随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级到重量级锁(锁升级只能从低到高升级,不会出现锁的降级)
JDK1.6默认开启偏向锁和轻量级锁,通过-XX:-UseBiasedLocking来禁用偏向锁
锁的状态保存在对象的头文件中
重量级锁 --- 依赖于操作系统Mutex Lock所实现的锁, 需要从JVM转到操作系统内核,进行互斥操作
轻量级锁 --- 并不是用来代替重量级锁,本意是在没有多线程竞争的前提下,减少传统的重量级锁使用产生的性能消耗
轻量级锁目的:
为了在线程交替执行同步块时提高性能 !!!
轻量级锁适用场景:
线程交替执行同步块的情况 ---- 锁竞争不激烈的情况!
如果存在同一时间访问同一个锁,就会导致轻量级锁升级为重量级锁
偏向锁 --- 为了在无多线程竞争的情况下,尽量减少不必要的轻量级锁执行路径
一旦线程第一次获得了监视对象,之后让监视对象 "偏向"这个线程,在该线程重复获取锁时,避免CAS操作
即:
设置一个变量,如果发现是true,无需再走加锁、解锁的流程!
偏向锁目的:
解决无竞争下的锁性能问题,在只有一个线程执行同步块时,进一步提高性能
总结:
ynchronized的底层实现主要依靠Lock-Free的队列,基本思路是自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量 !!!
67.Spring如何解决Bean的循环依赖? --- 只支持Singleton作用域的, setter方式的循环依赖!!!
参考:https://my.oschina.net/yibuliushen/blog/737640
http://blog.csdn.net/caomiao2006/article/details/46511123
Spring容器循环依赖包括构造器循环依赖和setter循环依赖
如果是构造器循环依赖,Spring容器将无法启动,报循环依赖异常BeanCurrentlInCreationException
解决方式:
将构造器注入方式改为属性注入方式 --- setter
Spring 支持setter方法注入属性方式的循环依赖
Spring中将循环依赖的处理分3中情况:
1.构造器循环依赖 --- 原理, Spring 不支持构造器方式的循环依赖
通过构造器注入构成的循环依赖是无法解决的,只能在容器启动时抛出BeanCurrentlInCreationException异常 --- 表示循环依赖
Spring容器将每一个正在创建的Bean的标识符(id)放到 "当前创建bean池" 中,bean标识符在创建过程中将一直保持在这个池中,
如果在创建bean过程中,发现自己已经在 "当前创建bean池"里时,将抛出 BeanCurrentlInCreationException异常,表示循环依赖,
而对于创建完毕的bean将从 "当前创建bean池"中清除掉
eg:
如在创建TestA类时,构造器需要TestB类,那将去创建TestB,在创建TestB类时又发现需要TestC类,则又去创建TestC,
最终在创建TestC时发现又需要TestA,从而形成一个环,没办法创建 --- 循环依赖,抛出 BeanCurrentlInCreationException异常
配置文件;
<bean id="testA" class="com.bean.TestA">
<constructor-arg index="0" ref="testB"/>
</bean>
<bean id="testB" class="com.bean.TestB">
<constructor-arg index="0" ref="testC"/>
</bean>
<bean id="testC" class="com.bean.TestC">
<constructor-arg index="0" ref="testA"/>
</bean>
测试用例:
@Test(expected = BeanCurrentlyInCreationException.class)
public void testCircleByConstructor() throws Throwable {
try {
new ClassPathXmlApplicationContext("test.xml");
} catch (Exception e) {
//因为要在创建testC时抛出;
Throwable ee1 = e.getCause().getCause().getCause();
throw e1;
}
}
分析:
Spring容器创建"testA"bean,首先去"当前创建bean池"查找是否当前bean正在创建,如果没发现,则继续准备其需要的构造器参数"testB",并将"testA"标识符放到"当前创建bean池"。
Spring容器创建"testB"bean,首先去"当前创建bean池"查找是否当前bean正在创建,如果没发现,则继续准备其需要的构造器参数"testC",并将"testB"标识符放到"当前创建bean池"。
Spring容器创建"testC"bean,首先去"当前创建bean池"查找是否当前bean正在创建,如果没发现,则继续准备其需要的构造器参数"testA",并将"testC"标识符放到"当前创建Bean池"。
到此为止Spring容器要去创建"testA"bean,发现该bean标识符在"当前创建bean池"中,因为表示循环依赖,抛出BeanCurrentlyInCreationException。
说明:
Spring中bean默认是单例的,对于singleton作用于的Bean,可通过setAllowCircularReferences(false)来禁用循环引用
2.Setter循环依赖 --- 原理, Spring支持setter方式注入属性的循环依赖!
setter注入方式构成的循环依赖,通过Spring容器提前暴露刚完成构造器注入但未完成其他步骤(eg:setter注入)的bean来完成的,
而且只能解决Singleton单例作用域的bean循环依赖,通过提前暴露一个单例工厂方法,从而使其他bean能引用到该bean(注意:此时仅仅只是生了一个bean,该bean还未调用其他方法,如setter注入)
对单例Bean循环依赖的处理:通过递归方法,找出当前Bean的所有依赖Bean,然后提前缓存起来
原理:
创建Bean A时,先通过无参构造器创建一个A实例,此时属性都是空的,但对象引用已经创建创建出来,然后把Bean A的引用提前暴露出来,
然后setter B属性时,创建B对象,此时同样通过无参构造器,构造一个B对象的引用,并将B对象引用暴露出来。
接着B执行setter方法,去池中找到A(因为此时,A已经暴露出来,有指向该对象的引用了),这样依赖B就构造完成,也初始化完成,然后A接着初始化完成,
循环依赖就这么解决了!!!
总结:
先创建对象引用,再通过setter()方式,给属性赋值,层层创建对象 !!!
Bean A初始化时,先对其依赖B进行初始化,同时,通过默认无参构造器,生成自己的引用,而不调用其setter()方法,
当B对象创建时,如果还依赖C,则也通过无参构造器,生成B的引用,
C对象创建时,如果引用了A,则去对象池中查到A的引用,然后调用setter()方式,注入A,完成C对象的创建
C创建完成后,B使用setter()方式,注入C,完成B对象创建,
B对象场景完成后,A使用setter()方式,注入B,完成A对象创建,
最终,完成setter()方式的循环依赖!
如果循环依赖的都是单例对象(都是通过setter方式注入属性的),那么这个肯定没问题,放心使用即可!!!
如果一个是单例,一个是原型,那么一定要保证单例对象能提前暴露出来,才可以正常注入属性!!!
3.prototype范围的依赖处理
对于"prototype"作用域bean,Spring容器无法完成依赖注入,因为Spring容器不进行缓存"prototype"作用域的bean,因此无法提前暴露一个创建中的bean
这个spring也无能为力,因为是原型对象,A创建的时候不会提前暴露出来,所以,每次都是要创建,创建的时候,发现有相同的对象正在创建,同样报错,循环依赖错误
4.Spring创建Bean的源码解释:
1.创建Bean的入口
AbstractBeanFactory-->doGetBean()
Object sharedInstance = getSingleton(beanName); //从缓存中查找,或者如果当前创建池中有并且已经暴露出来了,就返回这个对象
2.创建单例Bean方法
DefaultSingletonBeanRegistry-->getSingleton(String beanName, ObjectFactory<?> singletonFactory)
3.创建真正对象
AbstractAutowireCapableBeanFactory-->doCreateBean
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
} 注意这一步很关键,是调用构造方法创建一个实例对象,如果这个构造方法有参数,而且就是循环依赖的参数,那么这个对象就无法创建了,
因为到这里对象没有创建,也没有暴露当前对象,如果是无参的构造方法,那么就可以,先创建一个对象,尽管所有的属性都为空
68.Spring事务管理的原理?
参考:http://www.codeceo.com/article/spring-transactions.html
声明式事务管理,在Service之上或Service的方法之上,添加 @Transactional注解
@Transactional如何工作?
Spring在启动时,会去解析生成相关的Bean,这是会查看拥有相关注解的类和方法,
并且为这些类和方法生成代理,并根据 @Transactional的相关参数进行相关配置注入,
这样就在代理中把相关的事务处理掉了(开启正常提交事务,异常回滚事务)
真正的数据库层,事务提交和回滚是通过binlog和redo log实现的
Spring事务管理机制实现原理:
参考:
http://www.jianshu.com/p/4312162b1458
http://www.cnblogs.com/duanxz/p/3750845.html
http://www.92to.com/bangong/2016/11-05/12533010.html
在调用一个需要事务的组件时,管理器首先判断当前调用(即:当前线程)有没有事务,如果没有事务则启动一个事务,并把事务与当前线程绑定,
Spring使用TransactionSynchronizationManager的bindResource方法将当前线程与一个事务绑定,采用的方式就是ThreadLocal,
参考:DataSourceTransactionManager的启动事务用的代码 doBegin()
通过动态代理或AOP方式,对所有需要事务管理的Bean进行加载,生成代理对象,并根据配置在invoke()方法中对当前调用的方法名进行判定,
并在method.invoke()方法前后为其加上合适的事务管理代码,根据method.invoke()执行结果,正常提交事务,异常回滚事务
实现了EntityManager接口的持久化上下文代理,包含3个组成部分:
1.EntityManager Proxy本身
2.事务的切面
3.事务管理器
遇到过的问题:
参考:59,为何声明式事务没有生效?
69.Spring如何处理高并发?高并发下,如何保证性能?
1.单例模式 + ThreadLocal
单例模式大大节省了对象的创建和销毁,有利于性能提高,ThreadLocal用来保证线程安全性
Spring单例模式下,用ThreadLocal来切换不同线程直接的参数,用ThreadLocal是为了保证线程安全,实际上,ThreadLocal的key就是当前线程的Thread实例
单例模式下,Spring把每个线程可能存在线程安全问题的参数值放进了ThreadLocal,虽然是一个实例,但在不同线程下的数据是相互隔离的,
因为运行时创建和销毁的bean大大减少了,所以大多数场景下,这种方式对内存资源的消耗较少,并且并发越高,优势越明显
2.ThreadLocal
相比同步机制,ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。
因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,
在编写多线程代码时,可以把不安全的变量封装进ThreadLocal !!!
3.Spring MVC在并发访问时,是否会存在线程安全问题?
参考:http://blog.csdn.net/csluanbin/article/details/50930138
http://blog.csdn.net/a236209186/article/details/61460211
Struts2是基于类的拦截(每次处理请求,都会实例化一个对象Action,不会有线程安全问题)
Spring MVC 是基于方法的拦截,粒度更细,而Spring的Controller默认是Singleton的,即:每个request请求,系统都会用同一个Controller去处理,
Spring MVC和Servlet都是方法级别的线程安全,如果单例的Controller或Servlet中存在实例变量,都是线程不安全的,而Struts2确实是线程安全的
优点:
不用每次创建Controller,减少了对象创建和销毁
缺点:
Controller是单例的,Controller里面的变量线程不安全
解决方案:
1.在Controller中使用ThreadLocal变量,把不安全的变量封装进ThreadLocal,使用ThreadLocal来保存类变量,将类变量保存在线程的变量域中,让不同的请求隔离开来
2.声明Controller为原型 scope="prototype",每个请求都创建新的Controller
3.Controller中不使用实例变量
Spring MVC 如何保证request对象线程安全?
参考:http://blog.csdn.net/csluanbin/article/details/50930138
InvocationHandler接口:这是springmvc保证request对象线程安全的核心。
通过实现该接口,开发者能够在Java对象方法执行时进行干预,搭配Threadlocal就能够实现线程安全
问题:判断一下程序是否线程安全?
@Controller
public class UserController{
@Autowired
private HttpSession session
@RequestMapping(xxxxxxx)
public void getUser{
session.get ...
session.set...
....
}
}
结论:
该程序是线程安全的
解析:
项目启动和运行时,Controller对象中的HttpSession并不是HttpSession实例,而是一个代理,
是org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler代理了HttpSession ,可通过这个代码求证:System.out.println(Proxy.getInvocationHandler(session));
只要当你真正调用HttpSession中的非java.lang.Object方法时才会真真去调用被代理的HttpSession里面的方法
说一下session.get ...过程:首先从对象工厂从Threadlocal中取得HttpSession实例,然后通过反射调用该实例的set方法
特别注意:
1.Spring 应该是在请求进来的时候ThreadLocal.set(Session),然后在请求的生命周期中都是一个Thread ,执行完后ThreadLocal.remove(Session)
2.一个请求使用一个ThreadLocal,绑定对应的HttpSession,所以是线程安全的
3.对于 "注入" 到Controller中的单例对象, 都是由Spring统一管理的,Spring对注入Controller的对象使用了ThreadLocal + 代理机制,保证了线程安全
4.但是,对于在Controller中直接定义的实例变量,是线程不安全的!!!
eg:
@RestController
@RequestMapping("/test1")
public class ControllerTest1 {
private int i = 0;
@GetMapping("/count")
public void test1(){
System.out.println(i++);
}
//验证Controller中的实例变量,线程不安全
@GetMapping("/t1")
public void test3(){
ExecutorService service = Executors.newFixedThreadPool(100);
for (int i=0;i<1000;i++) {
service.execute(new Runnable() {
@Override
public void run() {
HttpUtils.sendGet("http://localhost:8080/test1/count", null);
}
});
}
}
}
调用:http://localhost:8080/test1/t1 方法,使用多线程对i进行操作,发现i的结果不是999,证明Controller中的实例变量是线程不安全的!
结论:
1.对于单例的Controller,Service中定义的实例变量,都不是线程安全的!!!
2.尽量避免在Controller和Service中定义多线程共享的实例变量
3.Spring使用ThreadLocal + InvocationHandler(动态代理)提供了高并发访问的性能
4.对于Controller和Service中的实例变量,多线程访问时,需要加锁处理 或 设置 scope = "prototype"为每个请求创一个对象
5.对于 @Autowire注入的HttpServletRequest和HttpSession,Spring进行了特殊处理,不会有线程安全问题
70.ConcurrentLinkedQueue与BlockingQueue
ConcurrentLinkedQueue源码解析: http://ifeve.com/concurrentlinkedqueue/
https://www.ibm.com/developerworks/cn/java/j-lo-concurrent/index.html
2类线程安全的队列:
1.阻塞队列 --- 阻塞算法 --- 队列使用一个锁(入队和出队用同一把锁)或两个锁(入队和出队用不同的锁)等方式实现
2.同步队列 --- 非阻塞算法 --- 使用循环CAS方式实现
LinkedBlockingQueue 线程安全的阻塞队列,实现了BlockingQueue接口,BlockingQueue继承自java.util.Queue接口,
并在接口基础上增加了take()和put()方法, 这2个方法正式队列操作的阻塞版本
先进先出,可以指定容量,默认最大是Integer.MAX_VALUE;其中主要用到put和take方法,put方法在队列满的时候会阻塞直到有队列成员被消费,take方法在队列空的时候会阻塞,直到有队列成员被放进来!!!
put() 向队列中放数据 take() 从队列中取数据
ConcurrentLinkedQueue 是Queue的一个安全实现.Queue中元素按FIFO原则进行排序.采用CAS操作,来保证元素的一致性。
非阻塞方式实现的无界线程安全队列 !!!
offer()添加元素, poll()获取元素 isEmpty()判断队列是否为空 (特别注意,不用size(),效率低,会遍历队列,尽量要避免用size而改用isEmpty())
采用CAS操作,允许多个线程并发执行,并不会因为你加锁而阻塞线程,使得并发性能更好!!!
使用:http://www.cnblogs.com/dmir/p/4907515.html
http://blog.csdn.net/sunxianghuang/article/details/52046150
ConcurrentLinkedQueue源码解析:http://ifeve.com/concurrentlinkedqueue/
总结:
多数生产消费模型的首选数据结构就是队列(先进先出)。Java提供的线程安全的Queue可以分为阻塞队列和非阻塞队列,
其中阻塞队列的典型例子是BlockingQueue,非阻塞队列的典型例子是ConcurrentLinkedQueue,在实际应用中要根据实际需要选用阻塞队列或者非阻塞队列
Java中的7种阻塞队列
阻塞队列: --- 阻塞的是线程操作(拿和取元素)
常用于生产者和消费者场景,是生产者用来存放元素、消费者用来获取元素的容器
put()阻塞:队列满时,阻塞插入元素的线程,直到队列不满
take()阻塞:队列空时,阻塞获取元素的线程,直到队列不空
ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
DelayQueue:一个使用优先级队列实现的无界阻塞队列。
SynchronousQueue:一个不存储元素的阻塞队列。 生产者和消费者直接传递数据,不对数据作缓存,生产者和消费者通过在队列里排队的方式来阻塞和唤醒 --- 速度快
线程数少时,使用SynchronousQueue 速度更快!!!
LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列
关于PriorityBlockingQueue优先级队列:
传统队列都是FIFO,优先级队列,通过Comparator或Comparable接口,根据元素的比较排序,确定出队列的顺序(优先级:即在compare()中指定的顺序)
1.PriorityQueue是基于优先堆的一个无界队列,元素默认按自认排序或通过提供的Comparator比较器在实例化队列时排序
2.优先级队列不允许空值(无法排序),而且不支持不可比较的对象
eg:
对于自定义类,优先级队列要求,1:类实现Comparable接口 或 2:通过Comparator对对象排序,并且在排序时会按照优先级处理其中的元素。
3.优先级队列的头,是基于自然排序或Comparator排序的最小元素,
如果多个对象拥有同样的顺序,则坑你随机取其中一个,当获取队列时,返回队列的头对象
4.优先队列的大小是不受限制的,但在创建时可以指定初始大小。当我们向优先队列增加元素的时候,队列大小会自动增加
5.PriorityQueue是非线程安全的,所以Java提供了PriorityBlockingQueue(实现BlockingQueue接口)用于Java多线程环境。
ArrayBlockingQueue、LinkedBlockingQueue、ConcurrentLinkedQueue的区别和使用场景?
区别:
1.三者都是线程安全的
2.2个BlockingQueue是阻塞的,ConcurrentLinkedQueue是并发的
3.2个BlockingQueue使用锁机制实现阻塞和线程安全(通过ReentrantLock + Condition阻塞容量为空时的取操作和容量满时的写操作),
ConcurrentLinkedQueue使用cas算法保证线程安全
4.ArrayBlockingQueue使用一个锁(lock + 2个Condition),而LinkedBlockingQueue使用2个锁(锁分离,取用takeLock + Condition,写用putLock+Condition),所以LinkedBlockingQueue的吞吐量大,并发性能比Array高
LinkedBlockingQueue,对头和尾采用不同的锁,提高了吞吐量,适合 "消费者生产者" 模式
ArrayBlockingQueue, 数组实现,使用一把全局锁并行对queue的读写操作,同时使用2个Condition阻塞容量为空时的读操作和容量满时的写操作
5.正因为LinkedBlockingQueue使用两个独立的锁控制数据同步,所以可以使存取两种操作并行执行,从而提高并发效率。
而ArrayBlockingQueue使用一把锁,造成在存取两种操作争抢一把锁,而使得性能相对低下。LinkedBlockingQueue可以不设置队列容量,默认为Integer.MAX_VALUE.其容易造成内存溢出,一般要设置其值
使用场景:
阻塞队列优点:
多线程操作不需要同步,
队列会自动平衡负载,即:生产和消费两边,处理快了会被阻塞,减少两边的处理速度差距,
自动平衡负载特性,造成它能被用于多生产者队列,队列满了就要阻塞等着,直到消费者使队列不满才能继续生产
ConcurrentLinkedQueue:
允许多线程共享访问一个集合,多用于消息队列!!!
多消费者消费同一个 用 ConcurrentLinkedQueue:
BlockingQueueue:
多线程共享时阻塞,多用于任务队列!!!
单消费者用 BlockingQueueue:
总结: 单个消费者用LinkedBlockignQueue, 多消费者用ConcurrentLinkedQueue !!!
单生产者,单消费者 用 LinkedBlockingqueue
多生产者,单消费者 用 LinkedBlockingqueue
单生产者 ,多消费者 用 ConcurrentLinkedQueue
多生产者 ,多消费者 用 ConcurrentLinkedQueue
71.Java多线程同步机制:3种类型
volatile 变量:轻量级多线程同步机制,不会引起上下文切换和线程调度。仅提供内存可见性保证,不提供原子性。 --- 只保证可见性,不保证原子性,不绝对线程安全!!!
CAS 原子指令:轻量级多线程同步机制,不会引起上下文切换和线程调度。它同时提供内存可见性和原子化更新保证。
内部锁(synchronized)和显式锁(各种Lock):重量级多线程同步机制,可能会引起上下文切换和线程调度,它同时提供内存可见性和原子性。
参考:
非阻塞算法在并发容器中的实现:ConcurrentLinkedQueue https://www.ibm.com/developerworks/cn/java/j-lo-concurrent/index.html
72.CAS在JDK中的实现
参考:http://blog.csdn.net/canot/article/details/50759424
1.Synchronized锁机制存在的问题:
(1)在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题。
(2)一个线程持有锁会导致其它所有需要此锁的线程挂起。
(3)如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能风险
优化:
偏向锁、轻量级锁、减小锁粒度
2.锁分类
悲观锁 --- 独占锁 --- synchronized是独占锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁
乐观锁 --- 每次不加锁,而是假设没有冲突,而去完成某项操作,如果因为冲突失败就重试,直到成功为止
3.CAS原理 --- 乐观锁 实现
CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做
CAS操作:
CAS有3个操作数:
V 内存值
A 旧的预期值
B 要修改的新值
当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做!!!
非阻塞算法: 一个线程的失败或挂起,不应该影响其他线程的失败或挂起的算法
CAS的硬件基础和实现原理:
现代的CPU提供了特殊指令,可以自动更新共享数据,而且能够检测到其他线程的干扰,
而compareAndSet()就用这些代替了锁定, compareAndSet利用JNI,借助调用的C语言来完成CPU指令的操作
CAS实现原理:
Java 中通过unsafe类的额compareAndSwap()方法实现的
eg:
AtomicInteger 如何实现无锁下的线程安全?
//在没有锁的机制下可能需要借助volatile原语,保证线程间的数据是可见的(共享的)。这样才获取变量的值的时候才能直接读取。
private volatile int value;
public final int get(){
return value;
}
//i++操作, 每次从内存中读取数据,然后将此数据和 +1 后的结果进行CAS操作,如果成功就返回结果,否则重试直到成功为止
public final int incrementAndGet(){
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
4.CAS的实现 compareAndSet() ---> unsafe.compareAndSwap()
compareAndSwap的4个参数:
参数1:需要改变的对象
参数2:偏移量
参数3:期待的值
参数4:更新之后的值
方法解析:
若调用该方法,value值与expect值相等,则将value修改为update值,并返回true,
如果调用该方法,value值与expect值不相等,则不做任何操作,并返回false
eg:
参考java.util.concurrent.atomic.AtomicLong的实现 i++操作
//+1操作, 一直执行CAS,失败则重试,直到操作成功,返回结果
public final long getAndIncrement() {
while (true) {
long current = get();
long next = current + 1;
//当+1操作成功的时候直接返回,退出此循环
if (compareAndSet(current, next))
return current;
}
}
//调用JNI实现CAS
public final boolean compareAndSet(long expect, long update) {
return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
}
5.CAS的缺点: 参考:http://www.cnblogs.com/zhuawang/p/4196904.html
http://blog.csdn.net/ghsau/article/details/17609747
1.存在ABA问题
ABA问题:
一个变量V,如果变量V初次读取时是A,并在准备赋值时检测到它还是A,
那能说明它的值没有被其他线程修改过了吗? --- 不一定
如果这段时间,它的值被其他线程改为了B,然后又改回了A, CAS操作会误认为它没有被修改过!!!
如何解决CAS中的ABA问题?
解决方案:
参考乐观锁实现机制,
在CAS操作是,带上版本号或时间戳,每修改一次,版本号+1,
不但比较对象是否相等,还有比较版本号是否一致!!!
JDK的解决方案:
java并发包提供了一个带有标记的原子引用类 "AtomicStampedReference",它可以通过控制变量值的版本来保证CAS的正确性!!!
参考:http://blog.hesey.net/2011/09/resolve-aba-by-atomicstampedreference.html
2.循环时间长,开销大 --- 自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销
3.只能保证一个共性变量的原子操作
--- 对一个共享变成可以使用CAS进行原子操作,但是多个共享变量的原子操作就无法使用CAS,这个时候只能使用锁
--- JDK1.5提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作
73.Java内存模型
参考:http://www.cnblogs.com/dongguacai/p/5970076.html
并发的2个关键问题:
1.线程间如何通信 --- 通信是指线程之间以何种机制来交换信息,在命令式编程中,通信机制有两种:共享内存和消息传递;JAVA的并发采用的是共享内存,线程之间的通信总是隐式进行
2.线程间如何同步 --- 同步指程序中用于控制不同线程间操作发生相对顺序的机制,在共享内存并发模型中,同步是显式进行的
Java内存模型:
1.共享变量 --- 分配在堆内存中元素都是共享变量,eg:实例变量、静态变量、数组和对象
2.非共享变量 --- 分配在栈上的都是非共享变量,主要指:局部变量,该变量为线程私有,不会在线程间共享,也不存在内存可见性问题
如果线程A和线程B需要进行通信,必须经过以下2个过程:
主内存 <---> JVM堆
本地内存 <---> 线程工作内存
1.线程A把本次内存中修改过的共享变量属性到主内存中
2.线程B到主内存中读取线程A已经更新过的共享变量到B的线程本地内存中
内存间交互操作:
主要指工作内存(线程本地内存)与主内存之间的交互,即:一个变量如何从主内存拷贝到工作内存,如何从工作内存刷新到主内存的实现细节
Java内存模型定义了8种操作:
lock:
作用于主内存, 把一个变量标识为某个线程独占状态
unlock: 作用于主内存,把一个处于锁定状态的变量释放,释放后变量可被其他线程锁定
read: 作用于主内存,把一个变量从主内存传输到工作内存,用于后面的load操作
load: 作用于工作内存,把read操作从主内存中得到的变量值放入工作内存的变量副本中
use: 作用于工作内存,把变量值传递给执行引擎,每当虚拟机需要使用变量的字节码指令时,将会执行这个操作
assign: 作用于工作内存,把从执行引擎接收到的值,赋值给工作内存的变量,每当虚拟机遇到需要给该变量赋值的字节码指令时执行这个操作
store: 作用于工作内存,把工作内存中的一个变量值传到主内存,以便后续的write操作
write: 作用于主内存,把store操作从工作内存中获取的值赋值给主存中的变量
8个操作的7个原则:
1、不允许read和load,store和write操作单独出现。
2、不允许一个线程丢弃它最近的assign操作,即变量在工作内存中的更新需要同步到主内存中。
3、不允许线程无原因地(没有发生过任何assign操作)把数据同步到主内存。
4、一个新的变量只能在主内存中产生,不能在工作内存中直接使用未被初始化的变量。
5、一个变量在同一时刻只能被一个线程lock,并且lock和unlock需要成对出现。
6、如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要执行load或者assgin操作。
7、对一个变量执行unclock之前,必须把此变量同步到主内存中。
74.Zookeeper搭建单机的伪分布式集群
参考:http://www.cnblogs.com/tenghoo/p/windows_zookeeper_pseudo_cluster.html
1.修改3个配置文件
修改confg下的zoo_sample.cfg 为zoo.cfg,修改每个节点的clientPort, 并设置每个节点的dataDir和dataLogDir
Master主节点:
clientPort=2181
dataDir=/tmp/zookeeper/cluster/master/data
dataLogDir=dataDir=/tmp/zookeeper/cluster/master/log
#cluster config
#参数解释:server.A = B:C:D : A表示这个是第几号服务器,B 是这个服务器的 ip 地址;C 表示的是这个服务器与集群中的 Leader 服务器交换信息的端口;D 表示的是万一集群中的 Leader 服务器挂了,需要一个端口来重新进行选举,选出一个新的 Leader
server.1=127.0.0.1:2887:3887
server.2=127.0.0.1:2888:3888
server.3=127.0.0.1:2889:3889
Slave1节点:
clientPort=2182
dataDir=/tmp/zookeeper/cluster/slave1/data
dataLogDir=dataDir=/tmp/zookeeper/cluster/slave1/log
#cluster config
#参数解释:server.A = B:C:D : A表示这个是第几号服务器,B 是这个服务器的 ip 地址;C 表示的是这个服务器与集群中的 Leader 服务器交换信息的端口;D 表示的是万一集群中的 Leader 服务器挂了,需要一个端口来重新进行选举,选出一个新的 Leader
server.1=127.0.0.1:2887:3887
server.2=127.0.0.1:2888:3888
server.3=127.0.0.1:2889:3889
Slave2点:
clientPort=2183
dataDir=/tmp/zookeeper/cluster/slave2/data
dataLogDir=dataDir=/tmp/zookeeper/cluster/slave2/log
#cluster config
#参数解释:server.A = B:C:D : A表示这个是第几号服务器,B 是这个服务器的 ip 地址;C 表示的是这个服务器与集群中的 Leader 服务器交换信息的端口;D 表示的是万一集群中的 Leader 服务器挂了,需要一个端口来重新进行选举,选出一个新的 Leader
server.1=127.0.0.1:2887:3887
server.2=127.0.0.1:2888:3888
server.3=127.0.0.1:2889:3889
2.添加data和log文件
在/tmp/zookeeper/cluster/目录下,添加master/data、master/log文件,2个salve同理
3.创建myid
在/tmp/zookeeper/cluster/目录下的 master/data、slave1/data、salve2/data下创建文件myid,并分别添加内容1,2,3
4.启动3个Server
用jps查看 Zookeeper进程
75.ReentrantLock + Condition 锁的复用,实现线程间通信
面试题:
子线程循环10次,接着主线程循环100,接着又到子线程循环10次,接着再回到主线程循环100,如此循环50次
分析:
1.因为是2个线程的操作,需要线程间通信,wait、notify 或 ReentrantLock + Condition
2.Condition:定义锁的条件队列,当在线程中调用其await()方法时,就会将其存放到锁的条件队列中,
比内置锁的优点:可以对一个锁定义多个条件队列
signal()方法唤醒队列中线程,FIFO顺序唤醒 !!! , 特别注意:Condition是按顺序唤醒队列中线程,不是随机!!!
3.Condition,将Object监视器方法(wait、notify和notifyAll)分解成截然不同的对象,
以便通过将这些对象与任意Lock实现组合使用,为每个对象提供多个等待Set(wait-set), !!!(最重要:Condition为每个对象,通过多个等待队列,按照FIFO先进先出的属性唤醒线程)
Lock替代了synchronized方法和语句,Condition替代了Object监视器方法的使用
、
4.Condition的强大之处:它可以为多个线程间建立不同的Condition
5.Condition,这个条件 可以为锁定的对象,创建多个线程等待队列,实现锁的多路复用!!!
static Lock locks = new ReentrantLock();
static Condition conditonMain = locks.newCondition();
static Condition conditonSun = locks.newCondition();
public static void main(String[] args) {
final ExecutorService executorService = Executors.newFixedThreadPool(100);
for(int bb = 0; bb < 10 ; bb++){
executorService.execute(new Runnable() {
@Override
public void run() {
try {
locks.lock();
for(int i=0 ; i<10 ; i++){
String name = Thread.currentThread().getName();
System.err.println("子线程循环:"+name+"-----"+i);
}
conditonMain.signal();
conditonSun.await();
} catch (Exception e) {
e.printStackTrace();
}finally{
locks.unlock();
}
}
});
try {
locks.lock();
conditonSun.signal();
conditonMain.await();
for(int i = 0 ; i< 100 ; i++){
String name = Thread.currentThread().getName();
System.err.println("主线程循环:"+name+"-----"+i);
}
} catch (Exception e) {
e.printStackTrace();
}finally{
locks.unlock();
}
}
}
关于条件变量Condition
参考:http://www.tuicool.com/articles/6vANna
--- 很大一个程度是为了解决 Object.wait/notify/notifyAll难以使用的问题!
--- Condition(条件队列或条件变量),主要用来等待某个条件的发生,
条件发生后,可以唤醒等待在该线程上的一个线程,或所有线程。
必须与Lock锁一起协同工作!!!
--- 每个Lock可以有任意多个Condition对象,Condition与Lock是绑定的,
--- 如果Lock是公平锁,线程按照FIFO的顺序从Condition.await()中释放,如果是非公平锁,后续的锁竞争就不保证FIFO顺序了
await* 对应于 Object.wait , signal 对应于 Object.notify , signalAll 对应于 Object.notifyAll
--- 实例:concurrent包中阻塞队列的实现,基本都是基于ReentrantLock + Condition的
76.Java并发编程的3个辅助类 CountDownLatch、CyclicBarrier、Semaphore
参考:http://blog.csdn.net/zhangliangzi/article/details/52526125
77.ConcurrentHashMap并不是绝对线程安全的?
ConcurrentHashMap原理: http://www.cnblogs.com/ITtangtang/p/3948786.html
78.HashMap、LinkedHashMap、TreeMap
HashMap: 用key做hash算法,然后将hash值映射到内存地址,直接取key所对应的的数据value
底层实现:数组(hash寻址)+链表(冲突解决),内存地址即:数组的下标索引
存入元素无序,不可重复
高性能保证: 只要HashCode() 和 Hash() 方法实现得足够好,能够尽可能地减少冲突的产生,那么对 HashMap 的操作几乎等价于对数组的随机访问操作
如果 HashCode() 或者 Hash() 方法实现较差,在大量冲突产生的情况下,HashMap 事实上就退化为几个链表,对 HashMap 的操作等价于遍历链表,此时性能很差
LinkedHashMap: 继承自HashMap,在其基础上,内部增加了一个链表,用以存放元素的顺序
底层实现:Hash表+链表, 依靠双向链表保证了迭代属性是插入的顺序,维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序可以是插入顺序或者是访问顺序
存放元素有序,按存放顺序
TreeMap: 通过红黑树实现的有序Key-Value集合,
元素有序,默认:按key的自然顺序排序
可通过Comparator比较器,对key进行排序
//双重检查锁的单例
public class Sig{
public volatile static sig = null;
private Sig(){}
public static getSig(){
if(sig==null){
synchorinzed(Sig.class){
if(sig==null){
sig = new Sig();
}
}
}
}
}
79.ThreadLocal源码分析:
ThreadLocal类中有一个Map对象,这个Map以每个Thread对象为key,保存了这个线程对应的局部变量值!!!!
实现方式:
http://blog.csdn.net/haoyifen/article/details/51126074
80.MySQL隔离级别和锁的关系
1.事务的4个特征
原子性:事务中包含的操作,看做一个逻辑单元,要么全成功,要么全失败!
一致性:只有合法的数据可以被写入数据库,否则事务应该将其回滚到最初的状态
隔离性:事务允许多个用户对同一数据进行并发访问,而不破坏数据的正确性和完整性,同时并行事务的修改必须与其他并行事务的修改相互独立
持久性:事务结束后,处理结果必须能持久化
2.数据库隔离级别
4个,由低到高:
√: 可能出现 ×: 不会出现
脏读
不可重复读 幻读
Read uncommitted
√
√
√
Read committed
×
√
√
Repeatable read
×
×
√
Serializable
×
×
×
这4个级别可以逐个解决:脏读、不可重复读、幻读这几类问题
MySQL默认隔离级别: Repeatable Read, 可重复读级别
http://lib.csdn.net/article/mysql/52873
81.非阻塞算法在并发容器中的实现
典型:
ConcurrentLinkedQueue
ConcurrentLinkedQueue源码解析: http://ifeve.com/concurrentlinkedqueue/
https://www.ibm.com/developerworks/cn/java/j-lo-concurrent/index.html
1.Java多线程同步机制 --- 3种类型
1.volatile变量:轻量级多线程同步机制,不会引起上下文切换和线程调度。 提供内存可见性和禁止指令排序, 不保证原子性
2.CAS原子指令: 轻量级多线程同步机制,不会引起上下文切换和线程调度。 同时提供可见性和原子性更新保证
3.内部锁(synchronized)和显示锁(各类Lock): 重量级多线程同步机制, 可能会引起上下文切换和线程调度,同时提供内存可见性和原子性
2.多核处理器系统对并发的支持
1.多处理系统提供了特殊的指令来管理对共享数据的并发访问,这些指令能实现原子化的读-改-写操作
Intel和AMD多处理器系统支持 CAS(比较并交换)指令
2.JDK为concurrent.atomic包中的原子类提供了compareAndSet()方法,
compareAndSet()方法使用机器级别的原子指令来原子化的更新值,
CAS 和 concurrent包中的原子类,是非阻塞算法实现并发容器的基础
3.非阻塞算法
1.要想提高并发性,就应该尽量使串行部分达到最大程度的并行
即:最小化串行代码的粒度是提高并发性能的关键 !!!
2.与锁相比,非阻塞算法在更细粒度的层面(机器级别的原子指令)协同多线程间的竞争。
它使得多个线程在竞争相同资源时不会发生阻塞,它的并发性与锁相比有了质的提高;同时也大大减少了线程调度的开销
由于几乎所有的同步原语都只能对单个变量进行操作,这个限制导致非阻塞算法的设计和实现非常复杂
4.非阻塞算法实现
1.基于非阻塞算法实现的并发容器
ConcurrentLinkedQueue: 基于链接节点的的无界线程安全队列
SynchronousQueue: 无容量的阻塞队列,使用双重数据结构来实现非阻塞算法
Exchangeer: 能对元数据进行配对和交互的交换器,使用消除技术来实现非阻塞算法
ConcurrentSkipListMap 一个可以根据key进行排序的可伸缩的并发Map
2.ConcurrentLinkedQueue 非阻塞算法实现的并发安全队列
1.使用CAS原子指令来处理对数据的并发访问 --- 非阻塞算法得以实现的基础
2.head/tail并非总是指向队列的头/尾节点,即:允许队列处于不一致状态,
这个特性把入队/出队时,原本需要一起原子化执行的两个步骤分离开来,从而缩小了入队/出队时需要原子化更新值的范围到唯一变量 --- 非阻塞算法得以实现的关键
3.由于队列有时会处于不一致状态,为此,ConcurrentLinkedQueue使用三个不变式来维护阻塞算法的正确性
4.以批处理方式来更新head/tail,从整体上减少入队/出队操作的开销
5.为了利于垃圾收集,队列使用特有的head更新机制;为了确保从已删除节点向后遍历,可到达所有的费删除节点,队列使用了特有的向后推进策略
3.3个不变式
基本不变式:
在执行方法之前和之后,队列必须要保持的不变式:
1.当入队插入新节点之后,队列中有一个next域为null的(最后)节点
2.从head开始遍历队列,可以访问所有item域不为null的节点
head不变式和可变式:
在执行方法之前和之后,head必须保持的不变式:
1.所有"活着"的节点(指未删除节点),都不能从head通过调用succ()方法遍历可达
2.head不能为null
3.head节点的next域不能引用到自身
在执行方法之前和之后,head的可变式
1.head节点的item域可能为null,也可能不为null
2.允许tail滞后于head,即:从head开始遍历队列,不一定能到达tail
tail不变式和可变式:
在执行方法之前和之后,tail必须保持的不变式:
1.通过tail调用succ()方法,最后节点总是可达的
2.tail不能为null
在执行方法之前和之后,tail的可变式:
1.tail节点的item域可能为null,也可能不为null
2.允许tail滞后于head,即:从head开始遍历队列,不一定能到达tail
3.tail节点的next域可以引用到自身
82.JVM内存模型+GC
1.垃圾回收机制
1.引用计数 对象被引用一次,计数器+1,当不再引用时,引用计数器减一;当引用计数器为0时,对象即可被回
2.根搜索法(可达性算法) 把对象的引用看做图结构,由根节点集合出发,不可达的节点即可回收
根节点包含的5种元素:
Java栈中的对象引用
本地方法栈中的对象引用
运行时常量池的对象引用
方法区中静态属性的对象引用
所有Class对象
2.常用垃圾回收算法
1.标记清除: 2个阶段, 第一阶:标记可用对象, 第二阶段:清除垃圾对象, 效率低,会产生内存碎片(不连续的内存空间),无法再次分配给较大对象
2.复制 : 用于新生代(适合回收生命周期短的对象),将内存分为2个区域,新对象都分配在一个区域,回收时将可用对象连续复制到另一个区域,清空当前区域内存
不会产生内存碎片,但对象分配只有一个区域,内存利用率低
3.标记整理: 适用于老年代,类似标记清除,在其基础上,将可用对象移动到一端连续的内存上,解决了内存碎片问题
4.分代算法: 基于分代特点
年轻代:复制算法
分为较大的Eden区和2个相等的Survivor区(from、to),比例一般是 8:1:1,
对象分配在Eden区,当GC发生时(新生代GC叫Minor GC),将Eden区与from区中的可用对象复制到To区中,
from区和to区互换名称,循环方法,直到发生如下2种请,对象进入老年代:
1.From区内的对象一大存活代数阈值(经历一次Minor GC对象的存活代数+1,经历GC的次数达到设定值), GC时进入To区,直接移动至老年代
2.在回收Eden和from区后,超出to区可容纳范围,则直接将存活对象移动至老年代
特别注意:
大的对象在分配时,直接进入老年代
老年代:标记整理算法
当老年代满时,会触发Full GC(新生代与老年代一起进行GC)
3.常用的垃圾回收器?特点?适合场景? 5类
1.Serial --- 绝对不推荐应用于服务器端!!!
年轻代复制算法、串行回收、Stop the world(GC时停止其他一切工作),适合单核CPU环境, 绝对不推荐应用于服务器端!!!
Serial 提供了老年代回收器 Serial Old,采用标记整理算法,特性与新生代一致, Serial + Serial Old适合客户端场景
2.ParNew --- 推荐用于服务器场景!
相当于Serial的多线程版本,并回收,年轻代用复制算法和"Stop The World"机制,适合多核CPU、低延迟环境,推荐应用于服务器场景
3.Parallel --- 非常适合于服务器场景!!!
与ParNew类似,复制算法、并行回收、“Stop the world”机制,但是与ParNew不同,Parallel可以控制程序吞吐量大小,也被称为吞吐量优先的垃圾收集器
Parallel 也有老年代版本, Parallel Old ,采用标记整理算法
Parallel + Parallel Old 非常适合于服务器场景!!!
4.CMS --- 为高并发、低延迟而生,采用标记-清除算法,会产生大量内存碎片,慎重使用!!!
与Parallel的高吞吐对应,CMS就是为高并发、低延时而生的。采用标记-清除算法、并行回收、“Stop the world”
5.G1 --- JDK新加的GC收集器,不使用分代内存策略(年轻代+老年代)!!!,而是将内存分为多个相等的区域
4.GC优化方案
1.不用显示调用System.gc()
JVM接受这个消息后,并不是立即做垃圾回收,而只是对几个垃圾回收算法做了加权,使垃圾回收操作容易发生,或提早发生,或回收较多而已。但即便这样,很多情况下它会触发Full GC,也即增加了间歇性停顿的次数
2.尽量减少临时对象的使用
3.对象不用时,最好显示置为null
4.尽量使用StringBuffer代替String累加字符串
5.尽量使用基本类型,代替包装类
6.尽量少用静态对象变量
静态变量属于全局变量,不会被GC回收,会一直占用内存
7.分散对象创建或删除的时间
集中在短时间内创建新对象,特别是大对象,会导致突然需要大量内存,JVM在这种情况,只能进行Full GC,以回收内存或整合内存碎片,从而增加主GC频率
从而增加主GC的频率。集中删除对象,道理也是一样的。它使得突然出现了大量的垃圾对象,空闲空间必然减少,从而大大增加了下一次创建新对象时强制主GC的机会
5.可能导致内存泄露的情况
1.静态集合类eg:HashMap、Vector等使用,容易出现内存泄露,这些静态变量的声明周期和程序一致,所有的对象Object也不能被释放,他们将一直被集合所引用
2.各类连接:数据库连接,网络连接,IO连接,等没有显示调用close()关闭,不被GC回收导致内存泄露
3. 监听器的使用,在释放对象的同时,没有相应删除监听器的时候,也可能导致内存泄露
83.Jenkins相关:
http://10.10.30.116:8888/jenkins/ 登录名,密码:admin admin
说明:
忘记Jenkins的admin密码,解决:http://blog.csdn.net/qq105319914/article/details/52094463
1.配置文件路径:/root/.jenkins/
2.初始密码:/root/.jenkins/secrets/initialAdminPassword
3.安装参考:http://www.cnblogs.com/h--d/p/5673085.html
http://m.blog.csdn.net/article/details?id=50518959
http://www.cnblogs.com/zz0412/p/jenkins02.html
http://blog.csdn.net/galen2016/article/details/53418708
Jenkins配置和使用: http://www.cnblogs.com/h--d/p/5682030.html
http://blog.csdn.net/tengdazhang770960436/article/details/53842604
4.Jenkins无法下载插件的解决方法:http://blog.csdn.net/russ44/article/details/52266953
常见的无法安装插件下载地址:
http://updates.jenkins-ci.org/download/plugins/
步骤:
从http://10.10.30.116:8888/jenkins/updateCenter/ 中查看为安装成功的插件,进行手动安装即可
安装顺序:
1.credentials --> plain-credentials --> credentials-binding
2.ssh-credentials --> ssh-slaves
3.git-client --> git-server --> git
4.pam-auth
5.build-pipeline-plugin --> workflow-cps -->workflow-multibranch --> pipeline-multibranch-defaults -->
pipeline-graph-analysis --> pipeline-rest-api
6.subversion
7.github --> github-oauth --> groovy --> github-branch-source -->github-api
5.常用插件的手动安装: 系统管理 --> Global Tool Configuration
1.JDK配置: 新增JDK --> 取消自动安装
别名: JDK1.8
JAVA_HOME:/home/hetiewei/software/java/jdk1.8.0_40
2.Maven配置:新增Maven --> 取消自动安装
别名: maven-3.3.9
MAVEN_HOME:/home/hetiewei/software/maven/apache-maven-3.3.9
2.Jenkins Blue Ocean使用
1.安装
1.Jenkins版本在2.7.x以上
2.登录Jenkins(admin,密码:admin或111111),系统管理——>管理插件-->可选插件 -->搜索 Blue Ocean
3.点击安装后重启,或直接安装,安装完毕后重启Jenkins服务器
2.使用 参考:https://testerhome.com/topics/6700
1.点击Open Blue Ocean进入Ocean UI界面
2.点击 New Pipeline新建项目 或使用旧版界面创建项目
3.推荐使用旧版界面创建项目:
1.点击新建 --> name: test1 ,选择Pipeline OK保存
2.创建Pipeline Script脚本 即: Jenkinsfile 最重要!!!
node {
stage('Clone Code') { // for display purposes
// Get some code from a GitHub repository
git 'https://github.com/trautonen/coveralls-maven-plugin.git/'
}
stage('Code Analysis') {
sh "mvn clean"
sh "infer -- mvn compile"
}
stage('Testing') {
sh "mvn test"
junit 'target/surefire-reports/TEST-*.xml'
}
stage('Package') {
sh "'mvn' -Dmaven.test.skip=true package"
archive 'target/*.jar'
}
stage('Deploy') {
echo 'pipeline success'
}
}
3.常见异常
Cannot run program "nohup" (in directory "C
Windows环境,需要把Jenkinsfile文件中的sh改为bat
4.新建时 没有构建Maven项目项,安装Maven项目插件:Maven Integration plugin
5.源码没有Git选项,安装Git插件: Git Plugin
6.
3.常规的Jenkins项目自动化构建 --- 基于Git
0.先进行全局变量的配置
常用插件的手动安装: 系统管理 --> Global Tool Configuration
1.JDK配置: 新增JDK --> 取消自动安装
别名: JDK1.8
JAVA_HOME:/home/hetiewei/software/java/jdk1.8.0_40
2.Maven配置:新增Maven --> 取消自动安装
别名: maven-3.3.9
MAVEN_HOME:/home/hetiewei/software/maven/apache-maven-3.3.9
1.构建自由风格的软件项目
1.新建
2.输入项目名称 jay-plat,构建一个自由风格的软件项目,点击OK
3.添加项目描述
4.选择源码管理: Git
输入 Repository URL :https://github.com/jayfeihe/jay-plat.git
5.构建-->增加构建步骤-->Execute Windows batch commnad :命令进入要构建项目
cd plat-config-server
mvn clean install
6.构建后操作-->增加构建后操作-->shell脚本,将构建后生产的jar或war放到指定服务器并启动
7.点击 Apply --> 点击保存
8.立即构建
2.构建Maven+Git项目
参考:http://blog.csdn.net/pucao_cug/article/details/52373655
https://m.aliyun.com/yunqi/articles/64970
http://jdkleo.iteye.com/blog/2159844
1.新建
2.输入项目名称 server2 ---> 选择 构建一个maven项目 -->点击OK
3.General ---> 添加项目描述
4.源码管理 --> Git
Repositories
Repository URL:
https://github.com/jayfeihe/jay-plat.git
5.构建环境
1.Pre Steps 可以添加构建之前执行的命令
2.Build
Root POM
: plat-config-server\pom.xml 这里选择以plat-config-server下的pom.xml构建项目 !!!
6.构建后操作-->增加构建后操作-->shell脚本,将构建后生产的jar或war放到指定服务器并启动
7.点击 Apply --> 点击保存
8.立即构建
特别注意!!!
构建环境中,Build 如果clone下的Git项目中包含多个Pom子项目,
可通过ROOT POM 来指定具体构建哪一个Maven项目 !!!
3.构建Pipeline项目 , 需要写基于Groovy的Jenkinsfile脚本
Jenkins Pipeline 参考:
http://www.07net01.com/2016/12/1731789.html
http://www.cnblogs.com/wzy5223/p/5554935.html
84.Redis实现分布式锁
*
* 基于Redis分布式锁实现的秒杀:http://blog.csdn.net/u010359884/article/details/50310387
*
* 在集群等多服务器中经常使用到同步处理一下业务,这是普通的事务是满足不了业务需求,需要分布式锁
*
* 分布式锁的常用几种实现:
Redission实现的基于redis的分布式锁
* 0.数据库乐观锁实现
* 1.Redis实现 --- 使用redis的setnx()、get()、getset()方法,用于分布式锁,解决死锁问题
* 2.Zookeeper实现
* 参考:http://surlymo.iteye.com/blog/2082684
* http://www.jb51.net/article/103617.htm
* http://www.hollischuang.com/archives/1716?utm_source=tuicool&utm_medium=referral
* 1、实现原理:
基于zookeeper瞬时有序节点实现的分布式锁,其主要逻辑如下(该图来自于IBM网站)。大致思想即为:每个客户端对某个功能加锁时,在zookeeper上的与该功能对应的指定节点的目录下,生成一个唯一的瞬时有序节点。判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。
2、优点
锁安全性高,zk可持久化
3、缺点
性能开销比较高。因为其需要动态产生、销毁瞬时节点来实现锁功能。
4、实现
可以直接采用zookeeper第三方库curator即可方便地实现分布式锁
*
* Redis实现分布式锁的原理:
* 1.通过setnx(lock_timeout)实现,如果设置了锁返回1, 已经有值没有设置成功返回0
* 2.死锁问题:通过实践来判断是否过期,如果已经过期,获取到过期时间get(lockKey),然后getset(lock_timeout)判断是否和get相同,
* 相同则证明已经加锁成功,因为可能导致多线程同时执行getset(lock_timeout)方法,这可能导致多线程都只需getset后,对于判断加锁成功的线程,
* 再加expire(lockKey, LOCK_TIMEOUT, TimeUnit.MILLISECONDS)过期时间,防止多个线程同时叠加时间,导致锁时效时间翻倍
* 3.针对集群服务器时间不一致问题,可以调用redis的time()获取当前时间
85.深入理解JVM
1.对象相关
1.对象的创建:
JVM遇到new指令时,首先去检查这个指令的参数能否在常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已被加载、解析和初始化过,
如果没有,则必须先执行类的加载过程
类加载检查通过后,虚拟机将为新生对象分配内存,对象所需内存的大小在类加载完成便可完全确定,为对象分配空间的任务,等同于把一块确定大小的内存,从Java堆中划分出来,把地址赋给引用对象
如果JVM堆是绝对规整的,所有用过的内存都放一边,没用过的内存放在另一边,中间放着一个指针作为分界点的指示器,
内存分配就仅仅是把指针向空闲空间那边移动与对象大小相等的距离 ---- 指针碰撞法
如果JVM堆式不规整的,使用的内存和未使用内存相互交错,则JVM必须维护一个列表,记录那些内存块可用,
在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录 ----- 空闲列表法
JVM堆的规整与否,决定了何种分配方式,JVM堆是否规整取决于GC的是否带有压缩整理功能!!!
Serial、ParNew收集器,使用复制算法 --- 使用 指针碰撞法 分配对象内存
CMS收集器,使用标记清除算法 --- 使用 空闲列表法 分配对象内存
对象创建存在的问题及解决:
1.线程安全问题,eg:修改一个指针所指向位置, 并发时,可能出现正在给对象A分配内存,指针还没来得及修改,对象B又同时使用了原来的指针来分配内存的情况
解决:
方案1。对分配内存空间的动作进行同步处理 --- JVM采用CAS失败重试的乐观锁机制保证操作的原子性
方案2。把内存分配的动作按照线程划分在不同的空间之中进行,
即:每个线程在Java堆中预选分配一小块内存,称为本地线程分配缓存(TLAB),那个线程要分配内存,就在哪个线程的TLAB上分配,
只有TLAB用完并分配新的TLAB时,才需要同步锁定。
JVM使用使用TLAB,通过 -XX:+-UseTLAB参数设定
对象内存分配完成后,JVM需要将分配到的内存空间都初始化为零值
然后JVM对对象进行必要的设置(eg:对象所属类实例,类的元信息,对象的哈希码,对象的GC分代年龄等信息,这些信息存在对象头中)
此时,对象init还没执行
最后执行new指令,按照程序进行初始化,一个对象才是创建成功!
2.对象内存布局
1.对象头
1.存储对象自身运行时数据,eg:哈希码、GC分代年龄、锁状态标识、线程持有的锁、偏向线程ID、偏向时间戳等
2.类型指针:即对象指向它的类元数据的指针, JVM依赖这个指针来确定对象是哪个类的实例
2.实例数据
1.程序中定义的各类字段属性
2.从父类继承的属性
3.对齐填充
HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,
当时实例数据部分没有对齐,就需要通过对齐填充来补全
3.对象的访问定位
引用(栈) ----> 对象(堆),
对象访问方式的2种实现:
1.句柄访问
堆中划分一块内存作为句柄池,reference引用中存储的是对象的句柄地址,
句柄中包含了对象实例数据和类型数据各自的具体地址
以 句柄 为中介,通过对象的句柄地址,查找对象的真实地址,从而找到该对象 !!!
---> 堆 实例池(对象实例数据)
栈引用 ---> 句柄
---> 方法区 对象类型数据
优点:
引用中存储的是稳定的句柄地址,对象被移动(GC回收时,会移动对象到新内存地址)时,只会改变句柄中的对象指针,reference引用本身不用修改
2.直接访问 --- HotSpot的默认对象访问方式
栈中存放引用,引用中存储的是对象的真实地址
优点:
速度更快,节省了一次指针定位的时间开销,因为对象的访问在Java中非常频繁,减小一次指针寻址开销,可以提供性能
4.对象分配引起的内存问题
OutOfMemoryError
1.Java堆溢出
-Xmx -Xms 设置JVM的最大和最小堆内存
循环创建对象,而保持引用不回收,会导致堆内存溢出, Java Heap Space
2.栈溢出
-Xss 设置JVM的栈大小
1.线程请求的栈深度大于虚拟机所允许的最大深度 --- StackOverflowError --- 一般由递归或方法调用层次太深导致!!!
2.JVM在扩展栈时,无法申请到足够的内存空间 --- OutOfMemoryError
3.方法区和运行时常量池溢出
-XX:PermSize 和 -XX:MaxPermSize 设置方法区大小
方法区存储 Class相关信息(类名、访问修饰符、常量池、字段描述、方法描述等),当方法区申请内存无法被满足时 --- OutOfMemoryError PermGen Space
测试:
String.intern()方法:如果字符串常量池中已经包含一个等于String对象的字符串,则返回池中字符串,
否则,将此String对象包含的字符串添加到常量池中,并返回此String对象的引用
eg:
降低方法区大小,然后循环向常量池添加字符串,可能导致方法区内存溢出
2.GC相关
1.如何确定回收对象
1.引用计数法
给对象添加一个引用计数器,每当有一个地方引用它,计数+1, 引用失效,计数-1,任何时刻计数=0,则表示对象不再被使用,可回收
缺点:
无法解决循环依赖问题
2.可达性算法
通过一系列的 GC Roots 对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为"引用链",
当对象到GC Roots没有任何引用链相连时,证明此对象是不可用的
可做GC Roots的对象:
JVM栈中引用的对象
方法区中静态属性引用的对象
方法区中常量引用的对象
本地方法栈中引用的对象
3.关于对象引用:
强引用: 类似 Object obj = new Object()的对象,只要强引用还存在,GC用于不会回收掉被引用的对象
软引用: JDK1.2+提供了SoftReference类来实现软引用,软引用关联着对象,在系统发生内存溢出异常之前,会把这些对象进行二次回收,如果回收软引用后,还没有足够空间,抛出内存溢出异常
弱引用: JDK1.2+提供了WeakReference类实现弱引用,被弱引用关联的对象,只能生存到下一次垃圾回收发生之前,
GC工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象
虚引用:
2.垃圾收集算法
1.标记-清除
阶段一:标记 --- 首先标记出所有需要回收的对象
阶段二:清除 --- 标记完成后,统一回收所有被标记的对象
问题:
1.标记和清除效率都不高
2.标记清除后会产生大量不连续的内存碎片,空间碎片过多可能导致程序在分配较大对象时,无法找到足够内存,而提前引发一次GC收集
2.复制算法 --- 适合新生代
为了解决效率问题,复制算法将内存按容量划分为大小相等的2份,每次只使使用其中的一块,
当这一块的内存用完,就将还存活的对象复制到另一块上面,然后再把已使用过的内存空间一次清理掉
每次都对整个半区进行内存回收,内存分配时不用考虑内存碎片问题,只要移动堆顶指针,按顺序分配内存即可
优点:
实现简单,运行高效
问题:
将内存缩小了一般
适合: 新生代的GC对象回收
新生代对象,绝大多数生命周期比较短,将新生代分为 较大的Eden和2个等大较小的from和to区,,
每次使用Eden和其中一个Survivor,当回收时,将Eden和Survivor中还存活的对象一次性复制到另外一个Survivor空间上,
最后清理掉Eden和刚才使用过的Survivor空间
HotSpot默认 Eden和Survivor比例 8:1:1,即:每次新生代中可用内存空间为新生代容量的 90%(eden+一个Survivor),只有10%的浪费
3.标记-整理 --- 适合老年代
老年代特点:生命周期长,被回收的少,不适合复制算法
标记过程与算法一相同,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都想一端移动,然后直接清理掉端边界以外的内存
即:
把未被标记的对象,移动到一端,指针指到临界端,清除临界端指针以外的内存空间,避免了内存碎片,同空间浪费,但增加了存活对象的移动开销
4.分代收集算法
新生代 --- 复制算法 --- 每次GC都有大批对象死去,只有少量存活
老年代 --- 标记整理 --- 对象存活率高,没有额外空间担保
3.垃圾收集器 --- 内存回收的具体实现
1.Serial 收集器 --- Client模式下,简单高效 --- 不能用于服务器环境,GC会停止一切服务!!!
单线程收集器,新生代,GC时,必须暂停其他所有工作线程,直到收集结束
2.ParNew收集器 --- 适合新生代GC
Serial的多线程版本,配合 CMS (老年代收集器) 使用
3.Parallel收集器 --- 新生代收集器,使用复制算法的并行多线程收集器
Parallel目标:达到一个可控制的吞吐量
CMS目标:关注点是尽量缩短垃圾收集时,用户线程的停顿时间
4.Serial Old --- Serial收集器的老年代版本,单线程,使用标记-整理算法
5.Parallel Old --- Parallel收集器的老年代版本,多线程,使用标记-整理算法, 只能配合新生代是Parallel使用
6.CMS --- 适合老年代,以获取最短回收停顿时间为目标的收集器 --- 最适合 服务器端使用!!!
--- 基于"标记清除算法"实现
GC时4个步骤:
1.初始标记
2.并发标记
3.重新标记
4.并发清除
解析:
初始标记和重新标记,需要 "停止一切",
初始标记:标记一下 GC Roots能直接关联到的对象,速度很快
并发标记:进行 GC Roots 跟踪的过程
重新标记:为了修正并发标记期间,因用户程序继续运行而导致标记产生变动的那部分对象,这个节点停顿时间一般会比初始标记节点稍长,但远比并发标记时间短
整个过程,并发标记和并发清除耗时最长, 但这两个过程都是多线程的,可以与用户线程一起工作,
总体上看,CMS收集器的内存回收过程和用户一起并发执行
优点:
并发收集,低停顿,适合老年代!!!
缺点:
标记-清除算法 会产生内存碎片,需要特殊的参数定义:对内存碎片进行合并,以避免大对象直接分配到老年代
GC执行时,会和用户线程抢CPU,对CPU资源非常敏感
7.G1 --- JDK1.7+提供,面向服务端应用的垃圾收集器!!!
特点:
并发并行,G1能充分利用多CPU,多核环境下的硬件优势,使用多个CPU来缩短 "停止一切" 的停顿时间
空间整合,整体上看是基于 标记-整理 算法实现的, 但局部上看是基于 复制算法实现的,
不会产生内存碎片,收集后能提供规整的可用内存
注意:
1.Parallel用于新生代 ,老年代必须用Parallel Old
2.ParNew(新生代)+CMS(老年代),吞吐量和性能 > Parallel + Parallel Old
4.内存分配与回收策略
1.对象优先在Eden分配,当Eden区没有足够空间进行分配时,触发一次Minor GC
2.大对象直接进入老年代,可通过参数设置大对象的大小 eg:很长的字符串或数组
3.长期存活的对象进入老年代,每次GC,存活的对象年龄+1,当对象年龄达到阈值,还没被回收,就进入老年代
4.动态对象年龄判定
如果在Survivor空间中,相同年龄所有对象大小总和大于 Survivor空间的一半,年龄大于或等于概念了的对象可以直接进入老年代,而不必达到年龄设置的阈值
5.空间分配担保
Minor GC(新生代GC)能回收的内存,尽量不要用Full GC(老年代GC)来做, 性能问题!!!
在发生Minor GC之前, JVM会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,
如果条件成立,则Minor GC可以确保是安全的,
如果不成立,JVM会查看HandlePromotionFailure设置值是否允许担保失败,
如果允许,JVM继续检查老年代最大可用的连续空间十分大于历次晋升到老年代对象的平均大小,
如果大于,进行一次Minor GC
如果小于,进行一次Full GC
如果HandlePromotionFailure设置值不允许担保失败,则进行一次Full GC
3.JVM类加载机制
1.JVM类加载机制:
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可被虚拟机直接使用的Java类型的过程,叫JVM的类加载机制
Java中,类型的加载、连接、初始化都是在程序运行期间完成的,会稍微增加一些性能开销,但提高了高度的灵活性,
Java动态扩展特性,就是依赖运行期动态加载和动态连接特定实现的
eg:
面向接口的程序,可以等到运行时再指定其实际实现类!!!
用户可以通过Java预定义和自定义类加载器,让一个本地程序可以运行从网络或其他地方加载的二进制流作为程序的一部分!!!
1.类加载时机
类的的生命周期
加载 ---> 连接(验证 --> 准备 --> 解析) ---> 初始化 ---> 使用 ---> 卸载
5种情况立刻对类进行初始化(加载、连接在初始化之前执行)
1.遇到new、getstatic、putstatic、invokestatic时,如果类没被初始化,则先触发初始化
即:new关键字实例化对象、读取或设置一个类的静态字段(final变量,已经在编译期把结果放入常量池),调用一个类的静态方法
2.使用java.lang.reflect包的方法对类进行反射调用时,如果类没被初始化,则先触发初始化
3.初始化一个类时,其父类未初始化,先初始化其父类
4.JVM启动时,用户需要指定一个执行的主类(包含main()方法的类),虚拟机会先初始化这个类
5.使用JDK1.7+动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果,涉及到类的静态变量或静态方法时,先对涉及的类进行初始化
解析:
以上5种场景,称为对一个类进行主动引用!!!
除此之外,所有引用类的方式都不会触发初始化,称为被动引用!!!
被动引用示例:
1.对于静态字段,只有直接定义这个字段的类才会被初始化,!!!
通过其子类来引用父类中定义的静态字段,只会触发父类的初始化,而不会触发子类的初始化!!!
2.常量会在编译期存入调用类的常量池中,本质上并没有直接引用到定义常量的类,不会触发定义常量类的初始化!!!
eg1:
/**
*被动使用类字段演示一:
*通过子类引用父类的静态字段,不会导致子类初始化
**/
public class SuperClass{
static{
System.out.println("SuperClass init!");
}
public static int value=123;
}
public class SubClass extends SuperClass{
static{
System.out.println("SubClass init!");
}
} /**
*非主动使用类字段演示
**/
public class NotInitialization{
public static void main(String[]args){
System.out.println(SubClass.value);
}}
结果:
SuperClass init!
123
分析:
对于静态字段,只有直接定义这个字段的类才会被初始化,!!!
通过其子类来引用父类中定义的静态字段,只会触发父类的初始化,而不会触发子类的初始化!!!
eg2:
/**
*常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。
**/
public class ConstClass{
static{
System.out.println("ConstClass init!");
} public static final String HELLOWORLD="hello world";
} /**
*非主动使用类字段演示
**/
public class NotInitialization{
public static void main(String[]args){
System.out.println(ConstClass.HELLOWORLD);
}}
结果:
hello world
解析:
常量在编译期会存入调用类的常量池中,本质上并未引用到定义常量的类,因此ConstClass不会被初始化!!!
2.类加载过程
1.加载:
完成3件事
1.通过类全限定名来获取定义此类的二进制字节流 --- 全限定名获取类的二进制字节流
2.将这个字节流所代表的静态存储结果转化为方法区的运行时数据结构 --- 把字节流,转化为方法区运行时数据结构
3.在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口 --- 生成类的Class对象
2.连接:
1.验证:
确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并不会危害虚拟机自身的安全
1.文件格式验证
2.元数据验证
3.字节码验证
4.符号引用验证
2.准备:
1.为static类变量分配内存(存储在方法区),并设初始零值,赋真实值实在初始化时,而实例变量则在对象创建时随对象一起分配在Java堆中
2.为final变量分配内存(方法区)并赋真实值
3.解析:
把常量池内的符号引用替换为直接引用的过程
符号引用:以一组符号来描述所引用的目标,符号可以是任何形式的字面量,引用的目标并不一定已经加载到内存中
直接引用:可以是直接指向目标的指针,相对偏移量或一定能间接定位到目标的句柄。直接引用的目标一定以及在内存中存在
3.初始化:
类加载过程中,除了在加载阶段用户可以通过自定义类加载器参与外,其他动作都是由JVM主导和控制的,
初始化阶段,才真正开始执行类中定义的Java代码
1.为static类变量赋初始值(准备阶段赋零值,初始化阶段赋代码中定义的真实值)
2.初始化类变量和其他资源,主要是执行类构造器<cinit>()方法的过程
关于<cinit>()方法
1.<cinit>()主要由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的,
按照语句在源码中出现的顺序,静态语句块中只能访问定义在其之前的变量,定义在其后的静态语句块变量,可以赋值,但不能访问
2.<cinit>()方法不需要显示调用父类构造器,JVM会保证在子类的<cinit>方法执行前,父类的<cinit>方法执行完毕,因此JVM中第一个执行的<cinit>()方法是Object
3.父类中定义的静态语句(块)先于子类中定义的静态语句(块)执行,
4.代码执行顺序
父静态块 <-- 子静态块 <-- 父普通代码块 <-- 父构造器 <-- 子普通代码块 <-- 子构造器
1.父类静态成员和静态初始化快,按在代码中出现的顺序依次执行。
2.子类静态成员和静态初始化块,按在代码中出现的顺序依次执行。
3. 父类的实例成员和实例初始化块,按在代码中出现的顺序依次执行。
4.执行父类的构造方法。
5.子类实例成员和实例初始化块,按在代码中出现的顺序依次执行。
6.执行子类的构造方法。
5.类加载器
用于实现类的加载,对任意一个类,都需要由它的类加载器和这个类本身,一同确立其在JVM中的唯一性。
即:
比较两个类是否相等,只有在这两个类是由同一个类加载器加载的前提下才有意义!!!
即使2个类来源于同一个Class文件,被同一个JVM加载,只要加载它们的类加载器不同,这两个类就必定不相等!!!
这里的相等,包括代表类Class对象的:
equals()方法、isAssignableFrom()方法、isInstance()方法的返回结果 和 instanceof 做对象所属关系的判定
1.双亲委派机制
启动类加载器Bootstrap ClassLoader(C++实现),是JVM自身的一部分
其他类加载器,由Java实现,独立于虚拟机外部,继承自java.lang.ClassLoader
双亲委派机制工作过程:
如果一个类加载器收到了类加载请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,
每一个层次的类加载器都是如此,因此所有的类加载请求最终都应该传送到顶层的启动类加载器中,
只有当父加载器反馈自己无法完成这个加载请求时(它的搜索范围中没有找到所需的类),子加载器才会尝试自己去加载
启动类加载器:加载 <JAVA_HOME>\lib\rt.jar里所有的类, C++实现,不是ClassLoader子类
扩展类加载器:加载 <JAVA_HOME>\lib\ext目录中的类
应用程序类加载器:加载 类路径ClassPath上指定的类库和目录中的class --- 如果没有自定义加载器,这个是应用程序默认的加载器!!!
类加载器层次关系:
启动类加载器
Bootstrap ClassLoader
|
|
扩展类加载器
Extension ClassLoader
|
|
应用程序类加载器
Application ClassLoader
|
|
|
|
自定义类加载器1 自定义类加载器2
4.Java内存模型与线程
1.Java内存模型主要目标是定义程序中各个变量的访问规则,
即:在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。
注意:
这里的变量包括实例字段、静态字段和构成数组对象的元素,
不包括局部变量和方法参数(它们是线程私有的,不会被共享,不存在竞争问题)
Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,
线程的工作内存中保存了该线程使用变量的主内存副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读取主内存中的变量
不同的线程之间也无法直接访问对方工作内存中变量,线程间变量值的传递,需要通过主内存来完成。
一般:
主内存指的是堆内存,
工作内存优先存储于寄存器或高速缓存总,因为程序运行时主要访问读写的是工作内存
2.内存间交互操作
主内存与工作内存交互协议:即:一个变量如何从主内存拷贝到工作内存、如何从工作内存同步回主内存指令的实现细节
Java内存模型定义了8种操作:
lock(锁定): 作用于主内存的变量,把一个变量标识为一条线程独占的状态
unlock(解锁): 作用于主内存的变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
read(读取): 作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存,以便随后的load动作使用
load(载入): 作用于工作内存的变,它把read操作从主内存中得到的变量值放入工作内存的变量副本中
use(使用): 作用于工作内存的变量,它把工作变量内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时,执行这个操作
assign(赋值): 作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存变量,每当虚拟机遇到一个给变量赋值的字节码指令是,执行此操作
store(存储): 作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用
write(写入): 作用于主内存的变量,它把store操作从工作内存中得到的变量值,放入主内存变量中
解析:
如果要把一个变量从主内存复制到工作内存,就要顺序执行read和load操作,
如果要把一个变量从工作内存复制到主内存,就要顺序执行store和write操作,
特别注意:
Java内存模型只要求,以上2对操作必须按顺序执行,而没有保证是连续执行,
即:read和load,store和write直接是可插入其他指令的,
eg:
对主内存中变量a、b进行访问时,一种可能出现顺序是 read a、read b、load b、load a
注意:
Java内存模型规定了在执行以上8种操作时,必须满足如下规则:
1.不允许read和load、store和write操作之一单独出现,即:不允许一个变量从主内存读去了但工作内存不接受,或从工作内存发起回写而主内存不接受的情况出现
2.不允许一个线程丢弃它最近的assign操作,即:变量在工作内存中改变之后,必须把变化同步会主内存
3.不允许一个线程无原因的(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存
4.一个变量只能在主内存中 "诞生",不允许在工作内存中直接使用一个未被初始化(read和load)的变量,即:对一个变量实施use、store操作之前,必须先执行过了assign和load操作
5.一个变量在同一时刻只允许一条线程对其进行Lock操作,但lock操作可以被同一条线程重复执行,多次执行后,只有执行相同次数的unlock操作,变量才会被解锁
6.如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assgin操作,初始化变量的值
7.变量没被lock,不允许对其执行unlock操作
8.对一个变量执行unlock之前,必须先把此变量同步会主内存中(执行store、write操作)
3.对volatile变量的特殊规则
1.保证可见性
对所有线程可见,一条线程修改了此变量,新值对其他线程是立即可知的,
强制所有线程,操作该变量时,重新重主内存中获取,操作完之后,立即同步到主内存
2.禁止指令重排序
3.不保证原子性!!!
valatile用途:
1.用volatile控制并发
2.结合CAS实现的原子性,实现乐观锁
4.原子性、可见性、有序性
1.原子性保证
1.synchronized 原理:通过字节码指令 monitorenter和monitorexit来保证线程安全
2.Lock 对锁定部分的代码,执行同步操作,同一时刻只允许一个线程操作锁定的代码
3.原子类AutoInteger
2.可见性 volatile
3.有序性 volatile、synchronized、先行发生原则
5.先行发生原则
判断数据是否存在竞争、线程是否安全的主要依据,依靠这个原则,可以通过几条规则,一揽子地解决并发环境下两个操作之间是否可能存在冲突的所有问题!
定义:
先行发生是Java内存模型中定义的两项操作之间的偏序关系,如果说操作A先行发生于操作B,
即:发生操作B之前,操作A产生的影响能被操作B观察到 -- "影响" 包括 修改了内存中共享变量的值、发送了消息、调了方法等
5.线程安全
1.实现方法
1.synchronized互斥同步
synchronized关键字经过编译后,会在同步块的前后分表形成monitorentrer和monitorexit两个字节码指令,调用操作系统的mutex互斥锁实现!!!
根据JVM虚拟机要求,在执行monitorenter指令是,首先要尝试获取对象的锁,如果这个对象没被锁定,或者当前线程已经拥有了那个对象的锁,把锁的计数器+1;
相应的在执行monitorexit指令时,将锁计数器 -1,当计数器为0时,锁被释放,如果获取对象锁失败,则当前线程就要阻塞等待,知道对象锁被另外一个线程释放为止
synchronzied是非公平锁
2.Lock --- ReentrantLock可重入锁
ReentrantLock增加了高级特性:
1.等待可中断
当持有锁的线程长期不释放锁时,正在等待的线程可以选择放弃等待,该做其他事情,可中断特性对处理执行时间非常长的同步块很有帮助
2.可实现公平锁
公平锁;多个线程在等待同一个锁时,必须按照申请锁的时间顺序依次获得锁,
ReentrantLock默认也是非公平的,但可通过构造器设置其为公平锁
3.锁可以绑定多个Condition条件
一个ReentrantLock可以绑定多个Condition条件
而synchronized中,锁对象的wait()、notify()、notifyAll()方法可以实现一个隐含条件
3.非阻塞同步
阻塞式同步,悲观锁 --- 互斥同步,主要问题是进行线程阻塞和唤醒所带来的性能问题
总是认为会出问题,无论共享数据是否真的会出现竞争,都要加锁
非阻塞同步,乐观锁 --- 基于冲突检测的乐观并发策略
通常:先执行操作,如果没有其他线程争用共享数据,就操作成功,
如果共享数据有争用,产生了冲突,就采取补偿机制(不断重试,直到成功为止)。
乐观并发策略的许多实现并不需要把线程挂起 --- 非阻塞同步
底层实现:
CAS ==> 依赖硬件指令,来保证原子性:
4.锁优化
自旋锁 JDK6后默认开启
互斥同步时,对性能最大的影响是阻塞的实现,挂起线程和恢复线程的操作都需要转入内核态完成,这些操作给系统的并发性能带来了很大压力。
许多应用,共享数据的锁状态只会持续很短时间,为了这段时间去挂起和恢复线程并不值得。
如果物理机有一个以上的处理器,能让2个或以上的线程同时执行,就可以让后面请求锁的那个线程"稍等一下",但不放器处理器的执行时间,
看看持有锁的线程是否很快就会释放,为了让线程等待,只需要让线程执行一个忙循环(自旋) --- 自旋锁
自旋等待本身虽然避免了线程切换的开销,但要占用处理时间,锁占用时间短时,自旋等待效果好,如果所占用时间长,自旋的线程只会白白消耗CPU资源
轻量级锁
本意是在没有多线程竞争前提下,减少传统的重量级锁适用操作系统互斥而产生的性能消耗,
偏向锁
86.MySQL事务隔离级别和Spring的事务传播机制?(同程面试)
事务的ACID特性:
原子性(要么都做,要么都不做)、
一致性(事务执行结果,必须是使数据库从一个一致性状态变到另一个一致性状态)、
隔离性(事务之间不能干扰)、
永久性(事务一旦提交,数据的改变是永久的)
MySQL的4种隔离级别
--- 用来限定事务内外的哪些改变是可见的,哪些是不可见的。
--- 低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销
Read Uncommited --- 读取未提交内容
所有事务都可以看到其他未提交事务的执行结果。
本隔离级别很少用于实际应用,
读取未提交的数据 --- 脏读
Read Commited --- 读取提交内容
大多数数据的默认隔离级别, 但非MySQL默认的!!!、
一个事务只能看见已经提交事务所做的改变,
这种隔离级别,支持不可重复读,因为同一事务的其他实例在该实例处理期间可能会有新的commit,所有同一select可能返回不同结果
Repeated Read --- 可重读
MySQL的默认事务隔离级别!!!
它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。
幻读问题:幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的 "幻影" 行。
InnoDB通过多版本并发控制机制解决了该问题!!!
Serizliable --- 可串行化
最高的隔离级别,它通过强事务排序,使之不可能相互冲突,从而解决幻读问题,
即:它在每个读的数据行上加上共享锁!!!
该隔离级别,可能导致大量的超时现象和锁竞争!!!
4种隔离级别是通过采取不同的锁类型来实现的,若读完的是同一个数据,就容易发生以下问题:
脏读:
某个事物已更新一份数据,另一个事务在此时读取了同一份数据,但由于某些原因,前一个事务回滚了,则后一个事务所读取的数据是脏数据!!!
不可重复读:
在一个事务的两次查询之中数据不一致,可能是两次查询过程中,插入了一个事务更新操作
幻读:
在一个事务的两次查询汇总数据笔数不一致,
eg:一个事务查询了几列数据,而另一个事务却在此时插入了新的几列数据,
先去的事务在接下来的查询中,就会发现有借了数据是它之前所没有的
4种隔离级别与可能产生的问题如下:
隔离解别
脏读
不可重复读
幻读
Read Uncommitted
Y
Y
Y
Read Committed
N
Y
Y
Repeatable(default)
N
N
Y
Serializable
N
N
N
Spring事务传播机制
事务传播行为类型
说明
PROPAGATION_REQUIRED
如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是 最常见的选择。
PROPAGATION_SUPPORTS
支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY
使用当前的事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW
新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED
以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER
以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED
如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与 PROPAGATION_REQUIRED 类似的操作。
87.Spring中的bean,在初始化之后或销毁之前执行指定功能,几种方式? (同程面试)
3种方式:
1.通过注解 @PostConstruct 和 @PreDestory 方法,实现初始化和销毁bean之前的操作
eg:
public class DataInitializer{
@PostConstruct
public void initMethod() throws Exception {
System.out.println("initMethod 被执行");
}
@PreDestroy
public void destroyMethod() throws Exception {
System.out.println("destroyMethod 被执行");
}
}
2.在xml中定义init-method和destory-method方法
1.Bean中定义方法
public class DataInitializer{
public void initMethod() throws Exception {
System.out.println("initMethod 被执行");
}
public void destroyMethod() throws Exception {
System.out.println("destroyMethod 被执行");
}
}
2.xml中对bean配置初始化init-method和销毁前destory-method执行的方法
<bean id="dataInitializer" class="com.jay.demo.DataInitializer"
init-method="initMethod" destory-method="destroyMethod"/>
3.通过Bean实现InitializingBean和DisposableBean接口
public class DataInitializer implements InitializerBean, DisposableBean{
@Override
public void afterPropertiesSet() throws Exception{
System.out.println("Bean初始化后,afterPropertiesSet被执行");
}
@Override
public void destory() throws Exception{
System.out.println("Bean被销毁之前, destory被执行");
}
}
分析:
1.方式1和方式2,本质是一个,只是一个注解方式实现,一个xml配置方式实现
2.Bean实例化执行的顺序:
初始化:
Constructor ---> @PostConstruct ---> InitializingBean(afterPropertiesSet方法) ---> init-method(xml配置)
销毁:
@PreDestory ---> DisposableBean(destory方法) ---> destory-method(xml配置)
执行顺序源码分析:
3.实现InitializingBean接口是直接调用afterPropertiesSet方法,比通过反射调用init-method指定方法效率相对来说要高点,但 init-method方式,消除了对Spring的依赖!
4.如果调用afterPropertiesSet()方法时出错,则不调用init-method指定的方法
5.Spring容器中Bean实例完整的生命周期:
开始 -->创建实例
--> 注入依赖关系
--> 调用afterPropertiesSet()方法
--> 调用init-method方法
--> 对外提供服务
--> 调用destory()方法
--> 调用destory-method指定的方法
--> 结束
注意:
当Bean实现ApplicationAware、BeanNameAware接口后,Spring容器会在该Bean初始化完成后,
即:init-method指定方法(如果有)执行之后,再来回调setApplicationContext(ApplicationContext context)和setBeanName(String beanName)方法
88.在项目启动前执行预处理方法的几种方式?
1.Web项目Servlet启动、执行、销毁的全过程
1.读取配置信息
启动Web项目时,容器(Tomcat)会读取配置文件(web.xml)中的<listener/>和<context-param/>标签
2.创建监听类
由容器创建<listener/>监听类实例,用于监听ServletContext、HttpSession的声明周期及书序变更
3.创建上下文
由容器创建ServletContext上下文实例,这时监听类实例会调用其contextInitialized(ServletContextEvent args)方法,并传入读取的<context-parm/>键值对,
在该方法中可以读取、操作ServletContext键值对
eg:
ServletContext = ServletContextEvent.getServletContext();
Value = ServletContext.getInitParameter(Key);
4.容器调用继承HttpServlet接口的类的构造方法,创建Servlet
5.创建ServletConfig
容器创建ServletConfig对象(包含Servlet初始化信息),并将ServletConfig对象与ServletContext对象关联
6.初始化Servlet
容器调用Servlet对象的初始化init(ServletConfig config)方法,并传入ServletConfig参数初始化Servlet
7.接收请求
当容器接收到Servlet请求时,容器创建ServletRequest和ServletResponse对象,然后调用service()方法,并传入参数,进行处理
8.响应请求
Service()方法通过ServletRequest对象获得请求信息,并处理该请求,再通过ServletResponse对象生成响应结果
9.销毁Servlet
当Web应用被终止时,Servlet容器会先调用Web应用中所有Servlet对象的destory()方法,然后再销毁Servlet对象。
此外容器还会销毁与Servlet对象关联的ServletConfig对象。在destroy()方法的实现中,可以释放servlet所占用的资源。如关闭文件输入输出流,关闭与数据库的连接
1.在项目启动时,执行某个方法的5种方式
1.实现Servlet监听器接口
--- ServletContextListener
eg:
1.定义监听器
public class InitListener implements ServletContextListener {
@Override
public void contextDestroyed(ServletContextEvent context) {
}
@Override
public void contextInitialized(ServletContextEvent context) {
// 上下文初始化执行
System.out.println("================>[ServletContextListener]自动加载启动开始.");
SpringUtil.getInstance().setContext( WebApplicationContextUtils.getWebApplicationContext(arg0.getServletContext()));
}
}
2.web.xml中配置该监听器
<listener>
<listener-class>com.test.init.InitListener</listener-class>
</listener>
2.实现Servlet的过滤器接口
--- Filter
1.自定义过滤器,重写 init()方法
2.在web.xml中配置过滤器
3.编写Servlet,在web.xml中配置容器启动后执行即可 --- HttpServlet
1.自定义Servlet,重写 init()方法
2.web.xml中配置Servlet
4.使用Spring IOC管理Bean,可以指定init-method,在bean加载成后,立即执行某个方法
5.使用Spring IOC管理Bean,可以实现Spring Bean后置处理器接口 --- BeanFactoryPostProcessor --- 表示该Bean加载完成后,执行一些自定义事件
public class KeyWordInit implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException {
System.out.println("================>[BeanFactoryPostProcessor]自动加载启动开始.");
ShopService shopService = factory.getBean("shopService", ShopService.class);
List<map<string, object="">> shopList = shopService.findAllShop();
System.out.println("================>" + shopList);
System.out.println("================>[BeanFactoryPostProcessor]自动加载启动结束.");
}
}
5种方式的执行顺序:
4===>5===>1===>2===>3
即:
指定init-method的Bean开始执行。
接着实现spring的Bean后置处理器开始执行
然后是Servlet的监听器执行
再接下来是Servlet的过滤器执行
最后才是Servlet执行
特别注意:
Spring提供的项目启动时执行的2种方法:
1.ApplicationListener<ContextRefreshedEvent> --- Spring容器初始化完成后,执行 onApplicationEvent()方法
可以使用Spring的 ApplicationListener,也可以完成项目启动时,Spring容器初始化完成后,执行方法!!!
参考:
http://blog.csdn.net/ilovejava_2010/article/details/7953419
@Service
public class StartGateServiceData implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// 在web项目中(spring mvc),系统会存在两个容器,一个是root application context
// ,另一个就是我们自己的 projectName-servlet context(作为root application context的子容器)。
// 这种情况下,就会造成onApplicationEvent方法被执行两次。为了避免这个问题,我们可以只在root
// application context初始化完成后调用逻辑代码,其他的容器的初始化完成,则不做任何处理。
if (event.getApplicationContext().getParent() == null) {
//需要执行的逻辑代码,当spring容器初始化完成后就会执行该方法。
}
}
}
2.自定义类,实现InitializingBean接口,然后交由Spring容器管理
eg:
需求:
权限管理系统,在项目启动时,把用户权限和资源信息加载到内存
分析:
给web容器添加一个Listener类,在容器启动的时候执行Listener的“初始化”方法,在这个初始化方法中执行查询数据库的所有操作,然后将数据库中的信息缓存起来
问题:
查询DB的Service和dao都是Spring IOC控制,Listener类只是在系统启动时会执行初始化方法,
但此时Service还没被Spring管理(Spring容器还没创建),即:Service和Dao无法访问数据库
解决:
自定义类,实现InitializingBean接口,然后交由Spring管理
Spring容器启动后,加载该类,自动执行其中的方法
参考:http://hbiao68.iteye.com/blog/2026210
89.线程安全的集合类
1.Vector、HashTable
2.Collections.synchronizedXxx(List、Set、Map)
3.并发包中
CopyOnWriteArrayList --- 其中的set、add、remove等方法,都使用了ReentrantLock来加锁和解锁,
当增加元素时,使用Arrays.copyOf()来拷贝副本,在副本上增加元素,然后改变原引用指向副本
CopyOnWriteArraySet --- 使用了
ConcurrentHashMap --- 允许多个修改操作并发进行,关键在于使用了锁分离技术。
它使用多个锁来控制对hash表的不同部分(Segment)进行修改,
每个段其实就是一个小的hashtable,它们都有自己的锁(由Segment继承ReentrantLock实现),
只要多个修改操作发生在不同的Segment段上,它们就能并发的进行
JDK1.8后,HashMap和ConcurrentHashMap的每个Segment都是通过红黑树实现!!!
各类BlockingQueue(ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue、DelayQueue(基于优先级队列实现)、SynchronousQueue)
ConcurrentLinkedQueue
ConcurrentSkipListMap
实现参考:http://www.2cto.com/kf/201212/175026.html
ConcurrentSkipListMap 继承 AbstractMap 实现ConcurrentMap接口
1.SkipList 跳表
Skip List是一种随机化的数据结构,基于并联的链表,其效率可以比拟二叉查找树(O(log n)),
跳跃列表是对有序的链表增加上附加的前进链接,增加是以随机化方式进行的,所以在列表中的查找可以快速的跳过部分列表,
所有操作都是以对数随机化的时间进行。
Skip List可以很好解决有序链表查找特定值的困难
Skip List(跳表)是一种可以代替平衡树的数据结构,默认按key值升序。
SkipList让已排序的数据分布在多层链表中,以0-1随机数决定一个数据的向上攀升与否,通过 "空间来换取时间"的一个算法,
在每个节点中增加了向前的指针,在插入、删除、查找时可忽略一些不可能设计到的节点,从而提高了效率
2.ConcurrentSkipListMap
提供了一种线程安全的并发访问的排序映射表。
内部是SkipList(跳表)结构实现,理论上能在O(log n)时间内完成查找、插入、删除操作。
调用ConcurrentSkipListMap的size时,由于多线程可以同时对映射表进行操作,所以映射表需要遍历整个链表才能返回元素个数
3.ConcurrentSkipListMap有几个ConcurrentHashMap不可比拟的优点:
1.ConcurrentSkipListMap的key是有序的
2.ConcurrentSkipListMap支持更高的并发,存取时间是 O(log N),和线程数几乎无关,
即:在数据量一定的情况下,并发的线程越多,ConcurrentSkipListMap越能体现出其优势
4.使用建议:
1.非多线程下,如果要保证有序,尽量使用TreeMap, 对于并发性相对较低的程序,可用Collections.synchronizedSortedMap()将TreeMap进行线程安全同步。
2.对于多线程高并发程序,如果需要对Map的key进行排序时,应当使用ConcurrentSkipListMap,能够提供更高的并发度
90.关于几个常用的Map
Map接口的4个实现类
HashMap:继承自Dictionary类,根据hashCode存储数据,访问速度快,无序(遍历时,取得数据的顺序是完全随机的),最多只允许一条记录的key为null,允许多个value为null,线程不安全
HashTable:继承自Dictionary类,不允许记录的key或value为null,线程安全,写入慢
LinkedHashMap:HashMap的子类,保存了记录插入顺序,用Iterator遍历时,按插入Map顺序取值
TreeMap:实现SortMap接口,可根据Key进行自定义排序,通过比较器Comparator或key实现comparable接口来指定顺序
HashMap中hash冲突的解决:
拉链法 --- 系统总是将新添加的 Entry 对象放入 table 数组的 bucketIndex 索引处——如果 bucketIndex 索引处已经有了一个 Entry 对象,那新添加的 Entry 对象指向原有的 Entry 对象(产生一个 Entry 链),如果 bucketIndex 索引处没有 Entry 对象,也就是上面程序代码的 e 变量是 null,也就是新放入的 Entry 对象指向 null,也就是没有产生 Entry 链
hash冲突的其他解决方法:
1.开放地址法 --- 线程探测法
即:当发生地址冲突时,安装某种方法,继续探测哈希表中的其他存储单元,直到找到空位置为止,将该元素放入该槽中
91.JVM常用的分析命令
1.jstat
--- 查看JVM的堆栈信息,能够查看eden、Survivor、old、perm等内存的capacity、utility信息,对系统是否有内存泄露以及参数设置是否合理有不错的意义
eg:
jstat -gc pid
显示gc信息、查看gc次数及时间
jstat -gccapacity pid
显示VM内存中三代(young、old、perm)对象的使用和占用大小
jstat -gcutil pid
统计gc信息
jstat -gcnew pid
年轻代对象信息
jstat -class pid 显示加载class的数量,及所占空间等信息
2.jstack
--- 查看JVM当前的thread dump,可以看到当前JVM中的线程状况,对于查找阻塞的线程有帮助
--- 用于打印给定的Java进程ID或远程调用服务的Java堆栈信息
3.jmap
--- 查看JVM当前的heap dump堆内存信息,可以看出当前JVM中各种对象的数量,所占空间等
项目中遇到的Bug及解决?
1.线程池问题
newCachedThreadPool(),吞吐量大,阻塞队列使用SynchronousBlockQueue吴荣林的阻塞队列,put必须等待take,同样take必须等待put,
适合执行耗时较短的多线程,线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程
如果瞬间进入的线程过多,而且线程耗时长,将会产生上千个线程,导致内存溢出,程序崩溃
2.Future问题,
Future + Callable可以定义新的线程,只有future.get()时,才会等待线程执行接收,如果没有调用get()方式,当前线程不会阻塞
如果有多个Future + Callable线程,为了应用多线程提高并发效率,可以把提交后返回的Future放到一个List<Futrue>中,
遍历该list,同时调用future.get(), 可以保证每个线程执行完后,才执行主程序中的代码
3.Spring事务问题,注解式事务不生效 参考 :59
4.MyBatis原理简述:
MyBatis程序根据XML配置文件创建SqlSessionFactory,SqlSessionFactory再根据配置(来源:配置文件和Java注解)获取一个SqlSession。
SqlSession包含了执行SQL所需的所有方法,可以通过SqlSession实例直接运行映射的SQL语句,完成对数据的增删改查和事务提交,用完后关闭SqlSession