jetty
最后更新于:2022-04-01 20:12:20
# java容器
java容器很很多,tomcat、jetty、jboss、resin、weblogic、webspere等等。
有收费的,也有开源免费的,性能可能是有些许差异的,理论上,收费的应该比免费的,性能要要一些。
但是,用开源免费的来做巨大访问量的(比如千万PV)应用,也是毫无问题的,当前我们所处的技术浪潮,性能的瓶颈一般都在数据库上,在硬盘的访问上,而不是网络请求和响应。
**已知互联网公司使用的java容器:**
jetty:google、美团
tomcat:yougou.com
jetty官网:[http://www.eclipse.org/jetty/](http://www.eclipse.org/jetty/)
jetty源码:git clone https://github.com/eclipse/jetty.project.git
关于tomcat的博客:[http://blog.csdn.net/puma_dong/article/details/21875253](http://blog.csdn.net/puma_dong/article/details/21875253)
[Jetty 的工作原理以及与 Tomcat 的比较](http://www.ibm.com/developerworks/cn/java/j-lo-jetty/)
[Google App Engine转向了Jetty](http://www.infoq.com/cn/news/2009/08/google-chose-jetty/)
# jetty日志
### jetty的日志记录
本周遇到一个jetty日志的问题,看jetty的request.log日志中,我们post过来的参数没有记录,google了好久,没有答案,于是把jetty源码下载下来,看了看日志这部分:jetty-server/src/main/resource/org/eclipse/jetty/server/NCSARequestLog.java,发现日志中关于参数相关的日志,只记录了request.getUri(),也就是说只有get的参数才能记录到日志里面去,post的参数都不会记录到日志里面。
**相关帖子:**
[http://wiki.eclipse.org/Jetty/Tutorial/RequestLog](http://wiki.eclipse.org/Jetty/Tutorial/RequestLog)
### 启动过程中一个日志输出的问题
启动过程中,jetty的INFO级别及以上的日志,会打印到IDE控制台(比如Eclipse),突然有一天,日志不再打印到IDE控制台,而是到某个地方后,重定向到了logs/jetty.log.2015-06-10,如图:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-27_5747b452330eb.jpg)
开始发现这个日志和mms-boot-0.8.jar中的配置一致,截图如下:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-27_5747b45272af3.jpg)
所以把问题的原因定义为:jetty定义的重定向,对于控制台的输出重定向到了文件(实际jetty8.xml仅仅是对System.out/error进行了重定向)。
后来了解到,其他的项目也是同样的启动方式,没有这个问题。
于是重新查原因,对比hotel-campaigns和hotel-cms的日志的不同,开始时有“先入为主”的思维定势,潜意识相信是jetty8.xml再捣鬼,所以对比两个项目的依赖mms的版本、及执行jetty8.xml处的日志,修改log4j.xml配置文件进行多种办法的尝试,没有找出问题。
结合下午发现的log4j.xml配置文件不生效,可以确定:IDE启动hotel-campaigns项目时,对于slf4j的实现,用了logback,为什么突然用了logback呢?
结合pom.xml,可以看到:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-27_5747b452a975a.jpg)
于是exclusions 这个jar,问题解决。
**参考文章:**
[http://www.slf4j.org/codes.html#multiple_bindings](http://www.slf4j.org/codes.html#multiple_bindings)
[http://www.slf4j.org/manual.html](http://www.slf4j.org/manual.html)
# 嵌入式Jetty和RunJettyRun插件
使用RunJettyRun插件,项目运行正常,截图如下:
使用嵌入式方式运行,打不出启动成功的标记,截图如下:
开始尝试解决问题:
1、由于“springmvc”是web.xml里面关于SpringMVC的servlet,尝试把SpringMVC相关的内容都去掉,依然没有打出启动成功的日志
2、尝试着访问了一下antispider-server提供的Thrift服务和Web服务,结果都是正常的,很是疑惑,这个时候怀疑:RunJettyRun插件(这相当于Jetty容器)和Bootstrap(这相当于Jetty内嵌),对于SpringMVC或者Web项目的处理方式,有很大不同,导致没有正常启动完毕。
评:这实际是由于对Jetty理解不深刻,胡乱猜忌Jetty了,如果作为容器和嵌入式,有很大的差别,这简直是不可想象的。
由于没有解决问题,结合Bootstrap源码,关注以上图示中的“第一点”和“第二点”
第一点:这一点没啥好说的,就是src/main/resource下没有config.properties,对于结果没有影响,期间经历一个小插曲,hotel-campaigns-web项目,没有config.properties文件,也没打这行日志,经查是因为依赖的sinai.client里面有config.properties
第二点:这一点因为使用RunJettyRun插件时没有这个日志,所以被高度怀疑,差异了一些资料,但是依然没有解决
http://stackoverflow.com/questions/22938689/info-no-spring-webapplicationinitializer-types-detected-on-classpath
http://stackoverflow.com/questions/16321819/no-spring-webapplicationinitializer-types-detected-on-classpath
http://steveliles.github.io/setting_up_embedded_jetty_8_and_spring_mvc_with_maven_and_no_xml.html
http://hitmit1314.iteye.com/blog/1315816
3、万不得已,新建一个单纯的aitispider-test项目,使用Bootstrap运行,并逐渐增加antispider-server的配置文件,终于在一步增加log4j.xml时,问题重现,找出原因:
4、"org"中,“ERROR”以下级别的日志被过滤了,用RunJettyRun插件,启动成功,是oejs包打印的,所以能显示;而用嵌入式Jetty,启动成功是org包打的,所以被过滤掉了,截图如下:
5、故事到此就结束了,但这个经历,加深了对嵌入式Jetty的理解:服从一套规范,实现一组标准,帮我们实现高效的Web通讯。如果写过ServerSocket通讯程序,会对嵌入式Jetty更深入的了解。
另外一个没有微观佐证的问题,关于Maven解决冲突的方式:
对于,这个问题,我使用准确告诉maven使用版本的方式解决(这是最标准、明确的方式,本身对于pom.xml就应该进行准确的定义,不应该依赖Maven自定义的方式帮我们解决冲突):
';
Java之旅–设计模式
最后更新于:2022-04-01 20:12:18
设计模式,先看名字,设计,模式,目的是为了设计,为了设计给出一些定义出来的,总结出来的,抽象出来的办法,叫做模式。
设计是什么?软件构建中的设计,承前(需求分析、产品定义、架构选择),启后或者伴随(编码、测试),包含结构、包、类、子程序,而模式讲的就是这些东西。
# 设计模式是一种思想
这次关于设计模式的分享不是纯技术,是思想。
思想的东西,很难讲,需要听众具备恰当的理解层次,而这种层次的进化,远比一种技术或者工具的进步难以捉摸,有时十年循环不如一夕顿悟。
设计模式是一种思想,是与语言强相关的。
在低级语言(机器、汇编)中,不会有模式;在未来的高级语言中,会从语法层面实现模式(模式本身是语义层面的);在当前广泛使用的高级语言(Java)中,模式被基于解决特定场景的问题,抽取出来,大行其道,广泛使用。
低级语言和高级语言,没有一条分界线,是逐渐进化的。每个程序员都处于其所在的层次上(或者叫境界),处于一个层次,能理解低层次的落后(比如机器语言),但无法理解高层次的先进。这就是《黑客与画家》中所说的Blub困境。
引申到职场、人生和世界观,每个人也都活在自己的抽象层次中,能看懂低层次的落后,但无法理解高层次先进在何处。从境界高低来讲,是有对错的,但如果没有进化到一个层次,也是无法从根本上被说服的,需要到达,才能认知。
# 面向对象的三大特征
面向对象三大特性:封装、继承、多态。
封装,往往和抽象、高内聚、一致的抽象层次等概念联系在一起。
继承是一条竖线,在编程中很常用,但更常用的是包含("has a"),从广义上来说,甚至可以认为"has a",也是一种适配器模式。能用包含,则不用继承,因为继承引入了继承体系上的复杂性。
多态是继承基础上的重写,加上父类变量对子类对象的引用。可以认为多态,是Java这种静态类型语言,向动态类型语言(比如ruby/python),迈出的一小步。
抽象:
抽取事物最本质形成概念。
并运用概念进行推理、判断的思维活动。
抽象交流。
模拟能力,想象能力。
表达能力(口头,文字,文言文和琼瑶剧)。
字、词、句、段.
# 单例
单例是最重要的一种设计模式。是无状态的逻辑。无状态是业务层实现高可用和可伸缩的重要手段。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-27_5747b45205d86.jpg)
### 单例一:基础
演示了一个最简单的单例、延迟初始化的单例,以及双重检查机制的单例的代码写法。
对于双重检查机制的单例,减小了线程间的碰撞(synchronized),只有前几个线程才可能碰撞,锁的力度小了,性能提高了,线程也安全了。
对于双重检查单例,没有实际意义,只有历史意义,增加Java程序员对于所犯错误的洞察力,如果写单例,有最简单的写法就够了。
过去陈旧的JDBC操作数据库编码过程中,有代码Class.forName("com.mysql.jdbc.Driver"),目的是把类装载到JVM的PermGen区域,这样类的静态方法就可以使用了,这和类的初始化没关系。
显式API初始化也有好处,比如web系统,init写到监听里面去。把初始化放到init里面。清晰了。
### 单例二:Spring
Java生态中,除了JDK之外,最基础的框架是Spring。Spring框架的高层抽象很多,帮我们解决了很多场景下的技术问题。
Spring的基础的、底层的思想是控制反转(Invest Of Control)、依赖注入(Dependency Injection),可以认为是单例的一种表现形式。说的是两个单例对象之间的关系。
控制反转,反转的控制权,原来是谁用谁有控制权;控制反转的思想是,你可以用,但是我依然有初始化自己的控制权。
依赖注入,谁要用(就是谁依赖),谁负责把别的类引入进来。
写简简单单的3个类,能解释Spring的思想。读取配置文件或者扫描Spring注解,利用反射实例化类,放置到一个Map中,这就是Spring IOC的基础过程了。
结合《Spring技术内幕:深入解析Spring架构与设计原理》,花费1个星期简要看看Spring源码,发现就是这样的。
### 单例三:状态和线程安全
参考文章:[http://www.iteye.com/topic/960532](http://www.iteye.com/topic/960532)
有状态:有状态就是有数据存储功能。
无状态:无状态的都是功能操作,对象是线程安全的。
几个概念:维护全局状态、控制状态读写、线程安全。
以上指的是状态的线程安全。对于HashMap的解释,如果从状态的线程安全绝度解释,也是说的通的,HashMap里面有数组,也是有状态的。
### 单例四:数据库
状态固化的单例:其实就是数据库,每一行可以看作一个单例,它是系统唯一的,也可以是线程安全的。
跨JVM的单例:实现方式有很多,只要满足有锁的持久化即可。
多进程安全的单例:说的是数据库锁。
以上都是分布式的思想。
自从有了数据库,程序员就变得廉价了。
# 其他的创建模式
工厂、抽象工厂、建造者、原型。
工厂:创建对象很复杂,所以需要一个创建对象的工厂。
建造者:比如耐克工厂,有鞋帮,有鞋带,合起来之后是一个完整的整体,这就是建造者模式。
什么时候用单例,什么时候用单一状态(类的单一状态)。
为什么不全部用static,而用单例?有一种解释是:控制反转、依赖注入,这种思想。保留了初始化自己的控制权。如果完全的工具类,没有初始化的需求,static就满足了。
# 结构模式
开闭原则:对修改封闭,对扩展开放。
包装(Composite):不想改变原代码,又想实现某些功能。
代理(Proxy):举了一个监护人的例子,让吃才能吃。不改变语义,前前后后做点东西出来。
适配(Adapter):adapter比proxy更牛逼一些,它改变了语义,些许进行改造。
外观(Facade):和代理先比,结构相似,语义不同。
继承是标签,接口表现了具备什么能力。
**面向对象一直争论的问题:**
是用包含(用你的能力,但不打你的标签,"has a"),还是用继承("is a")。一种观点认为,继承对于软件设计,引入了复杂性。
# 行为模式
行为模式,描述的是对象的动作。局限性,必须顺序执行。???
模板:高层的流程抽象。
责任链:演示一个通过注解定义执行顺序的历程。好处:1.动态的组织流程;2. 代码控制流程的执行;深化一下,就是工作流。
以上两种模式,是重构的利器,加上反射,就可以写通用功能的中间件了。
# 不是结束
设计模式是很枯燥的,记住三件事:创建一个对象、包装一个对象、根据当前的场景,信手拈来。
农夫山泉:我们不生产水,我们只是大自然的搬运工。程序员不能成为农夫山泉,不能只是jar包的搬运工。要学会创造,一边搬砖的时候,一边想一想,如何创造出更优秀的东西。
单例/静态、线程安全、无状态,是从3个维度对软件问题的描述,单例/静态是思想角度(单例是设计模式,是语义层面的思想,静态是编程语言从语法角度固化的思想),线程安全是技法角度(是一种基于现代的硬件基础的、常见的、需要解决的,重要的问题场景),无状态是数据角度(无状态的类,实际就是不具备数据存储能力的类)。
# 大师的话(linus)
烂程序员关心的是代码。好程序员关心的是数据结构和它们之间的关系。
面向对象语言以对象为核心,加一些相关联的方法,简直是呓语。
重要的东西应该是数据结构,对象本身有啥重要?
真正有意思的,是在不同类型的不同对象交互而且有锁规则的时候。
但是,即使是这时候,封装什么“对象接口”也绝对错误,因为不再是单一对象的问题了。
他的结论是,面向对象解决的都是一些小问题。
这位大师是写操作系统的,从他的角度来看,自然是正确的,即使不从他的角度,我们也可以认识到数据结构的重要性。
[http://www.csdn.net/article/1970-01-01/2824040](http://www.csdn.net/article/1970-01-01/2824040)
';
Java之旅–硬件和Java并发(神之本源)
最后更新于:2022-04-01 20:12:16
魔法类的玄幻小说中,经常有个“猪脚”特别厉害,别人都用顶级的寒冰箭或者大火球,他不用,他只用一级的寒冰箭,恰到好处的使用,瞬发的使用,特别厉害。
为什么他能做到呢?因为他悟出了一种叫做“神之本源”或者“力量之源”的东西,掌握了魔法的本质,操控程度达到了极致,故而就厉害到了极致,成了“猪脚”。
本篇的讲座,对于Java并发来说,也是这样一种东西,让我们从最底层,从硬件级别,了解Java并发的本质,就好像我们掌握了“神之本源”。
# 现代计算机
先讲一下现代计算机(2015年之前)的发展现状,架构的解决方案、语言的低级特性,本质上是基于硬件的当前现状产生的。
### 计算
现代计算机中,已经不追求CPU的时钟频率更高,而是朝着多核的方向发展。因为CPU已经快得离谱,追求更快对于日常应用意义不大,故而更追求多核、并行计算。
在Core 2 3.0 GHz上,大部分简单指令的执行只需要一个时钟周期,也就是1/3纳秒。
| **数据读取位置** | **花费CPU时钟周期** | **花费的时间(单位纳秒)** |
|-----|-----|-----|
| 寄存器 | 1 cycle | 1/3 |
| L1 Cache | 3-4 cycles | 0.5-1 |
| L2 Cache | 10-20 cycles | 3-7 |
| L3 Cache | 40-45 cycles | 15 |
| 跨槽传输 | | 20 |
| 内存 | 120-240 cycles | 60-120 |
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-27_5747b451a70da.jpg)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-27_5747b451bd122.jpg)
参考文章:
[http://duartes.org/gustavo/blog/post/what-your-computer-does-while-you-wait/](http://duartes.org/gustavo/blog/post/what-your-computer-does-while-you-wait/)
### 速度
对于速度,有个形象的比喻,如果,我们把CPU的一个时钟周期看作一秒,则:
从L1 cache读取信息就好像是拿起桌上的一张草稿纸(3秒);
从L2 cache读取信息就好像是从身边的书架上取出一本书(14秒);
从主存中读取信息则相当于走到办公楼下去买个零食(4分钟);
硬盘寻道的时间相当于离开办公大楼并开始长达一年零三个月的环球旅行。
### 缓存
缓存无处不在,不论是硬盘、网卡、显卡、Raid卡、HBA卡,都有缓存;缓存是比较容易的解决性能问题的简单方案,非常奏效,非常管用。
通过一级一级的缓存,来增加速度,比如:
CPU的L1只有512K,L2是2M,L3只有好的服务器才有,是18M,L3很贵;
硬盘的缓存,一般只有64M,通过这个64M的缓存来增加速度。
**引申一个硬盘的问题:**
如果掉电了呢?
服务器硬盘,可以通过硬盘内部电池保证缓存内容可以写到盘片上去,而家用计算机硬盘是做不到的,这也是价格差别很大的原因之一。
扩展一下,EMS磁盘阵列,做的很好,很快,是通过多磁头技术,减轻某一个磁盘的压力,内部也有缓存。
关于Cache,在架构设计时,经常要解决的一个问题就是:读和写的不安全感。写的时候要保证持久化设备和Cache都写了,读的时候,只读Cache。而服务器端的设备,比如上面说的服务器硬盘,这些问题都是考虑到了的。
### CAS
CAS,Campare And Swap,所有的现代CPU都支持。也是各种语言中,无锁算法,无锁并发实现的根基。
这是CPU硬件级别的,OS也不知道,所以这种实现方式能带来性能上的一定提升,也有副作用。
CAS提供了在激烈争用情况下更佳的性能,换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。
但是,这是给高级用户准备的,慎用,只有当深切了解确实是需要的场景时,才可用。
参考文章:
[JDK 5.0 中更灵活、更具可伸缩性的锁定机制](http://www.ibm.com/developerworks/cn/java/j-jtp10264/index.html)
# Java并发
用一个例程,来说明Java并发的问题,演示4种场景:线程不安全、synchronized、模拟CAS、原子类,如下:
~~~
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import sun.misc.Unsafe;
/**
* 演示Java并发的几种实现方法
*
*/
public class CompareAndSwapShow {
private static int factorialUnSafe;
private static int factorialSafe;
private static int factorialCas;
private static long factorialCasOffset;
private static AtomicInteger factorialAtomic = new AtomicInteger(0);
private static int SIZE = 200;
private static CountDownLatch latch = new CountDownLatch(SIZE * 4);
private static Object lock = new Object();
private static Unsafe unsafe;
// 获取CasTest的静态Field的内存偏移量
static {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
factorialCasOffset = unsafe.staticFieldOffset(CompareAndSwapShow.class.getDeclaredField("factorialCas"));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 存储每种计算方法的最终结果
*/
private static Map factorialMax = new HashMap();
public static void main(String[] args) throws Exception {
for (int i = 0; i < SIZE; i++) {
new Thread(new IncreamUnSafe()).start();
new Thread(new IncreamSafe()).start();
new Thread(new IncreamCas()).start();
new Thread(new IncreamAtomic()).start();
}
latch.await();
System.out.println("IncreamUnsafe Result:" + factorialMax.get("unsafe"));
System.out.println("IncreamSafe Result:" + factorialMax.get("safe"));
System.out.println("IncreamCas Result:" + factorialMax.get("cas"));
System.out.println("IncreamAtomic Result:" + factorialMax.get("atomic"));
}
/**
* 非线程安全的阶乘
*
*/
static class IncreamUnSafe implements Runnable {
@Override
public void run() {
for (int j = 0; j < 1000; j++) {
factorialUnSafe++;
}
recordMax("unsafe", factorialUnSafe);
latch.countDown();
}
}
/**
* 线程安全的阶乘
*
*/
static class IncreamSafe implements Runnable {
@Override
public void run() {
synchronized (lock) {
for (int j = 0; j < 1000; j++) {
factorialSafe++;
}
}
recordMax("safe", factorialSafe);
latch.countDown();
}
}
/**
* 用CAS算法实现的线程安全的阶乘,Java的原子类就是这么搞的,死循环,就是疯狂的压榨CPU
*
*/
static class IncreamCas implements Runnable {
@Override
public void run() {
for (int j = 0; j < 1000; j++) {
for (;;) {
int current = factorialCas;
int next = factorialCas + 1;
if (unsafe.compareAndSwapInt(CompareAndSwapShow.class, factorialCasOffset, current, next)) {
break;
}
}
}
recordMax("cas", factorialCas);
latch.countDown();
}
}
static class IncreamAtomic implements Runnable {
@Override
public void run() {
for (int j = 0; j < 1000; j++) {
factorialAtomic.incrementAndGet();
}
recordMax("atomic", factorialAtomic.get());
latch.countDown();
}
}
/**
* 记录每个线程的最终结果
*
* @param key
* @param target
*/
static synchronized void recordMax(String key, int target) {
Integer value = factorialMax.get(key);
if ((value == null) || (value < target)) {
factorialMax.put(key, target);
}
}
}
~~~
**重点解释一下场景1:**factorialUnSafe++,并不是原子的,会被分解是3条CPU指令:获取,加1,赋值;当CPU的调度,在这3个指令中间时,就可能产生线程安全问题。
附上一张图:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-27_5747b451e1c6d.jpg)
# 总结
为什么AtomicInteger的实现中,用死循环?原因是因为CPU太快了。其他一些概念,比如“自旋锁”,也是这个道理。
性能写到极致,是可以用上L1、L2,Java程序员是做不到了,离底层太远。应用场景中,最大的性能瓶颈,往往都是数据库,而不是程序。如果问题出在程序,则基本都是程序的Bug,而不是程序没有优化到极致。比如:曾经遇到一个CPU Load逐渐到达几百的场景,而最终的答案,是特殊情况下的线程死循环。
CPU为了提高速度,加了很多Cache,这对我们写好程序也有影响。如果是一个CPU,就不会存在线程安全问题了吗?依然会的,CPU调度的时候,依然可能在3条指令之间中断。
volatile关键字,屏蔽了L1、L2,让变量的结果之间写到内存,写的时候锁总线,所以其他线程读到的一定是最新的结果,实现了读线程安全。参考文章:[聊聊并发(一)——深入分析Volatile的实现原理](http://www.infoq.com/cn/articles/ftf-java-volatile)。
ThreadLocal,这个类在Java语言中,紧紧跟随着线程,存储线程本身的变量,非常像CPU的L1、L2。
既然CPU那么快,为什么CPU还会经常负载报警呢?抛开应用层面的原因不说,从语言角度来说,在操作系统层面上,是有锁的, strace跟一下,发现很多,futex,这是JVM为了保证代码执行的顺序性。虽然我的代码没有加锁,实际是有的。搞c的比java快得多,这也是原因。
# 程序员的天空
编程,就跟写作、绘画、作曲一样,首先是一种创造性的活动,而不是一个技术工作。
当然,对一种技术或编程语言的不断练习和保持熟悉很重要,这其实就是在学习使用工具和技法,但它并不会让你本质上变成一名更优秀的程序员。它只是让你能更熟练的使用工具。
而能让你成为更优秀的程序员的是学会如何思考问题,因为最终你是把脑子里思考出的逻辑转换成了一系列操作计算机的指令,让计算机遵照指令解决问题。
学习如何正确的思考——如何抽象归纳,如何组合,如何分析信息,如何自我反省——可以通过各种方式,远非只有编程一种。
有一本书:《黑客与画家》,可以看一遍。
# 后记
知识支离破碎,单讲起来很多人能知道,如果能串起来,殊为不易,这是一个融会贯通的阶段,悟道的前篇。
深度,和广度是相得益彰的。
深度,是技术层面更需要的;广度是管理层面更需要的。
二者相辅相成,就无敌了。
';
Java之旅–Linux&java进阶(看清操作系统层面的事)
最后更新于:2022-04-01 20:12:13
Java的生产系统,最常用的是Linux,所以当解决生产系统问题时,理解Linux系统,熟练掌握常用命令,对于解决问题,甚至对从更高层次理解Java,都是很有帮助的。
那么,应该怎么学Linux呢?上周五听了一次高手讲座,对于学习的串联、衔接、进阶,都是很有帮助的,所以分享到这里来。
本次讲座,先走马观花串一遍Linux命令,让其可以辅助我们的工作。但是不仅仅是讲几个命令而已,而是希望能看清楚一些操作系统层面上的事,达到理解和境界思想上的提升。
理解是灵魂,串讲是骨架,然后可以自行丰满血肉,这是目的。
本次讲座,特别适合“有一定理解,但是不深刻,或者恰恰处于进阶边缘”这种情况的人,一讲,一串,醍醐灌顶,理解会立刻深了一层。
# 1、strace
strace常用来跟踪进程执行时的系统调用和所接收的信号。
strace -T -t -f java Test >out 2>&1
strace -T -t -f p pid
通过这个工具,我们可以看到当Java程序,或者任何一种程序在Linux系统上运行时,实际上都是被分解成Linux API的。
讲这个命令的目的,是提示我们,可以用一种方式,用一种工具,比如strace,去理解我们的程序的运行。
语言,不论在哪种平台运行,最后都是要分解成操作系统API,分解成CPU指令。这是思维的进阶,或者我们忙于日常应用系统的开发,忘记了曾经的本质。
**参考文章:**
[使用strace, ltrace寻找故障原因的线索](http://blog.csdn.net/delphiwcdj/article/details/7387325)
[五种利用strace查故障的简单方法](http://blog.csdn.net/dlmu2001/article/details/8842891)
# 2、进程
pstree 是Linux的进程体系。
ps 是系统某个时刻的进程切面,代表某个时刻有多少个进程在运行。
因为在Linux下,包括进程、Socket等,都是以文件的形式存在的,内存中的。我们可以通过ps找出进程id,然后通过“运行时文件系统”,查看进程的任何信息。
从操作系统层面鉴定进程的信息(路径、jar、连接等),是完全准确的,比从配置文件里面查询准确,也不需要问别人。
ps -ef | grep catalina
cd /proc/60282/fd
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-27_5747b450c18da.jpg)
可以进一步查看更多内容,比如:
a、写多少日志:ll | grep log
b、启动要那些jar:ll | grep jar
c、看到那些一闪一闪的了吗?那些是socket,代表谁连我了:ll | grep socket
d、对于这些socket句柄,我们可以进一步查看是什么进程:lsof | grep 387084
# 3、内存+CPU
top,这是应该极熟的Linux命令,是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,其显示的内容,和Windows的任务管理器是一样的。
free ,查看内存使用情况。
我们也可以在Windows下使用jvisualvm,以图形化的方式,查看Java应用的内存,线程,也可以把线程dump下来,比如,Test应用运行方式如下:
java -Dcom.sun.management.jmxremote.port=9998 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Djava.rmi.server.hostname=192.168.80.128 Test
则可以在本地通过jvisualvm监控进程情况,在命令行输入jvisualvm,远程,连接ip地址,之后右键这个远程连接,新建JMX连接,输入端口,则可以监控这个远程java进程了,截图如下:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-27_5747b450df66e.jpg)
# 4、网络
netstat -anop | grep LIST 可以查看,都启动了那些端口,比如:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-27_5747b45106303.jpg)
第六列“2489/mongod”,进程号2489,是mongod进程,在27017端口监听mongo接入。
有的时候,netstat不显示PID和应用,这一般是两个原因:权限不够,或者netstat版本太低,小于1.6,如果是版本太低,可以用:lsof -i:端口号,来找出PID。
参考文章:[netstat不显示进程名pid的bug](http://blog.chinaunix.net/uid-28337979-id-4230505.html)。
netstat -anop | grep EST 可以查看,端口都被谁连着呢,比如:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-27_5747b45123fa6.jpg)
第4列,代表自己的IP和端口;第5列,对标对方的IP和端口;第6列,代表进程号和启动进程的应用。
**其他常用的:**
strace -T -t -e network,poll curl www.baidu.com 我们可以看到从我们的机器,到百度的服务器,经过的路由路径
cat /etc/resolv.conf 可以看到域名解析服务器的顺序
cat /etc/hosts The static table lookup for host name(主机名查询静态表)
# 5、线程
top -p 59851 shift+H,查看59851进程,有多少个线程。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-27_5747b45139b3d.jpg)
第一列PID,线程ID,换算成16进制,就是线程栈中的NID。
我们可以使用 jstack -l 59851查看,整个进程的信息,也可以使用jstack -l 59851 | grep NID,只看一个线程栈的信息。
很多时候,查看问题的时候,都需要找出占用资源最多的线程,然后查看这个线程的线程栈信息。
这几个步骤,操作起来很容易,其实最最关键的是,线程栈dump下来了,要看得懂。
关于JVM线程的讲解,有一些非常好的文章,如下:
[JVM 内部运行线程介绍](http://ifeve.com/jvm-thread/)
[JVM合集](http://ifeve.com/category/jvm/)
# 6、GC
Linux下一个进程里面的线程,都是共享内存的,所以当分析内存时 ,要看进程。
jstat -gccause 59851 1000 jstat,即JVM Statistics Monitoring Tool,用于收集HotSpot虚拟机各方面的运行数据
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-27_5747b45153f1a.jpg)
我们不怕YGC,YGC很频繁,很快,系统耗时极短,微妙级,对于用户来说是没有感觉的;
但是FGC要注意,FGC进行的时候,JVM是假死的,只进行FullGC,不处理其他请求,可能会持续几秒,FGC太多,说明YGC回收不了,太多的对象进入老年代,很快占满,系统肯定有问题的。
java -XX:+PrintFlagsInitial ,可以查看JVM的参数配置情况,比如,默认情况下,我们可以看到,Survior:Eden = 1:8,New:Old = 2:1。
jmap -histo 59851 | more Memory Map for Java,生成虚拟机的内存转储快照(heapdump文件)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-27_5747b45167d46.jpg)
通过jmap,可以找出系统中可能有安全隐患(比如太多而不能回收)的对象。
通过这些手段,不用分析代码,不用停机,就可以找出问题,解决问题。
jps、jstat、jstack、jmap,这些不是Linux命令,而是JDK写好的放在rt.jar里面的用于辅助诊断的Java APP。
GC是Java程序员的基本功,应该理解的很清晰。
# 7、dstat和iftop
场景1:演示通过sftp上传文件
可以在top中看到sshd和sftp耗费CPU资源很大,因为sftp是基于sshd的;可以在dstat中看到,receive流量很大。
场景2:演示http请求百度首页,通过ss5代理压测,在ss5机器中,dstat显示的send和recv几乎一样。
和我们通常理解的,应该会有很少的发出数据,和很多的接收数据不一致。原因是:
这种理解不对的,作为ss5代理的机器,既从远端接收到大量数据,同时把接收到的大量数据,转发给它所代理的机器,所以有这个网络现象。
这个时候,我们通过iftop,则可以清楚的看到IP到IP的发出发入数据包。
所以,知道,借助于工具,基于Linux的知识,基于Java的知识,定位问题,找出问题,是很关键的。
iftop 是第三方的,是一个实时流量监控工具:
主要用来显示本机网络流量情况及各机器相互通信的流量集合,如单独同那台机器间的流量大小,非常适合于代理服务器和iptables服务器使用。
官方网站:http://www.ex-parrot.com/~pdw/iftop/。
**安装步骤:**
yum -y install flex byacc libpcap ncurses ncurses-devel libpcap-devel
wget http://soft.kwx.gd/tools/iftop-0.17.tar.gz #获得软件包
tar zxvf iftop-0.17.tar.gz #解压
cd iftop-0.17 #进入目录
./configure #使用默认配置
make && make install #编译并安装
如果libpcap包装不上的话,需要到http://pkgs.org/自行下载rpm进行安装。
dstat是Linux自带的,可以yum安装。
**dstat截图:**
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-27_5747b4517a063.jpg)
**iftop截图:**
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-27_5747b4518f69e.jpg)
# 8、“|” 管道。连接、过滤,这是管道的作用,就像连接千家万户的自来水管
管道操作符,按行给数据,一行一给,把上个命令的输出,变成下个命令的输入。
经常使用的参数-c ,类似于 SQL中的group by。
在linux中,要把日志文件,当成数据库表,日志文件也是有行有列的。日志要标准,要有规律,就会很方便的分割查找。
我们可以借助less、more、cat、sort、uniq、grep、awk等命令,很方便的分析日志。
另外,awk是一个强大的文本分析工具,awk在对数据分析并生成报告时,很强大。简单来说awk就是把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再进行各种分析处理。
# 9、其他常用命令
df -h 显示磁盘分区使用情况
du -sh /root/temp 显示某个文件夹的大小
find /root/temp -type f -name "*.txt" | xargs grep "main" 在/root/temp目录下,在普通文件中,在扩展名为txt的文件中,查找含有main字符串的行
lsof -l 显示所有的socket句柄
lsof | grep deleted 刚刚rm或者echo冲文件内容后,看看空间有没有释放
sed 文件内容查找和替换
vim 文本编辑器,常用就会熟悉。开始的时候记录最常用的,比如,翻屏:CTRL+U(D),2gg,3dd,3dw,yy,/查找,?查找。
# 10、学会分析错误的思路
学会错误的模拟重现,会模拟重现就能解决。模拟重现的手段:可以开发一些质量差的代码,把错误做出来。
工具只是帮助我们定位问题的,帮我们记录发生问题时的操作系统切面,要理解这个切面所代表的意义、所隐含的异常,还需要对相关基础知识的深刻了解。比如:
- 用jstat看GC,需要对Java的内存管理、垃圾收集技术深入了解;
- 用netstat看网络,需要对网络知识,特别是TCP,有深入了解;
- 用jstack转储线程栈,需要对Java线程的知识,有深入了解。
';
Java之旅–XML/JSON
最后更新于:2022-04-01 20:12:11
XML和JSON是两种常用的数据交换格式。虽然对于XML和JSON的各种操作,仅仅是常用的工具jar包的使用,没有什么技术含量,但鉴于这两种数据格式的普遍使用,还是拿出一点时间,进行一下简单总结。
# XML
XML官网:[http://www.xml.com/](http://www.xml.com/)
XML保留字符有5个:&、>、<、'、""。
对于XML的解析方式,有两种:DOM方式和SAX方式。DOM是读入内存之后进行各种操作,SAX是流式操作、一次性的。其他的一些工具jar包,比如JDOM、DOM4J,都是对于这两种方式的高层次封装。
**参考网址:**
[http://wenku.baidu.com/link?url=7VjI_4xMpWdV2O82WrNI2KO2UNuhefJYeGYe17QUmH89Nlc9NH20oVr8ZMJ2w1RSvphm5UE88L4FhB4fJgCcV4HldRlJsP9n_o1n1r7gunG](http://wenku.baidu.com/link?url=7VjI_4xMpWdV2O82WrNI2KO2UNuhefJYeGYe17QUmH89Nlc9NH20oVr8ZMJ2w1RSvphm5UE88L4FhB4fJgCcV4HldRlJsP9n_o1n1r7gunG)
[http://inotgaoshou.iteye.com/blog/1012188](http://inotgaoshou.iteye.com/blog/1012188)
**DOM图示:**
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-27_5747b45086c6f.jpg)
**SAX图示:**
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-27_5747b450a3889.jpg)
**演示代码:**
~~~
import java.io.File;
import java.util.Stack;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.w3c.dom.Attr;
import org.w3c.dom.Comment;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
/**
* 演示两种XML的解析方式:DOM和SAX
*
* 至于JDOM和DOM4J,只是在这两种方式之上的更高层次的封装
*
*/
public class XmlDemo {
public static void main(String[] args) throws Exception {
XmlDemo xmlDemo = new XmlDemo();
// DOM方式
DomDemo domDemo = xmlDemo.new DomDemo("src/main/java/com/cl/roadshow/java/xml/people.xml");
domDemo.iterateByName("PERSON");
domDemo.recursiveElement();
// SAX方式
SaxDemo saxDemo = xmlDemo.new SaxDemo("src/main/java/com/cl/roadshow/java/xml/people.xml");
saxDemo.showEvents();
saxDemo.parseDocument();
}
/**
* DOM方式解析XML
*
*/
class DomDemo {
private String path;
public DomDemo(String path) {
this.path = path;
}
/**
* 查询所有符合给到名称的Node,大小写敏感
*
* @param tagName
* @throws Exception
*/
public void iterateByName(String tagName) throws Exception {
// 获得DOM解析器工厂
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
// 获得具体的DOM解析器
DocumentBuilder db = dbf.newDocumentBuilder();
// 解析XML文档,获得Document对象(根结点)
Document doc = db.parse(new File(path));
NodeList nodeList = doc.getElementsByTagName(tagName);
for (int i = 0; i < nodeList.getLength(); i++) {
Element element = (Element) nodeList.item(i);
String content = element.getElementsByTagName("NAME").item(0).getFirstChild().getNodeValue();
System.out.println("name:" + content);
content = element.getElementsByTagName("ADDRESS").item(0).getFirstChild().getNodeValue();
System.out.println("address:" + content);
content = element.getElementsByTagName("TEL").item(0).getFirstChild().getNodeValue();
System.out.println("tel:" + content);
content = element.getElementsByTagName("FAX").item(0).getFirstChild().getNodeValue();
System.out.println("fax:" + content);
content = element.getElementsByTagName("EMAIL").item(0).getFirstChild().getNodeValue();
System.out.println("email:" + content);
System.out.println("--------------------------------------");
}
}
/**
* 从根节点开始,遍历XML的所有元素
*
* @throws Exception
*/
public void recursiveElement() throws Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(new File(path));
// 获得根元素结点
Element root = doc.getDocumentElement();
parseElement(root);
}
/**
* 递归方法
*
* @param element
*/
private void parseElement(Element element) {
String tagName = element.getNodeName();
NodeList children = element.getChildNodes();
System.out.print("<" + tagName);
// element元素的所有属性所构成的NamedNodeMap对象,需要对其进行判断
NamedNodeMap map = element.getAttributes();
// 如果该元素存在属性
if (null != map) {
for (int i = 0; i < map.getLength(); i++) {
// 获得该元素的每一个属性
Attr attr = (Attr) map.item(i);
String attrName = attr.getName();
String attrValue = attr.getValue();
System.out.print(" " + attrName + "=\"" + attrValue + "\"");
}
}
System.out.print(">");
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
// 获得结点的类型
short nodeType = node.getNodeType();
if (nodeType == Node.ELEMENT_NODE) {
// 是元素,继续递归
parseElement((Element) node);
} else if (nodeType == Node.TEXT_NODE) {
// 递归出口
System.out.print(node.getNodeValue());
} else if (nodeType == Node.COMMENT_NODE) {
System.out.print("");
}
}
System.out.print("" + tagName + ">");
}
}
/**
* SAX方式解析XML
*
*/
class SaxDemo {
private String path;
public SaxDemo(String path) {
this.path = path;
}
public void showEvents() throws Exception {
// 获得SAX解析器工厂实例
SAXParserFactory factory = SAXParserFactory.newInstance();
// 获得SAX解析器实例
SAXParser parser = factory.newSAXParser();
// 开始进行解析
parser.parse(new File(path), new EventHandler());
}
public void parseDocument() throws Exception {
// 获得SAX解析器工厂实例
SAXParserFactory factory = SAXParserFactory.newInstance();
// 获得SAX解析器实例
SAXParser parser = factory.newSAXParser();
// 开始进行解析
parser.parse(new File(path), new ParseHandler());
}
/**
* 演示SAX解析方式的事件驱动过程
*
*/
class EventHandler extends DefaultHandler {
@Override
public void startDocument() throws SAXException {
System.out.println("\n--------------------------------------");
System.out.println("start document");
}
@Override
public void endDocument() throws SAXException {
System.out.println("finish document");
System.out.println("--------------------------------------");
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
System.out.println("start element");
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
System.out.println("finish element");
}
}
/**
* 演示用SAX方式解析PERSON节点的过程
*
*/
class ParseHandler extends DefaultHandler {
private Stack stack = new Stack();
private String name;
private String tel;
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
stack.push(qName);
for (int i = 0; i < attributes.getLength(); i++) {
String attrName = attributes.getQName(i);
String attrValue = attributes.getValue(i);
System.out.println(attrName + "=" + attrValue);
}
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
String tag = stack.peek();
if ("NAME".equals(tag)) {
name = new String(ch, start, length);
} else if ("TEL".equals(tag)) {
tel = new String(ch, start, length);
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
stack.pop(); // 表示该元素已经解析完毕,需要从栈中弹出
if ("PERSON".equals(qName)) {
System.out.println("NAME:" + name);
System.out.println("TEL:" + tel);
System.out.println();
}
}
}
}
}
~~~
# JSON
JSON官网:[http://www.json.org/json-zh.html](http://www.json.org/json-zh.html)
对于JSON的解析,各种语言下都有 很多可用客户端,在Java下,fastjson是推荐使用的一种,快、强大、无依赖。
**代码演示:**
~~~
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
/**
* fastjson 是一个性能很好的 Java 语言实现的 JSON 解析器和生成器,来自阿里巴巴的工程师开发
*
*
* 主要特点:比其它任何基于Java的解析器和生成器更快,包括jackson;强大;零依赖
*
*/
public class FastjsonDemo {
public static void main(String[] args) {
// 将JSON和JavaBean对象互相转换
Person person = new Person(1, "张三", null);
String jsonString = JSON.toJSONString(person);
System.out.println(jsonString);
person = JSON.parseObject(jsonString, Person.class);
System.out.println(person.getName());
System.out.println("--------------------------------------");
// 将JSON字符串转化成List对象
Person person1 = new Person(1, "fastjson1", 11);
Person person2 = new Person(2, "fastjson2", 22);
List persons = new ArrayList();
persons.add(person1);
persons.add(person2);
jsonString = JSON.toJSONString(persons);
System.out.println("json字符串:" + jsonString);
persons = JSON.parseArray(jsonString, Person.class);
System.out.println(persons.toString());
System.out.println("--------------------------------------");
// 将JSON字符串转化成List对象
List list1 = new ArrayList();
list1.add("fastjson1");
list1.add("fastjson2");
list1.add("fastjson3");
jsonString = JSON.toJSONString(list1);
System.out.println(jsonString);
List list2 = JSON.parseObject(jsonString, new TypeReference
';
- >() {
});
System.out.println("list2:" + list2.toString());
System.out.println("--------------------------------------");
// JSON
Java之旅–通讯
最后更新于:2022-04-01 20:12:09
# 概述
通讯,源于网络,网络从下到上,分为7层:物理层、数据链路层、网络层、传输层、会话层、表示层和应用层;通讯,基于协议,常用的协议有很多,比如网络层协议IP、传输层协议TCP/UDP、应用层协议HTTP/SOAP/REST等。
我们还会经常听到Socket,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口。有个形象的比喻:HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通信的能力。
任何语言,都会提供对通讯的支持,我们今天用Java语言,演示几种在Java中比较常用的通讯技术,包括:Socket、Http、REST、Dubbo、Thrift、FTP。
# Socket
**SocketServer.java**
~~~
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketServer {
public static void main(String[] args) {
SocketServer ss = new SocketServer();
//设定服务端的端口号
ServerSocket s = null;
try {
s = new ServerSocket(10099);
System.out.println("ServerSocket Start:"+s);
while(true)
{
Socket socket = s.accept(); //这个地方是阻塞的
System.out.println("Server_Accept:"+socket);
SocketServer.HandleSocket vsm = ss.new HandleSocket(socket);
new Thread(vsm).start();
}
} catch (IOException e) {
e.printStackTrace();
} finally
{
try {
s.close();
} catch (IOException e) {
}
}
}
class HandleSocket implements Runnable
{
private Socket socket;
public HandleSocket(Socket socket)
{
this.socket = socket;
}
@Override
public void run() {
BufferedReader br = null;
PrintWriter pw = null;
try {
//用于接收客户端发来的请求
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//用于发送返回信息,可以不需要装饰这么多io流使用缓冲流时发送数据要注意调用.flush()方法
pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())),true);
String str = br.readLine();
if(str != null)
{
pw.println("OK_"+Thread.currentThread().getName());
pw.flush();
System.out.println("Command:"+str);
}
} catch (Exception e) {
e.printStackTrace();
}finally{
System.out.println("Server_Close:"+socket);
try {
br.close();
pw.close();
socket.close();
} catch (Exception e2) {
}
}
}
}
}
~~~
**SocketClient.java**
~~~
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
public class SocketClient {
public static void main(String[] args) throws InterruptedException {
for(int i=0;i<10;i++) {
call( "Hello" + i);
}
}
public static void call(String command) {
Socket socket = null;
BufferedReader br = null;
PrintWriter pw = null;
try {
//客户端socket指定服务器的地址和端口号
socket = new Socket("127.0.0.1", 10099);
System.out.println("Client_Open" + socket + " Command:" + command);
//同服务器原理一样
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())));
pw.println(command);
pw.flush();
String str = br.readLine();
System.out.println("Client_Receive:"+str);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
System.out.println("Client_Close:"+socket);
br.close();
pw.close();
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
~~~
# Http
### **HttpConnection演示**
~~~
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
public class HttpConnectionTest {
public static void main(String[] args) throws Exception {
System.out.println("begin send");
String inputParam = "Test ";
URL url = null;
HttpURLConnection httpConn = null;
OutputStream output = null;
OutputStreamWriter outr = null;
//url = new URL("http://127.0.0.1:8888/iotest/ReadServlet");
url = new URL("http://www.baidu.com");
httpConn = (HttpURLConnection) url.openConnection();
httpConn.setConnectTimeout(30000);
httpConn.setReadTimeout(30000);
HttpURLConnection.setFollowRedirects(true);
httpConn.setDoOutput(true);
httpConn.setRequestMethod("POST");
httpConn.setRequestProperty("Content-Type", "text/xml");
httpConn.connect();
output = httpConn.getOutputStream();
outr = new OutputStreamWriter(output);
// 写入请求参数
outr.write(inputParam.toString().toCharArray(), 0, inputParam.toString().length());
outr.flush();
outr.close();
System.out.println("send ok");
int code = httpConn.getResponseCode();
System.out.println("code " + code);
System.out.println(httpConn.getResponseMessage());
//读取响应内容
String sCurrentLine = "";
String sTotalString = "";
if (code == 200)
{
java.io.InputStream is = httpConn.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
while ((sCurrentLine = reader.readLine()) != null)
if (sCurrentLine.length() > 0)
sTotalString = sTotalString + sCurrentLine.trim();
} else
{
sTotalString = "远程服务器连接失败,错误代码:" + code;
}
System.out.println("response:" + sTotalString);
}
}
~~~
### **Apache HttpComponents**
官网:[http://hc.apache.org/](http://hc.apache.org/)
已经废弃的Jar(这个项目,自从2007年8月份就不再维护了,已经迁移到org.apache.httpcomponents了):
~~~
commons-httpclient
commons-httpclient
3.1
~~~
org.apache.httpcomponents maven依赖:
~~~
org.apache.httpcomponents
httpcore
4.2.3
org.apache.httpcomponents
httpclient
4.2.3
~~~
Apache HttpComponents代码演示:
~~~
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.util.ArrayList;
import java.util.List;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.HttpParams;
import com.creditease.ns.oltp.tunneladapter.tunnels.cb.util.CbConst;
/**
* 更多参考网址:http://hc.apache.org/
*
*/
public class HttpClientTest {
static String url = "http://www.tuicool.com/";
public static void main(String[] args) throws HttpException, IOException {
testHttp2();
}
public static void testHttp2() {
org.apache.http.client.HttpClient httpClient = null;
HttpPost httpPost = null;
InputStream in = null;
httpClient = new DefaultHttpClient();
httpPost = new HttpPost(url);
String resp = "";
try {
httpClient = new DefaultHttpClient();
httpPost = new HttpPost(url);
if (url.indexOf("https") != -1) {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
in = new FileInputStream("quick.keystore");
keyStore.load(in, "chinabank".toCharArray());
SSLSocketFactory sslSocketFactory = new SSLSocketFactory(keyStore);
Scheme scheme = new Scheme("https", 443, sslSocketFactory);
httpClient.getConnectionManager().getSchemeRegistry().register(scheme);
}
HttpParams httpParams = httpClient.getParams();
httpParams.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 1000 * 20);
httpParams.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 1000 * 40);
List reqPair = new ArrayList();
reqPair.add(new BasicNameValuePair("charset", "UTF-8"));
reqPair.add(new BasicNameValuePair("req", "nihao"));
UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(reqPair, "UTF-8");
httpPost.setEntity(urlEncodedFormEntity);
HttpResponse response = httpClient.execute(httpPost);
HttpEntity responseEntity = response.getEntity();
if (responseEntity != null) {
InputStream is = responseEntity.getContent();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int ch = 0;
while ((ch = is.read(buffer)) != -1) {
baos.write(buffer, 0, ch);
}
byte bytes[] = baos.toByteArray();
resp = new String(bytes, CbConst.ENCODING);
}
int statusCode = response.getStatusLine().getStatusCode();
System.out.println("statusCode:" + statusCode);
System.out.println("resp:" + resp);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (in != null) {
in.close();
}
httpPost.releaseConnection();
httpClient.getConnectionManager().shutdown();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
~~~
quick.keystore,是一个密码保护的文件,存放私钥和证书。可以通过JDK自带的keytool工具生成。只要有密码,可以提取出私钥和证书。参考网址:
[从Java Keystore文件中提取私钥、证书](http://blog.csdn.net/moreorless/article/details/4985940)[](#)
[keystore提取私钥和证书](http://blog.csdn.net/madun/article/details/8677783)
[java 调用 keytool 生成keystore 和 cer 证书](http://blog.csdn.net/saindy5828/article/details/11987587)
[KeyStore中的别名](http://blog.csdn.net/peterwanghao/article/details/1771723)
### 一个Http压测例程
~~~
import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map.Entry;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class HttpSocketPressTest {
private Long succNum = 0L;
private Long failNum = 0L;
private Long connFailNum = 0L;
private Long readFailNum = 0L;
private Long otherFailNum = 0L;
private Long beginTime = System.currentTimeMillis();
private Boolean recordSuccReturn = false;
private String url = "http://www.baidu.com";
public static final int ThreadNum = 100;
public static final int MonitorInterval = 3;
public static void main(String[] args) throws Exception {
createDir("d:\\httptest\\");
createDir("d:\\httptest\\succ\\");
createDir("d:\\httptest\\fail\\");
createDir("d:\\httptest\\exec\\");
createDir("d:\\httptest\\execother\\");
HttpSocketPressTest hspt = new HttpSocketPressTest();
if ((args != null) && (args.length > 0)) {
hspt.setRecordSuccReturn(true);
}
System.out.println("开" + ThreadNum + "个线程,测试用使用Socket5[10.100.140.85:1080]代理Get方式访问外网。");
System.out.println("如果需要对200返回也记录返回内容,请运行程序时输入任意参数,记录位置d:/httptest/succ。");
System.out.println("异常及非200返回会写在d:/httptest/exception和fail目录。");
ExecutorService executorService = Executors.newFixedThreadPool(ThreadNum);
for (int i = 0; i < ThreadNum; i++) {
executorService.submit(new HttpConn(hspt));
}
Monitor m = new Monitor(hspt);
Timer timer = new Timer();
timer.schedule(m, MonitorInterval * 1000, MonitorInterval * 1000);
}
public synchronized void IncreaseSuccNum() {
succNum++;
}
public synchronized void IncreaseFailNum() {
failNum++;
}
public synchronized void IncreaseConnFailNum() {
connFailNum++;
}
public synchronized void IncreaseReadFailNum() {
readFailNum++;
}
public synchronized void IncreaseOtherFailNum() {
otherFailNum++;
}
public synchronized Long getSuccNum() {
return succNum;
}
public synchronized Long getFailNum() {
return failNum;
}
public synchronized Long getConnFailNum() {
return connFailNum;
}
public synchronized Long getReadFailNum() {
return readFailNum;
}
public synchronized Long getOtherFailNum() {
return otherFailNum;
}
public Long getBeginTime() {
return beginTime;
}
public void setRecordSuccReturn(Boolean b) {
recordSuccReturn = b;
}
public Boolean getRecordSuccReturn() {
return recordSuccReturn;
}
public String getUrl() {
return url;
}
private static void createDir(String dir) {
File f = new File(dir);
if (!f.exists()) {
f.mkdir();
}
}
}
class Monitor extends TimerTask {
private HttpSocketPressTest hspt;
private Long times = 0L;
public Monitor(HttpSocketPressTest hspt) {
this.hspt = hspt;
}
@Override
public void run() {
Long nowTime = System.currentTimeMillis();
Long interval = nowTime - hspt.getBeginTime();
System.out.println("");
System.out.println("间隔" + HttpSocketPressTest.MonitorInterval + "秒报告一次结果,这是第" + ++times + "次,如下:");
System.out.println("当前时间:" + new Date());
System.out.println("累计运行时间(单位秒):" + (interval / 1000));
System.out.println("成功次数:" + hspt.getSuccNum() + ";平均每秒成功次数:" + ((hspt.getSuccNum() * 1000) / interval));
System.out.println("失败次数:" + hspt.getFailNum() + "【Connection timed out:" + hspt.getConnFailNum()
+ ",Read timed out:" + hspt.getReadFailNum() + ",其他异常:" + hspt.getOtherFailNum() + "】");
}
}
class HttpConn implements Runnable {
private HttpSocketPressTest hspt;
public HttpConn(HttpSocketPressTest hspt) {
this.hspt = hspt;
}
@Override
public void run() {
while (true) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd-HHmmss");
String d = sdf.format(new Date());
// System.out.println("[" + d + "] [" + Thread.currentThread().getName() +
// "] [访问外网]");
// long startTime = System.currentTimeMillis();
String strUrl = hspt.getUrl();
String domainName = strUrl.split("\\.")[1];
HttpURLConnection httpConn = null;
BufferedReader reader = null;
try {
URL url = null;
url = new URL(strUrl);
Proxy p;
p = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress("10.100.140.85", 1080));
httpConn = (HttpURLConnection) url.openConnection(p);
httpConn.setConnectTimeout(30 * 1000);
httpConn.setReadTimeout(30 * 1000);
HttpURLConnection.setFollowRedirects(true);
httpConn.setDoOutput(true);
httpConn.setRequestMethod("GET");
httpConn.setRequestProperty("Content-Type", "text/xml");
for (Entry> m : httpConn.getHeaderFields().entrySet()) {
// System.out.println(m.getKey() + ":" + m.getValue());
}
// 这行代码会报:java.net.ConnectException: Connection timed out: connect
httpConn.connect();
int code = httpConn.getResponseCode();
// 读取响应内容
String sCurrentLine = "";
StringBuilder sb = new StringBuilder();
if (code == 200) {
hspt.IncreaseSuccNum();
} else {
hspt.IncreaseFailNum();
}
sb.append("StatusCode:").append(code).append("\n");
sb.append("url:").append(strUrl).append("\n");
InputStream is = httpConn.getInputStream();
reader = new BufferedReader(new InputStreamReader(is));
// 这行代码会报:java.net.SocketTimeoutException: Read timed out
while ((sCurrentLine = reader.readLine()) != null) {
if (sCurrentLine.length() > 0) {
sb.append(sCurrentLine.trim());
}
}
// System.out.println("接收内容大小:" + sb.toString().length());
if (hspt.getRecordSuccReturn() && (code == 200)) {
writeFile("d:\\httptest\\succ\\" + d + "_" + UUID.randomUUID() + ".html", sb.toString());
}
if (code != 200) {
writeFile("d:\\httptest\\fail\\" + d + "_" + UUID.randomUUID() + ".html", sb.toString());
}
} catch (Exception e) {
e.printStackTrace();
System.out.println(new Date());
hspt.IncreaseFailNum();
String msg = e.toString();
Boolean isOther = false;
if (msg.indexOf("Connection timed out") != -1) {
hspt.IncreaseConnFailNum();
} else if (msg.indexOf("Read timed out") != -1) {
hspt.IncreaseReadFailNum();
} else {
isOther = true;
hspt.IncreaseOtherFailNum();
}
if (isOther) {
writeFile("d:\\httptest\\execother\\" + domainName + "_" + d + "_" + UUID.randomUUID() + ".txt", strUrl
+ "\r\n" + e);
} else {
writeFile("d:\\httptest\\exec\\" + domainName + "_" + d + "_" + UUID.randomUUID() + ".txt", strUrl + "\r\n"
+ e);
}
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (httpConn != null) {
httpConn.disconnect();
}
// System.out.println("[" + d + "] [" + Thread.currentThread().getName()
// + "] [访问外网] [cost:["
// + (System.currentTimeMillis() - startTime) + "ms]]");
}
}
}
private void writeFile(String name, String content) {
FileWriter writer = null;
try {
// 打开一个写文件器,构造函数中的第二个参数true表示以追加形式写文件
writer = new FileWriter(name, false);
writer.write(content);
writer.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
~~~
# Dubbo
...待续
# Thrift
...待续
# Rest
...待续
# FTP
...待续
';
JDK框架简析–java.io包中的输入输出类库
最后更新于:2022-04-01 20:12:06
# 题记
JDK,Java Development Kit。
我们必须先认识到,JDK只是,仅仅是一套Java基础类库而已,是Sun公司开发的基础类库,仅此而已,JDK本身和我们自行书写总结的类库,从技术含量来说,还是在一个层级上,它们都是需要被编译成字节码,在JRE中运行的,JDK编译后的结果就是jre/lib下的rt.jar,我们学习使用它的目的是加深对Java的理解,提高我们的Java编码水平。
本系列所有文章基于的JDK版本都是1.7.16。
源码下载地址:[https://jdk7.java.net/source.html](https://jdk7.java.net/source.html)
# 本节内容
Java IO库提供了一个可以称之为链接的机制,可以将一个流与另一个流首尾衔接,形成一个流管道的链接。这种机制实际上是装饰模式(Decorator)的应用。
通过流的链接,可以动态的增加流的功能,而这种功能的增加,是通过动态的组合一些流的基本功能获取的。
我们要获取一个IO对象,往往要产生多个IO对象,这也是Java IO库不太容易掌握的地方,但这种在IO库中Decorator模式的运用,给我们提供了实现上的灵活性。
# 流的分类
节点流:从特定的地方读写的流类,比如磁盘或内存;比如FileInputStream/FileOutputStream就是节点流。
过滤流:使用节点流作为输入输出,提供功能的增强,继承自FilterInputStream/FilterOututStream的类(比如DataInputStream提供读写Java基本数据类型的能力,BufferedInputStream提供数据缓存的能力)。
管道流:用于线程间的通信,比如PipedInputStream/PipedOutputStream。
演示一个功能增强的流程:
File --》FileInputStream(从文件中读取字节) --》 BufferedInputStream(增加了缓冲功能)--》DataInputStream(增加了读取Java基本数据类型的能力)--》数据
数据 --》DataInputStream(输出流写入Java基本类型)--》BufferedOutputStream(提供数据写入缓冲区的功能)--》FileOutputStream(写入文件)--》File
字节流和字符流:
Java语言使用Unicode来表示字符。
Reader和Writer主要用来读写字符。
# 对象序列化
将对象转化为字节流保存起来,并在日后还原这个对象,这种机制叫做对象序列化。
将一个对象保存到永久存储设备上,叫做持久化。
一个对象要想能够实现序列化,必须实现java.io.Serializable接口,这个接口是一个声明式接口,没有任何内容,只是告诉编译器,对象是可以序列化的。
当一个对象被序列化时,只保留这个对象的非静态成员变量,不能保存任何成员方法和静态的成员变量。
如果一个对象的成员变量是一个对象,那么这个对象的数据成员也会被保存。
如果一个可序列化的对象包含对某个不可序列化的对象的成员变量的引用,那么整个序列化操作会失败,并且抛出一个异常:java.io.NotSerializableException。我们可以将这个引用标记为transient,那么这个对象也可以序列化。因为用transient关键字标记的变量,在序列化时,会被抛弃。
### serialVersionUID
Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。
(1)实现了Serializable接口的对象,可以显示设置serialVersionUID的值(JDK源码中就是这么干的)
(2)也可以不设置serialVersionUID的值,这时,Java序列化机制会根据编译的class(它通过类名,方法名等诸多因素经过计算而得,理论上是一一映射的关系,也就是唯一的)自动生成一个serialVersionUID作序列化版本比较用,这种情况下,如果class文件(类名、方法等)没有发生变化,增加空格、换行、注释是可以的,编译多次,serialVersionUID也不会变化的;如果增加了方法、变量等,则会报异常:java.io.InvalidClassException
(3)日常生产中,我们经常这么设置:private static final long serialVersionUID = 1L; 这时,Java序列化机制会认为版本是一致的,只会对能对照起来的变量进行赋值,对于类和序列化的数据中不一致的地方(比如序列化之后,类增加减少了字段),会直接抛弃
### 对象序列化代码演示
~~~
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.URI;
import java.net.URISyntaxException;
public class Test {
public static void main(String[] args) throws Exception {
serial();
Person p = deserial();
System.out.println(p.getName());
System.out.println(p.getAge());
}
private static void serial() throws Exception {
Person p = new Person("zhangsan",18);
FileOutputStream fos = new FileOutputStream("/Users/puma/tt/person.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(p);
oos.flush();
oos.close();
}
private static Person deserial() throws Exception{
FileInputStream fis = new FileInputStream("/Users/puma/tt/person.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
Person p = (Person)ois.readObject();
return p;
}
}
class Parent implements Serializable {}
class Person extends Parent {
private static final long serialVersionUID = 1L;
private String name;
private Integer age;
public Person(String name,Integer age) {
this.setName(name);
this.setAge(age);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
~~~
# NIO
Java Non-blocking IO,很多著名的异步通信框架,比如Netty/Mina,都是基于NIO的,对性能大有提升。
异步 I/O 的一个优势在于,它允许您同时根据大量的输入和输出执行 I/O。同步程序常常要求助于轮询,或者创建许许多多的线程以处理大量的连接。
使用异步 I/O,您可以监听任何数量的通道上的事件,不用轮询,也不用额外的线程。
临时还没来得及仔细钻研,记录几篇文章:
[http://blog.csdn.net/kobejayandy/article/details/11545057](http://blog.csdn.net/kobejayandy/article/details/11545057)
[http://news.cnblogs.com/n/205413/](http://news.cnblogs.com/n/205413/)
[http://weixiaolu.iteye.com/blog/1479656](http://weixiaolu.iteye.com/blog/1479656)
[http://www.iteye.com/topic/834447](http://www.iteye.com/topic/834447)
[http://blog.csdn.net/anders_zhuo/article/details/8535719](http://blog.csdn.net/anders_zhuo/article/details/8535719)
';
JDK框架简析–java.util包中的工具类库
最后更新于:2022-04-01 20:12:04
# 题记
JDK,Java Development Kit。
我们必须先认识到,JDK只是,仅仅是一套Java基础类库而已,是Sun公司开发的基础类库,仅此而已,JDK本身和我们自行书写总结的类库,从技术含量来说,还是在一个层级上,它们都是需要被编译成字节码,在JRE中运行的,JDK编译后的结果就是jre/lib下的rt.jar,我们学习使用它的目的是加深对Java的理解,提高我们的Java编码水平。
本系列所有文章基于的JDK版本都是1.7.16。
源码下载地址:[https://jdk7.java.net/source.html](https://jdk7.java.net/source.html)
# 本节内容
在本节中,简析java.util包所包含的工具类库,主要是集合相关的类库,其次还有正则、压缩解压、并发、日期时间等工具类。
本篇内容大致、简单的对于java.util包进行了一个描述,以后会逐渐进行内容补充,本篇文章相当于一个占位符,所谓先有了骨架,才能逐渐丰满![偷笑](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-18_569ca4488de4a.gif)
# 集合类
### 基本情况
**主要接口及其继承关系如下:**
SortedSet --> Set --> Collection --> Iterable
List --> Collection --> Iterable
SortedMap --> Map
**常用类及其继承关系如下:**
HashSet/LinkedHashSet --> Set
TreeSet --> SortedSet --> Set
ArrayList/LinkedList --> List
HashMap --> Map
TreeMap --> SortedMap --> Map
统一称谓:Collection分支的,我们称之为“聚集”;Map分支的,我们称之为“映射”。
Collection继承自Iterable,所以其下的类都可以用迭代器Iterator访问,也可以用for(E e:es)形式访问;Map可以用实现了其内部接口Entry的对象,作为一个元素。
Hashtable和HashMap,他们都实现了Map接口;Hashtable继承自古老的抽象类Dictionary,是线程安全的;HashMap继承自较新的抽象类AbstractMap,不是线程安全的。
HashMap允许null的键和值,而Hashtable不允许null的键和值,这是因为:
Hashtable有方法contains方法(判断是否存在值),如果允许的话,则不论key或者value为null,都会返回null,这容易误解,所以Hashtable就强制限制了,对于null 键和值,直接抛出NullPointerException;
HashMap没有contains方法,分别是containsKey()和containsValues()。
另外JDK5开始,对于线程安全的Map,有一种ConcurrentHashMap,高效,其实现线程安全的过程中,没有使用synchronized,是一种分段的结构,并用CAS这种无锁算法实现了线程安全。
### Hash
Object类有两种方法来推断对象的标识:equals()和hashCode()。
一般来说,如果您忽略了其中一种,您必须同时忽略这两种,因为两者之间有必须维持的至关重要的关系。
特殊情况是根据equals() 方法,如果两个对象是相等的,它们必须有相同的hashCode()值,Object源码中对此有要求,尽管这通常不是真的。
[http://blog.sina.com.cn/s/blog_5dc351100101l57b.html](http://blog.sina.com.cn/s/blog_5dc351100101l57b.html)
[http://fhuan123.iteye.com/blog/1452275](http://fhuan123.iteye.com/blog/1452275)
关于HashMap的源码分析,可以参考:[http://github.thinkingbar.com/hashmap-analysis/](http://github.thinkingbar.com/hashmap-analysis/)
LinkedHashMap,重写了HashMap的迭代器、AddEntry、Entry等几个方法和类,用一个双向链表存储元素加入的顺序;这可以按照访问顺序排序,最近访问的元素(get/put),会被放在链表的末尾,这是LRU算法(Least Recenty Used),最近最少使用算法。
### ArrayList和LinkedList
关于ArrayList和LinkedList,ArrayList是基于数组的,这种方式将对象放在连续的位置中,读取快,但是容量不足时需要进行数组扩容,性能降低,插入和删除也慢;LinkedList是基于链表的,插入和删除都快,但是查找麻烦,不能按照索引查找。所以说,对于构造一个队列是用ArrayList或者LinkedList,是根据性能和方便来考虑的,比如LinkedList有removeLast(),ArrayList只能remove(index),用LinkedList构造一个Queue的代码演示如下:
~~~
class Queue {
private LinkedList llt;
public Queue() {
llt = new LinkedList();
}
public void add(String s) {
llt.add(s);
}
public String get() {
return llt.removeLast(); //队列
//return llt.removeFirst(); //堆栈
}
public boolean isNull() {
return llt.isEmpty();
}
}
~~~
### ConcurrentModificationException
~~~
import java.util.*;
import java.util.Map.Entry;
class Test
{
public static void main(String[] args) throws Exception {
HashMap mTemp = new HashMap();
mTemp.put("test1",1);
Iterator> iTemp = mTemp.entrySet().iterator();
//以下这行代码会引发java.util.ConcurrentModificationException,
//因为对聚集创建迭代器之后,进行遍历或者修改操作时,如果遇到期望的修改计数器和实际的修改计数器不一样的情况(modCount != expectedModCount)
//就会报这个Exception,乐观锁的思想
//mTemp.put("test2",2);
while(iTemp.hasNext()) {
System.out.println(iTemp.next().getKey());
}
//for循环,写法更简单一些,在编译后,还是会被转换为迭代器
for(Entry e : mTemp.entrySet()) {
System.out.println(e.getKey());
}
ArrayList al = new ArrayList();
al.add("test");
for(String s : al) {
Integer i = Integer.reverse((new java.util.Random().nextInt(100)));
al.add(i.toString()); //这行代码也会报ConcurrentModificationException
}
}
}
~~~
对于这种情况,可以使用java.util.concurrent包中的相关类,比如CopyOnWriteArrayList,就不会报这个异常了,因为CopyOnWriteArrayList类最大的特点就是,在对其实例进行修改操作(add/remove等)会新建一个数据并修改,修改完毕之后,再将原来的引用指向新的数组。这样,修改过程没有修改原来的数组,也就没有了ConcurrentModificationException错误。
我们可以参考CopyOnWriteArrayList的源码:
~~~
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return true (as specified by {@link Collection#add})
*/
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
~~~
ConcurrentModificationException,表明:我正读取的内容被修改掉了,你是否需要重新遍历?或是做其它处理?这就是fast-fail(快速失败机制)的含义。
虽然这只是在一个线程之内,并不是多线程的,我们同样也可以这样理解fast-fail:
Fail-fast是并发中乐观(optimistic)策略的具体应用,它允许线程自由竞争,但在出现冲突的情况下假设你能应对,即你能判断出问题何在,并且给出解决办法;
悲观(pessimistic)策略就正好相反,它总是预先设置足够的限制,通常是采用锁(lock),来保证程序进行过程中的无错,付出的代价是其它线程的等待开销。
快速失败机制主要目的在于使iterator遍历数组的线程能及时发现其他线程对Map的修改(如put、remove、clear等),因 此,fast-fail并不能保证所有情况下的多线程并发错误,只能保护iterator遍历过程中的iterator.next()与写并发.
### TreeSet和Collections.sort
TreeSet是基于TreeMap的实现,底层数据结构是“红黑树”,数据加入时已经排好顺序,存取及查找性能不如HashSet;Collections.sort是先把List转换成数组,再利用“归并排序”算法进行排序,归并排序是一种稳定排序。
关于TreeMap的文章:
[http://zh.wikipedia.org/wiki/%E7%BA%A2%E9%BB%91%E6%A0%91](http://zh.wikipedia.org/wiki/%E7%BA%A2%E9%BB%91%E6%A0%91)
[http://www.cnblogs.com/fornever/archive/2011/12/02/2270692.html](http://www.cnblogs.com/fornever/archive/2011/12/02/2270692.html)
[http://shmilyaw-hotmail-com.iteye.com/blog/1836431](http://shmilyaw-hotmail-com.iteye.com/blog/1836431)
[http://www.ibm.com/developerworks/cn/java/j-lo-tree/index.html](http://www.ibm.com/developerworks/cn/java/j-lo-tree/index.html)
[http://blog.csdn.net/chenhuajie123/article/details/11951777](http://blog.csdn.net/chenhuajie123/article/details/11951777)
对这两种排序算法的性能比较如下(24核、64G内存,RHEL6.2):
在数据已经基本排好顺序的情况下,排序元素数目,在某个段内(大约是2万-20万),TreeSet更高效;其他数目下Collections.sort更高效;
在数据随机性较强的情况下,排序元素数目,在1万之内,相差不大,Collections.sort性能略高;在1万之外,80万之内,TreeSet性能明显高于Collections.sort;80万之外,Collection.sort性能更高;java.util.concurrent.ConcurrentSkipListSet这种基于“跳表”的线程安全的可排序类,在30万之内,性能高于Collection.sort,30万之外,性能低于Collection.sort,ConcurrentSkipListSet的排序性能总是低于TreeSet。
ConcurrentSkipListSet有一个平衡的树形索引机构没有的好处,就是在并发环境下其表现很好。
这里可以想象,在没有了解SkipList这种数据结构之前,如果要在并发环境下构造基于排序的索引结构,那么也就红黑树是一种比较好的选择了,但是它的平衡操作要求对整个树形结构的锁定,因此在并发环境下性能和伸缩性并不好。
代码演示如下:
~~~
import java.util.TreeSet;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Collections;
import java.util.Arrays;
import java.util.ListIterator;
import java.util.Random;
import java.util.Iterator;
class Test {
public static void main(String[] args) {
final int LEN = 300000;
final int SEED = 100000;
Random r = new Random();
System.out.println("---------------------------");
long b = System.currentTimeMillis();
TreeSet ts = new TreeSet(new Comparator(){
public int compare(Temp t1,Temp t2) {return t1.id-t2.id;}
});
for(int i=0;i aTemp = new ArrayList();
Iterator it = ts.iterator();
while(it.hasNext()) {
aTemp.add(it.next());
}
System.out.println(System.currentTimeMillis() - b);
System.out.println("---------------------------");
b = System.currentTimeMillis();
ArrayList al = new ArrayList();
for(int i=0;i() {
public int compare(Temp t1,Temp t2) {return t1.id-t2.id;}
});*/
Temp[] a = new Temp[al.size()];
al.toArray(a);
System.out.println(System.currentTimeMillis() - b);
Arrays.sort(a,new Comparator() {
public int compare(Temp t1,Temp t2) {return t1.id-t2.id;}
});
System.out.println(System.currentTimeMillis() - b);
ListIterator li = al.listIterator();
for(int i=0;i进行排序比较,性能很差。开始以为JDK对Entry做过优化,static/final之类,后来把对象也改成final,里面元素也改成final,发现性能依旧很差,完全不能解释,感觉无法理解。
后来,发现是两段代码不一致,使用Entry进行排序的代码有bug,导致排序的数据很少,所以显得性能好。。。。
所以,无端的臆测还是不要的,建立在JDK深入理解的基础上就好。
### 另外一个排序思路
比如,取出Top 20,也不一定要全部排序,可以只取前20个,经验证,小数据量时,性能也是非常高,大数据未验证。代码大致如下:
~~~
int n = 0;
double minScore = 100; //Top20中最小的积分
String minKey = ""; //最小值所在的Key
Map skuTop = new HashMap();
Set styles = new HashSet(); //过滤同款
for(String sku :tempSkuViewSkus.get(goodsUser.getKey())) {
boolean filter = false;
filter = filterSameStyle(sku,styles);
if(filter) continue;
//过滤不成功,直接continue
Set userSet = goodsUserView.get(sku);
if(userSet == null || userSet.size() == 0) continue;
//这一步,积分的计算,是最耗时的操作(性能瓶颈所在)
double score = mathTools.getJaccardSimilar(goodsUser.getValue(), userSet);
//前20个直接进入Map
if(n++ < ConstMongo.maxRecomNum) {
skuTop.put(sku, score);
if(score < minScore) {
minScore = score;
minKey = sku;
}
continue;
}
if(score <= minScore) continue;
//替换最小值
skuTop.remove(minKey);
skuTop.put(sku, score);
minScore = score;
minKey = sku;
for(Entry e : skuTop.entrySet()) {
if(e.getValue() < minScore) {
minScore = e.getValue();
minKey = e.getKey();
}
}
}
~~~
### 正则表达式
~~~
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Hello {
public static void main(String[] args)
{
Pattern pattern = Pattern.compile("正则表达式");
//Pattern pattern = Pattern.compile("Hello,正则表达式\\s[\\S]+");
Matcher matcher = pattern.matcher("正则表达式 Hello,正则表达式 World");
//替换第一个符合正则的数据
System.out.println(matcher.replaceFirst("Java"));
}
}
~~~
常用的开发语言都支持正则表达式,但是其对于正则支持的程度是不一样的。
Js正则:[http://msdn.microsoft.com/zh-cn/library/ae5bf541(VS.80).aspx](http://msdn.microsoft.com/zh-cn/library/ae5bf541(VS.80).aspx)
Python正则:[http://www.cnblogs.com/huxi/archive/2010/07/04/1771073.html](http://www.cnblogs.com/huxi/archive/2010/07/04/1771073.html)
Java正则:
[http://www.blogjava.net/xzclog/archive/2006/09/19/70603.html](http://www.blogjava.net/xzclog/archive/2006/09/19/70603.html)
[http://www.cnblogs.com/android-html5/archive/2012/06/02/2533924.html](http://www.cnblogs.com/android-html5/archive/2012/06/02/2533924.html)
# 并发相关类
如下章节的内容有简单使用演示:[http://blog.csdn.net/puma_dong/article/details/37597261#t5](http://blog.csdn.net/puma_dong/article/details/37597261#t5)
# 压缩解压类
如下章节的内容有简单使用演示:[http://blog.csdn.net/puma_dong/article/details/23018555#t20](http://blog.csdn.net/puma_dong/article/details/23018555#t20)
# 其他工具类
定时器、日期、时间、货币等
';
JDK框架简析–java.lang包中的基础类库、基础数据类型
最后更新于:2022-04-01 20:12:02
# 题记
JDK,Java Development Kit。
我们必须先认识到,JDK只是,仅仅是一套Java基础类库而已,是Sun公司开发的基础类库,仅此而已,JDK本身和我们自行书写总结的类库,从技术含量来说,还是在一个层级上,它们都是需要被编译成字节码,在JRE中运行的,JDK编译后的结果就是jre/lib下的rt.jar,我们学习使用它的目的是加深对Java的理解,提高我们的Java编码水平。
本系列所有文章基于的JDK版本都是1.7.16。
源码下载地址:[https://jdk7.java.net/source.html](https://jdk7.java.net/source.html)
# 本节内容
在本节中,简析java.lang包所包含的基础类库,当我们新写一个class时,这个package里面的class都是被默认导入的,所以我们不用写import java.lang.Integer这样的代码,我们依然使用Integer这个类,当然,如果你显示写了import java.lang.Integer也没有问题,不过,何必多此一举呢![大笑](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569e21abc5518.gif)
# Object
默认所有的类都继承自Object,Object没有Property,只有Method,其方法大都是native方法(也就是用其他更高效语言,一般是c实现好了的),
Object没有实现clone(),实现了hashCode(),哈希就是对象实例化后在堆内存的地址,用“==”比较两个对象,实际就是比较的内存地址是否是一个,也就是hashCode()是否相等,
默认情况下,对象的equals()方法和==符号是一样的,都是比较内存地址,但是有些对象重写了equals()方法,比如String,使其达到比较内容是否相同的效果
另外两个方法wait()和notify()是和多线程编程相关的,多线程里面synchronized实际就是加锁,默认是用this当锁,当然也可以用任何对象当锁,wait()上锁,线程阻塞,notify()开锁,收到这个通知的线程运行。以下代码示例:
~~~
class Test implements Runnable
{
private String name;
private Object prev;
private Object self;
public Test(String name,Object prev,Object self)
{
this.name=name;
this.prev = prev;
this.self = self;
}
@Override
public void run()
{
int count = 2;
while(count>0)
{
synchronized(prev)
{
synchronized(self)
{
System.out.println(name+":"+count);
count--;
self.notify(); //self解锁
}
try
{
prev.wait(); //prev上锁
} catch(InterruptedException e)
{
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws Exception
{
Object a = new Object();
Object b = new Object();
Object c = new Object();
Test ta = new Test("A",c,a);
Test tb = new Test("B",a,b);
Test tc = new Test("C",b,c);
new Thread(ta).start();
Thread.sleep(10);
new Thread(tb).start();
Thread.sleep(10);
new Thread(tc).start();
}
}
~~~
以上代码将顺序输出:A:2、B:2、C:2、A:1、B:1、C:1 。
Object类占用内存大小计算:[http://m.blog.csdn.net/blog/aaa1117a8w5s6d/8254922](http://m.blog.csdn.net/blog/aaa1117a8w5s6d/8254922)
Java如何实现Swap功能:[http://segmentfault.com/q/1010000000332606](http://segmentfault.com/q/1010000000332606)
# 构造函数和内部类
构造函数不能继承,是默认调用的,如果不显示用super指明的话,默然是调用的父类中没有参数的构造函数。
~~~
class P {
public P() {System.out.println("P");}
public P(String name) {System.out.println("P" + name);}
}
class S extends P {
public S(String name) {
super("pname"); //不过不指定的话,默认是调用的父类的没有参数的构造函数
System.out.println("name");
}
}
~~~
关于内部类,在应用编程中较少用到,但是JDK源码中大量使用,比如Map.Entry,ConcurrentHashMap,ReentrantLock等,enum本身也会被编译成static final修饰的内部类。
关于内部类的更多内容,可以参阅这篇文章:[http://android.blog.51cto.com/268543/384844/](http://android.blog.51cto.com/268543/384844/)
# Class和反射类
Java程序在运行时每个类都会对应一个Class对象,可以从Class对象中得到与类相关的信息,Class对象存储在方法区(又名Non-Heap,永久代),当我们运行Java程序时,如果加载的jar包非常多,大于指定的永久代内存大小时,则会报出PermGen错误,就是Class对象的总计大小,超过永久代内存的缘故。
Class类非常有用,在我们做类型转换时经常用到,比如以前用Thrift框架时,经常需要在Model类型的对象:Thrift对象和Java对象之间进行转换,需要手工书写大量模式化代码,于是,就写了个对象转换的工具,在Thrift对象和Java对象的Property名字一致的情况下,可以使用这个工具直接转换,其中大量使用了Class里面的方法和java.lang.reflect包的内容。
关于Calss类,方法众多,不详述。下面附上这个Thrift工具的代码。
~~~
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* Thrift前缀的对象和不含Thrift前缀的对象相互转换.
* 参考:
* http://blog.csdn.net/it___ladeng/article/details/7026524
* http://www.cnblogs.com/jqyp/archive/2012/03/29/2423112.html
* http://www.cnblogs.com/bingoidea/archive/2009/06/21/1507889.html
* http://java.ccidnet.com/art/3539/20070924/1222147_1.html
* http://blog.csdn.net/justinavril/article/details/2873664
*/
public class ThriftUtil {
public static final Integer THRIFT_PORT = 9177;
/**
* Thrift生成的类的实例和项目原来的类的实例相关转换并赋值
* 1.类的属性的名字必须完全相同
* 2.当前支持的类型仅包括:byte,short,int,long,double,String,Date,List
* 3.如果有Specified列,则此列为true才赋值,否则,不为NULL就赋值
* @param sourceObject
* @param targetClass
* @param toThrift:true代表把JavaObject转换成ThriftObject,false代表把ThriftObject转换成JavaObject,ThriftObject中含有Specified列
* @return
*/
public static Object convert(Object sourceObject,Class> targetClass,Boolean toThrift)
{
if(sourceObject==null)
{
return null;
}
//对于简单类型,不进行转换,直接返回
if(sourceObject.getClass().getName().startsWith("java.lang"))
{
return sourceObject;
}
Class> sourceClass = sourceObject.getClass();
Field[] sourceFields = sourceClass.getDeclaredFields();
Object targetObject = null;
try {
targetObject = targetClass.newInstance();
} catch (InstantiationException e1) {
e1.printStackTrace();
} catch (IllegalAccessException e1) {
e1.printStackTrace();
};
if(targetObject==null)
{
return null;
}
for(Field sourceField:sourceFields)
{
try {
//转换时过滤掉Thrift框架自动生成的对象
if(sourceField.getType().getName().startsWith("org.apache.thrift")
||sourceField.getName().substring(0,2).equals("__")
||("schemes,metaDataMap,serialVersionUID".indexOf(sourceField.getName())!=-1)
||(sourceField.getName().indexOf("_Fields")!=-1)
||(sourceField.getName().indexOf("Specified")!=-1)
)
{
continue;
}
//处理以DotNet敏感字符命名的属性,比如operator
String sourceFieldName = sourceField.getName();
if(sourceFieldName.equals("operator"))
{
sourceFieldName = "_operator";
} else {
if(sourceFieldName.equals("_operator"))
{
sourceFieldName = "operator";
}
}
//找出目标对象中同名的属性
Field targetField = targetClass.getDeclaredField(sourceFieldName);
sourceField.setAccessible(true);
targetField.setAccessible(true);
String sourceFieldSimpleName = sourceField.getType().getSimpleName().toLowerCase().replace("integer", "int");
String targetFieldSimpleName = targetField.getType().getSimpleName().toLowerCase().replace("integer", "int");
//如果两个对象同名的属性的类型完全一致:Boolean,String,以及5种数字类型:byte,short,int,long,double,以及List
if(targetFieldSimpleName.equals(sourceFieldSimpleName))
{
//对于简单类型,直接赋值
if("boolean,string,byte,short,int,long,double".indexOf(sourceFieldSimpleName)!=-1)
{
Object o = sourceField.get(sourceObject);
if(o != null)
{
targetField.set(targetObject, o);
//处理Specified列,或者根据Specified列对数值对象赋NULL值
try
{
if(toThrift)
{
Field targetSpecifiedField = targetClass.getDeclaredField(sourceFieldName+"Specified");
if(targetSpecifiedField != null)
{
targetSpecifiedField.setAccessible(true);
targetSpecifiedField.set(targetObject, true);
}
} else {
Field sourceSpecifiedField = sourceClass.getDeclaredField(sourceFieldName+"Specified");
if(sourceSpecifiedField != null
&& "B,S,B,I,L,D".indexOf(targetField.getType().getSimpleName().substring(0,1))!=-1
)
{
sourceSpecifiedField.setAccessible(true);
if(sourceSpecifiedField.getBoolean(sourceObject)==false)
{
targetField.set(targetObject, null);
}
}
}
} catch (NoSuchFieldException e) {
//吃掉NoSuchFieldException,达到效果:如果Specified列不存在,则所有的列都赋值
}
}
continue;
}
//对于List
if(sourceFieldSimpleName.equals("list"))
{
@SuppressWarnings("unchecked")
List
';
Spring实用功能–Profile、WebService、缓存、消息、ORM
最后更新于:2022-04-01 20:11:59
本篇介绍一些Spring与其他框架结合的实用功能,包括:Apache CXF WebService框架、Redis缓存、RabbitMQ消息、MyBatis框架。
另外对于Profile,也是Spring3.0开始新加的功能,对于开发测试环境、和生产环境分别采用不同的配置,有一定用处。
# Profile
Spring3.1新属性管理API:PropertySource、Environment、Profile。
Environment:环境,本身是一个PropertyResolver,但是提供了Profile特性,即可以根据环境得到相应数据(即激活不同的Profile,可以得到不同的属性数据,比如用于多环境场景的配置(正式机、测试机、开发机DataSource配置))。
Profile:剖面,只有激活的剖面的组件/配置才会注册到Spring容器,类似于maven中profile,Spring 3.1增加了一个在不同环境之间简单切换的profile概念, 可以在不修改任何文件的情况下让工程分别在 dev/test/production 等环境下运行。
为了减小部署维护,可以让工程会默认运行在dev模式,而测试环境和生产环境通过增加jvm参数激活 production的profile.
比如,对于如下的一个例子,由于测试环境和生产环境,连接数据库的方式不一样,可以有如下的解决办法:
1、首先ApplicationContext.xml中,xsi:schemaLocation需要引用3.2的xsd
2、ApplicationContext.xml配置如下:
~~~
~~~
3、开发环境配置,在web.xml中,如下配置:
~~~
spring.profiles.default
dev
~~~
4、生产环境配置
比如,对于Jboss,在bin/run.conf里面,增加启动参数:-Dspring.profiles.active=production
JAVA_OPTS="-Xms2048m -Xmx2048m -XX:MaxPermSize=1024m -Dorg.jboss.resolver.warning=true -Dsun.rmi.dgc.client.gcInterval=3600000 -Dsun.rmi.dgc.server.gcInterval=3600000 -Dsun.lang.ClassLoader.allowArraySyntax=true -Dorg.terracotta.quartz.skipUpdateCheck=true -Dspring.profiles.active=production"
以上是对于Web项目中如何利用profile的一种演示,如果是maven项目,也可以在maven打包时采用不同的profile,命令如下:
mvn clean package -Dmaven.test.skip=true -Ponline
通过P参数采用不同的profile,这样可以实现为开发、测试、生产打出不同的包。
不过,不推荐这种打包方式,应该是对于开发、测试、生产打出一样的包,然后根据机器本身的环境,来决定程序是按照那种环境来运行。
如果公司有根据环境变量的自动化部署方式(比如dev/test/stage/online),则这个profile是非常管用的。
# WebService
Java生态下的WebService框架非常多,apache cxf 是与spring结合最好的一种。配置步骤如下:
1、pom.xml,增加依赖:
~~~
org.apache.cxf
cxf-rt-frontend-jaxws
2.7.5
org.apache.cxf
cxf-rt-transports-http
2.7.5
~~~
2、web.xml,增加servlet:
~~~
cxf
org.apache.cxf.transport.servlet.CXFServlet
2
cxf
/*
~~~
3、resources目录下,增加applicationContext-cxf.xml,内容如下:
~~~
~~~
4、BasicWebService来的内容大致如下:
~~~
@WebService(name = "BasicWebService", serviceName = "BasicWebService", portName = "BasicWebServicePort", targetNamespace = "http://api.domain.com/ws")
@Service
public class BasicWebService {
@WebMethod
public void sendHtmlMail(@WebParam(name = "headName") String headName,
@WebParam(name = "sendHtml") String sendHtml) {
sendMail.doSendHtmlEmail(headName, sendHtml);
}
}
~~~
使用Apache CXF框架,是被Spring容器管理的,也就是说,BasicWebService本身可以设置@Service标记,也可以在BasicWebService中使用@Autowired进行注入。
而其他框架的WebService,比如Jboss直接通过Servlet方式暴露的WebService就不能这样,只能通过一个SpringContextHolder手动从Spring容器中拿,大致如下:
1、首先在web.xml中增加WebService类的servlet,如下:
~~~
BasicWebService
com.xx.BasisWebService
BasicWebService
/BasicWebService
~~~
2、BasicWebService的内容大致如下:
~~~
@WebService(name = "BasicWebService", serviceName = "BasicWebService", portName = "BasicWebServicePort", targetNamespace = "http://api.sina.com/ws")
public class BasicWebService {
//这是从Spring容器中拿对象,SpringContextHolder是一个实现了org.springframework.context.ApplicationContextAware的类
private ISystemConfigService systemConfigService = SpringContextHolder.getBean(ISystemConfigService.class);
@WebMethod
public String test(@WebParam(name = "inputpara") String inputpara) {
return inputpara + "_100";
}
}
~~~
# Redis
Spring可以简化调用Redis的操作,配置大致如下:
1、pom.xml增加依赖:
~~~
org.springframework.data
spring-data-redis
1.0.6.RELEASE
~~~
2、resources目录下,增加applicationContext-redis.xml,内容如下:
~~~
Spring-cache
~~~
3、缓存写入参考实现:
~~~
@Service
public class BrandBaseServiceImpl implements IBrandBaseService {
@Override
@Cacheable(value = CacheClientConstant.COMMODITY_BRAND_REDIS_CACHE, key = "'commodity:webservice:all:brand:list'")
public List getAllBrands() {
try
{
List brands = brandMapper.getAllBrands();
return brands;
} catch (Exception ex)
{
logger.error(ex.toString());
return null;
}
}
@Override
@Cacheable(value = CacheClientConstant.COMMODITY_BRAND_REDIS_CACHE, key = "'commodity:webservice:brand:no:'+#brandNo")
public Brand getBrandByNo(String brandNo) {
if (StringUtils.isBlank(brandNo))
return null;
return brandMapper.getBrandByNo(brandNo);
}
}
~~~
4、缓存清除参考实现:
~~~
@Service
public class RedisCacheUtil {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private JedisConnectionFactory jedisConnectionFactory;
@CacheEvict(value = CacheClientConstant.COMMODITY_CATEGORY_REDIS_CACHE, key = "'commodity:webservice:category:no:'+#categoryNo")
public void cleanCatCacheByNo(String categoryNo)
{
List keys = new ArrayList();
logger.info("[商品服务端]清理分类categoryNo:{}缓存,REDIS SERVER地址:{}", categoryNo, jedisConnectionFactory.getHostName() + ":" + jedisConnectionFactory.getPort());
if (StringUtils.hasText(categoryNo)) {
keys.add("commodity:webservice:category:no:" + categoryNo);
cleanAgain(keys);
}
}
@CacheEvict(value = CacheClientConstant.COMMODITY_SYSTEMCONFIG_REDIS_CACHE, allEntries = true)
public void cleanSystemConfigAll()
{
logger.info("[商品服务端]清楚SystemConfig缓存");
}
/**
* 考虑到主从延迟可能会导致缓存更新失效,延迟再清理一次缓存
* @param keys 需要清除缓存的KEY
*/
private void cleanAgain(List keys) {
if (CollectionUtils.isEmpty(keys)) {
return;
}
for (String key : keys) {
logger.info("清理缓存,KEY:{}", key);
redisTemplate.delete(key);
}
}
}
~~~
# RabbitMQ
Spring也可以简化使用RabbitMQ的操作,配置大致如下:
1、pom.xml增加依赖:
~~~
org.springframework.amqp
spring-amqp
${spring.amqp.version}
org.springframework.amqp
spring-rabbit
${spring.amqp.version}
~~~
2、发送消息代码例子:
~~~
@Service
public class MessageSendServiceImpl implements IMessageSendService {
private static final String EXCHANGE = "amq.topic";
@Autowired
private volatile RabbitTemplate rabbitTemplate;
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public Boolean sendMessage(String commodityNo) {
Commodity c = getCommodity(commodityNo);
// 发送rabbitMQ消息(topic)
rabbitTemplate.convertAndSend(EXCHANGE, "commodity.update.topic", c);
logger.info("发送消息成功(topic):商品编号:" + commodityNo);
return true;
}
}
~~~
3、resources目录下,增加applicationContext-rabbitmq.xml,用来配置接收消息,内容如下:
~~~
~~~
4、接收消息代码例子:
~~~
@Component
public class CommodityUpdateListener {
public void handleMessage(Commodity commodity) {
if(commodity==null)
{
logger.info("XXX");
return;
}
//处理逻辑
}
}
~~~
5、处理消息错误代码例子:
~~~
@Component
public class RabbitMqErrorHandler implements ErrorHandler {
private static Logger logger = LoggerFactory.getLogger(RabbitMqErrorHandler.class);
@Override
public void handleError(Throwable t) {
logger.error("Receive rabbitmq message error:{}", t);
}
}
~~~
# MyBatis
Spring可以大大简化使用MyBatis这种ORM框架,定义出接口和Mapper文件之后,Spring可以自动帮我们生成实现类。我曾经在DotNet框架下使用过MyBatis.Net,所有的Mapper的实现类都需要手工写代码,而Spring帮我节省了很多编码工作量。
大致配置步骤如下:
1、pom.xml增加依赖:
~~~
org.mybatis
mybatis-spring
1.1.1
org.mybatis.caches
mybatis-ehcache
1.0.1
~~~
2、resources目录下,applicationContext.xml中,一般放置关于mybatis的配置,内容如下:
~~~
Spring公共配置
~~~
3、定义接口,及在src/main/resource对应接口的包路径下定义同名的xml配置文件即可。
Spring初始化完毕后,会自动帮我们生成Mapper的实现类。
';
Java之旅–定时任务(Timer、Quartz、Spring、LinuxCron)
最后更新于:2022-04-01 20:11:57
在Java中,实现定时任务有多种方式,本文介绍4种,Timer和TimerTask、Spring、QuartZ、Linux Cron。
以上4种实现定时任务的方式,Timer是最简单的,不需要任何框架,仅仅JDK就可以,缺点是仅仅是个时间间隔的定时器,调度简单;Spring和QuartZ都支持cron,功能都很强大,Spring的优点是稍微简单一点,QuartZ的优点是没有Spring也可使用;Linux Cron是个操作系统级别的定时任务,适用于所有操作系统支持的语言,缺点是精度只能到达分钟级别。
# Timer和TimerTask
关于Timer定时器的实现原理,如果我们看过JDK源码,就会发现,是使用的Object.wait(timeout),来进行的线程阻塞,timeout是根据下次执行实际和当前实际之差来计算。实际上,这可以归结为一个多线程协作(协作都是在互斥下的协作)问题。
在java.util.concurrent中,有个ScheduledThreadPoolExecutor,也可以完全实现定时任务的功能。
而其他的框架,无非是功能的增强,特性更多,更好用,都是在基础的java之上的包装。
代码示例如下:
~~~
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class TimerTest extends TimerTask
{
private Timer timer;
public static void main(String[] args)
{
TimerTest timerTest= new TimerTest();
timerTest.timer = new Timer();
//立刻开始执行timerTest任务,只执行一次
timerTest.timer.schedule(timerTest,new Date());
//立刻开始执行timerTest任务,执行完本次任务后,隔2秒再执行一次
//timerTest.timer.schedule(timerTest,new Date(),2000);
//一秒钟后开始执行timerTest任务,只执行一次
//timerTest.timer.schedule(timerTest,1000);
//一秒钟后开始执行timerTest任务,执行完本次任务后,隔2秒再执行一次
//timerTest.timer.schedule(timerTest,1000,2000);
//立刻开始执行timerTest任务,每隔2秒执行一次
//timerTest.timer.scheduleAtFixedRate(timerTest,new Date(),2000);
//一秒钟后开始执行timerTest任务,每隔2秒执行一次
//timerTest.timer.scheduleAtFixedRate(timerTest,1000,2000);
try
{
Thread.sleep(10000);
} catch (InterruptedException e)
{
e.printStackTrace();
}
//结束任务执行,程序终止
timerTest.timer.cancel();
//结束任务执行,程序并不终止,因为线程是JVM级别的
//timerTest.cancel();
}
@Override
public void run()
{
System.out.println("Task is running!");
}
}
~~~
# 使用spring @Scheduled注解执行定时任务
这种方式非常简单,却能使用cron完成和QuartZ一样的功能,值得推荐一下。
**ApplicationContext.xml:**
beans根节点增加内容:xmlns:task="http://www.springframework.org/schema/task"
beans根节点中,xsi:schemaLocation属性下增加:
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.1.xsd
**实现类:**
~~~
@Component //import org.springframework.stereotype.Component;
public class MyTestServiceImpl implements IMyTestService {
@Scheduled(cron="0/5 * * * * ? ") //每5秒执行一次
@Override
public void myTest(){
System.out.println("进入测试");
}
}
~~~
**注意几点:**
spring的@Scheduled注解 需要写在实现上;
定时器的任务方法不能有返回值;
实现类上要有组件的注解@Component,@Service,@Repository
# QuartZ
### QuartZ With Spring
### applicationContext-schedule.xml
~~~
5
~~~
### 实现类
~~~
package xx.schedule;
@Component
public class StartThrift {
/**
* 调度入口
*/
public void execute() {
// to do something
}
}
~~~
### QuartZ No Spring
**使用的QuartZ jar是1.8.5,如下:**
~~~
org.quartz-scheduler
quartz
1.8.5
~~~
**调用类实现代码如下:**
~~~
import org.quartz.CronExpression;
import org.quartz.CronTrigger;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.impl.StdSchedulerFactory;
public class InvokeStatSchedule {
public void start() throws SchedulerException
{
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
//InvokeStatJob是实现了org.quartz.Job的类
JobDetail jobDetail = new JobDetail("jobDetail", "jobDetailGroup", InvokeStatJob.class);
CronTrigger cronTrigger = new CronTrigger("cronTrigger", "triggerGroup");
try {
CronExpression cexp = new CronExpression("0 0 * * * ?");
cronTrigger.setCronExpression(cexp);
} catch (Exception e) {
e.printStackTrace();
}
scheduler.scheduleJob(jobDetail, cronTrigger);
scheduler.start();
}
}
~~~
**定时任务类代码如下:**
~~~
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class InvokeStatJob implements Job {
@Override
public void execute(JobExecutionContext arg0) throws JobExecutionException {
//...要定时操作的内容
}
}
~~~
# Linux Cron
这其实也是一种非常普遍的实现定时任务的方式,实际是操作系统的定时任务。Linux Cron只能到达分钟级,到不了秒级别。
一般我们是设置定时执行一个sh脚本,在脚本里面写一些控制代码,例如,有如下的脚本:
3,33 * * * * /usr/local/log_parser/run_log_parser.sh &
**run_log_parser.sh的内容大致如下:**
~~~
#!/bin/sh
log_parser_dir=/usr/local/log_parser
tmp_file=/usr/local/run_parser_tmp.txt
parser_log=/usr/local/access_parser.log
tmpDir=/data/applogs/access_logs_bp
date >> "$parser_log"
if [! -f "$tmp_file"]; then
echo '访问日志解析正在进行,尚未完成' >> "$parser_log"
echo '' >> "$parser_log"
else
echo '开始解析访问日志' >> "$parser_log"
touch "$tmp_file"
cd "$log_parser_dir"
python access_log_parser.py >> "$parser_log"
rm "$tmp_file"
echo '解析访问日志完成' >> "$parser_log"
echo '' >> "$parser_log"
cd "$tmpDir"
gzip gzip WEB0*
mv *.gz gz/
echo '压缩备份日志及移动到压缩目录成功' >> "$parser_log"
fi
~~~
';
《Spring3.X企业应用开发实战》学习笔记–SpringMVC
最后更新于:2022-04-01 20:11:55
本篇是《Spring3.X企业应用开发实战》,陈雄华 林开雄著,电子工业出版社,2012.2出版”的学习笔记的第三篇,关于SpringMVC。
Spring MVC 3.0和早期版本相比拥有了一个质的飞跃,全面支持REST风格的WEB编程、完全注解驱动、处理方法签名非常灵活、处理方法不依赖于Servlet API等。
由于Spring MVC框架在后头做了非常多的隐性工作,所以想深入掌握Spring MVC 3.0并非易事,本章我们在学习Spring MVC的各项功能时,还深入其内部了解其后台的运作机理,只有了解这些机理后,才能更好地使用这个当前最先进的MVC框架。
服务器启动时加载配置文件的顺序:web.xml applicationContext.xml springmvc-servlet.xml。
# Spring MVC概述
Spring MVC通过一套MVC注解,让POJO成为处理请求的控制器,无须实现任何接口,同时,Spring MVC还支持REST风格的URL请求:注解驱动及REST风格的Spring MVC是Spring 3.0最出彩的功能之一。
此外,Spring MVC在数据绑定、视图解析、本地化处理及静态资源处理上都有不俗的表现。
# DispatcherServlet
Spring MVC框架围绕DispatcherServlet这个核心展开,DispatcherServlet是Spring MVC的总导演、总策划,它负责截获请求并将其分派给相应的处理器处理。
在web.xml中配置DispatcherServlet,截获特定的URL请求:
~~~
springmvc
org.springframework.web.servlet.DispatcherServlet
1
springmvc
*.do
~~~
**Spring如何将上下文中的SpringMVC组件装配到DispatcherServlet中?**
WebApplicationContext初始化后,此时Spring上下文中的Bean已经初始化完毕,开始执行DispatcherServlet的initStrategies()方法,代码如下:
~~~
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(); //1.初始化上传文件解析器
initLocalResolver();//2.初始化本地化解析器
initThemeResolver();//3.初始化主题解析器
initHandlerMappings();//4.初始化处理器映射器
initHandlerAdapters();//5.初始化处理器适配器
initHandlerExceptionResolver();//6.初始化处理器异常解析器
initRequestToViewNameTranslator();//7.初始化请求道试图名解析器
initViewResolvers();//8.初始化试图解析器
}
~~~
该方法的工作是通过反射机制查找并装配Spring容器中显式自定义的组件Bean,如果找不到,则装配默认的组件实例,默认的组件实例在spring-webmvc-版本.RELEASE.jar包中,org/springframework/web/servlet路径下的DispatcherServlet.properties中间中定义。
其中文件上传没有默认的解析器,如果需要,自行配置,比如:
~~~
~~~
# 注解驱动的控制器
在POJO类定义处标注@Controller,再通过 扫描相应的类包,即可使POJO成为一个能处理HTTP请求的控制器。
@RequestMapping:不但支持标注的URL,还支持Ant风格(即?、*和**的字符)和带有{xxx}占位符的URL。带占位符的URL是Spring 3.0新增的功能,该功能在Spring MVC向REST目标挺进的发展过程中具有里程碑的意义。
通过@PathVariable可以将URL中的占位符参数绑定到控制器处理方法的入参中。
# 如何设置方法入参以绑定请求信息
其他参考:[http://blog.csdn.net/kobejayandy/article/details/12690161](http://blog.csdn.net/kobejayandy/article/details/12690161)
使用命令/表单对象绑定请求参数值:
这是最常用的,即入参就是一个POJO。Spring MVC会按照请求参数名和对象属性名进行匹配,自动为对象填充属性值,支持级联的属性名,例如:
@RequestMapping("/handleInsert)
public String handleInsert(User user,String operator)…
使用@RequestParam、@CookieValue、@RequestHeader,分别获取请求、Cookie、请求报文头的传入值,他们都有3个参数:
value:参数名;
required:是否必需,默认为true,表示请求中必须包含对应的参数名,如果不存在将抛出异常;
defaultValue:默认参数名,设置该参数时,自动将required设为false。极少情况需要使用该参数,也不推荐使用该参数
使用Servlet API对象作为入参:
public String handleInsert(HttpServletRequest request,HttpServletResponse response)...
另外Spring MVC在 org.springframework.web.context.request包中定义了若干个可代理Servlet原生API类的接口,如WebRequest和NativeWebRequest,他们也允许作为代理类的入参,通过这些代理类可访问请求对象的任何信息。
使用IO对象作为入参:
Servlet的ServletRequest拥有getInputStream()和getReader()方法,可以通过他们读取请求的信息。相应Servlet的ServletResponse拥有getOutputStream()和getWriter()方法,可以通过它们输出响应信息。
Spring MVC允许控制器的处理方法使用java.io.InputStream/java.io.Reader及java.io.OutputStream/java.io.Writer作为方法的入参,Spring MVC将获取ServletRequest的InputStream/Reader或ServletResponse的OutputStream/Writer,然后传递给控制器的处理方法。
使用其他类型的参数:
比如java.util.Locale、java.security.Principal,可以通过Servlet的HttpServletRequest的getLocale()及getUserPrincipal()得到相应的值。
# HttpMessageConverter进行消息对象转换
HttpMessageConverter是Spring 3.0新添加的一个重要接口,它负责将请求信息转换为一个对象(类型为T),将对象(类型为T)输出为响应信息。
DispatcherServlet默认已经安装了AnnotationMethodHandlerAdapter作为HandlerAdapter的组件实现类,HttpMessageConverter即由AnnotationMethodHandlerAdapter使用,
将请求信息转换为对象,或将对象转换为响应信息。
Spring为HttpMessageConverter提供了众多的实现类,他们组成了一个功能强大、用途广泛的HttpMessageConverter家族。
AnnotationMethodHandlerAdapter默认一级装配了如下的HttpMessageConverter:
StringHttpMessageConverter
ByteArrayHttpMessageConverter
SourceHttpMessageConverter
XmlAwareFormHttpMessageConverter
如果需要装配其他类型的HttpMessageConverter,可在Spring的Web容器上下文中自行定义一个AnnotationMethodHandlerAdapter,如果在Spring容器中显式定义了一个
AnnotationMethodHandlerAdapter,则Spring MVC将使用它覆盖默认的AnnotationMethodHandlerAdapter。
如何使用HttpMessageConverter将请求信息转换并绑定到处理方法的入参中呢?
Spring MVC提供了两种途径:
1.使用@RequestBody/@ResponseBody对处理方法进行标注
2.使用HttpEntity/ResponseEntity作为处理方法的入参或返回值
**关于HttpMessageConverter,得出如下几条结论:**
1.当控制器处理方法使用到@RequestBody/@ResponseBody 或 HttpEntity/ResponseEntity时,Spring MVC才使用注册的HttpMessageConverter对请求/响应消息进行处理
2.当控制器处理方法使用到@RequestBody/@ResponseBody 或 HttpEntity/ResponseEntity时,Spring首先根据请求头或响应头的Accept属性选择匹配的HttpMessageConverter,进而根据参数类型或反向类型的过滤得到匹配的HttpMessageConverter,如果找不到可用的HttpMessageConverter将报错
3.@RequestBody/@ResponseBody不需要成对出现,如果方法入参使用到@RequestBody,Spring MVC选择匹配的HttpMessageConverter将请求消息转换并绑定到该入参中。如果处理方法标注了
4.@ResponseBody,Spring MVC选择匹配的HttpMessageConverter将方法返回值转换并输出相应消息。
5.HttpEntity/ResponseEntity的功用和@RequestBody/@ResponseBody相似
# **RestTemplate**
RestTemplate是Spring 3.0新增的模板类,在客户端程序中可使用该类调用Web服务端的服务,它支持REST风格的URL。此外,它项AnnotationMethodHandlerAdapter一样拥有一个httpMessageConverter的注册表,它默认注册了5个HttpMessageConverter。
# spring-servlet.xml
spring-servlet.xml配置(WEB-INFO目录下):
~~~
~~~
# 处理模型数据
对于MVC框架来说模型数据是最重要的,因为控制(C)是为了产生模型数据(M),而视图(V)则是为了渲染模型数据。
如何将模型数据暴漏给视图是Spring MVC框架的一项重要工作,Spring MVC提供了多种途径输出模型数据,介绍如下:
1、ModelAndView:处理方法返回值为ModelAndView时,方法体即可通过该对象添加模型数据
2、@ModelAttribute:方法入参标注该注解后,入参的对象就会放到数据模型中
3、Map及Model:入参为org.springframework.ui.Model、org.springframework.ui.ModelMap或java.util.Map时,处理方法返回时,Map中的数据会自动添加到模型中
4、@SessionAttribute:将模型中的某个属性暂存到HttpSession中,以便多个请求之间可以共享这个属性
# 处理方法的数据绑定
我们知道Spring 会根据请求方法签名的不同,将请求信息中的信息以一定的方式转换并绑定到请求方法的入参中。
在请求消息到达真正调用处理方法的这一段时间内,Spring还完成了很多工作,包括数据转换、数据格式化及数据校验等。这一节使用较少,临时不去研究。
# 视图和视图解析器
请求处理方法执行完成后,最终返回一个ModelAndView对象。
对于那些返回String、View或ModelMap等类型的处理方法,Spring MVC也会在内部将它们装配成一个ModelAndView对象,它包含了视图逻辑名和模型对象的信息。
Spring MVC借助视图解析器(ViewResolver)得到最终的视图对象(View),这可能是我们常见的JSP视图,也可能是一个基于FreeMarker、Velocity模板技术的视图,还可能是PDF、Excel、XML、JSON等各种形式的视图。
对于最终究竟采取何种视图对象对模型对象进行渲染,Controller并不关心,Controller的工作重点聚集在生产模型数据的工作上,从而实现MVC的充分解耦。
**FreeMarker配置:**
~~~
0
zh_CN
UTF-8
UTF-8
rethrow
#.##
yyyy-MM-dd
HH:mm:ss
yyyy-MM-dd HH:mm:ss
~~~
针对在实际开发过程中使用到的FreeMarker语法,JSTL标签等,可以逐渐熟悉和记录。
FreeMarker和Velocity是除JSP之外被使用最多的页面模板技术。页面模板编写好页面结构,模板页面中使用一些特殊的变量标识符绑定Java对象的动态数据。
FreeMarker是一个模板引擎,一个基于模板生产文本输出的通用工具,FreeMarker可以基于模板产生HTML、XML、JAVA源代码等多种类型的输出内容。虽然FreeMarker具有一些编程能力,单通常由Java程序准备数据,FreeMarker仅负责基于模板对模型数据进行渲染的工作。
# 关于Web开发
要想成为一名Web开发高手,不能仅满足于知道如何做,更要抛开现象探究本质。
笔者认为要成为一名Web开发高手,必须熟练了解如下内容:
1、每次请求和响应的背后究竟发生了哪些步骤,客户端服务器是如何通过HTTP请求报文进行交互的;
2、深刻掌握MIME类型的知识;
3、深刻掌握HTTP响应状态码的知识,如404、303究竟代表什么。
**帖子:**
1、http长连接:[http://www.blogjava.net/xjacker/articles/334709.html](http://www.blogjava.net/xjacker/articles/334709.html)
';
《Spring3.X企业应用开发实战》学习笔记–DAO和事务
最后更新于:2022-04-01 20:11:53
本篇是《Spring3.X企业应用开发实战》,陈雄华 林开雄著,电子工业出版社,2012.2出版”的学习笔记的第二篇,关于DAO和事务。
本篇从DAO操作,以及事务处理的基本知识谈起,介绍事务本身,以及Spring如何通过注解实现事务。
# DAO
近几年持久化技术领域异常喧嚣,各种框架如雨后春笋般地冒出,Sun也连接不断的颁布了几个持久化规范。
Spring对多个持久化技术提供了持久化支持,包括Hibernate,iBatis,JDO,JPA,TopLink,另外,还通过Spring JDBC框架对JDBC API进行简化。
Spring面向DAO制定了一个通用的异常体系,屏蔽具体持久化技术的异常,使业务层和具体的持久化技术达到解耦。
此外,Spring提供了模板类简化各种持久化技术的使用。
通用的异常体系及模板类是Spring整合各种五花八门持久化技术的不二法门,Spring不但借此实现了对多种持久化技术的整合,还可以不费吹灰之力整合潜在的各种持久化框架,体现了“开-闭原则”的经典应用。
Spring支持目前大多数常用的数据持久化技术,Spring定义了一套面向DAO层的异常体系,并为各种支持的持久化技术提供了异常转换器。这里,我们在设计DAO接口时,就可以抛开具体的实现技术,定义统一的接口。
不管采用何种持久化技术,访问数据的流程是相对固定的。Spring将数据访问流程划分为固定和变化两部分,并以模板的方式定义好流程,用回调接口将变化的部分开放出来,留给开发者自行定义。这样,我们仅需提供业务相关的逻辑就可以完成整体的数据访问了。
### 4种配置数据源的办法
1、DHCP数据源
org.apache.commons.dbcp.BasicDataSource,需要commons-dhcp.jar和commons-pool.jar
对于MySQL,如果数据源配置不当,将可能发生经典的“8小时问题”:
原因是MySQL在默认情况下,如果发现一个连接的空闲时间超过8小时,将会在数据库端自动关闭这个连接。而数据源并不知道这个连接以及被数据库关闭了,当它将这个无用的连接返回给某个DAO时,DAO就会报无法获取Connection的异常。
如果采用DHCP的默认配置,由于testOnBorrow属性的默认值为true,数据源将在连接交给DAO前,检测这个链接是否是好的,如果连接有问题(在数据库端被关闭),则会取一个其他的连接给DAO。所以,并不会有“8小时问题”。如果每次将连接交给DAO时都检测连接有效性,在高并发的应用中将带来性能的问题,因为它会需要更多的数据库访问请求。
一种推荐的高效方式是:将testOnBorrow设置为false,而将testWhileIdle设置为true,再设置好timeBetweenEvictionRunsMillis值。这样,DBCP将通过一个后台线程定时对空闲连接进行检测,当发现无用的连接时,就会将他们清除掉。只要这个值小于8小时,就可以避免“8小时问题”。
当然, MySQL本身可以通过调整interactive-timeout(以秒为单位)配置参数,更改空闲连接的过期时间。所以,设置这个timeBetweenEvictionRunsMillis值时,必须首先获知MySQL空闲连接的最大过期时间。
**8小时问题的其他参考:**[http://blog.csdn.net/wangfayinn/article/details/24623575](http://blog.csdn.net/wangfayinn/article/details/24623575)
2.C3P0数据源,它在lib目录中与Hibernate一起发布。
很多生产项目使用这个数据源的连接池,推荐。
3、Spring的数据源实现类
org.springframework.jdbc.datasource.DriverManagerDataSource
这个数据源是没有连接池的,比较适合单元测试或简单的独立应用使用
4、使用应用服务器的JNDI数据源
org.springframework.jndi.JndiObjectFactoryBean
一般是在开发过程中使用Spring的数据源实现类就可以了,有公司也在测试生产环境中使用JNDI数据源。
不过这种方式,会对容器做一些改动,不是一种最好的方式,个人推荐C3P0。
### 一个JBOSS的JNDI数据源文件例子
一个JBOSS的JNDI数据源文件mysql-ds.xml,需要拷贝至{jboss_home}/server/{default}/deploy/,内容如下:
~~~
MySqlDSSlave1
jdbc:mysql://ip:3306/dbname?useUnicode=true&characterEncoding=utf-8
com.mysql.jdbc.Driver
root
root
org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter
5
150
org.jboss.resource.adapter.jdbc.vendor.MySQLValidConnectionChecker
select 1
mySQL
MySqlDS_JDBC
jdbc:mysql://ip:3306/dbname?useUnicode=true&characterEncoding=utf-8
com.mysql.jdbc.Driver
root
root
org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter
5
150
org.jboss.resource.adapter.jdbc.vendor.MySQLValidConnectionChecker
select 1
mySQL
~~~
### Spring Data
Spring有一个比较活跃的子项目,名为SpringData,这个项目的目标主要是让访问No-SQL更加方便,并支持map-reduce框架和云计算的数据服务。
第二个目标就是支持基于关系型数据库的数据服务,如Oracle RAC。对于拥有海量数据的项目,可以用SpringData这样的项目来简化项目的开发,SpringData会让数据的访问变得更加方便。SpringData由多个子项目组成,支持CouchDB、MongoDB、Neo4J、Hadoop、Hbase、Cassandra等,有兴趣的读者可以关注:http://www.springsource.com/spring-data。
# 事务
### 何为数据库事务
数据库事务有严格的定义,它必须同时满足4个特性:原子性(Atomic)、一致性(Consistency)、隔离性(Isolation)和持久性(Durabiliy),简称为ACID。
原子性:表示组成一个事务的多个数据库操作是一个不可分割的原子单元,只有所有的操作执行成功,整个事务才提交,事务中任何一个数据库操作失败,已经执行的任何操作都必须撤销,让数据库返回到初始状态。
一致性:事务操作成功后,数据库所处的状态和它的业务规则是一致的,即数据不会被破坏。如从A账户转账100元到B账户,不管操作成功与否,A和B的存款总额是不变的。
隔离性:在并发数据操作时,不同的事务拥有各自的数据空间,他们的操作不会对对方产生干扰。准确地说,并非要求做到完全无干扰,数据库规定了各种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性越好,但并发性越弱。
持久性:一旦数据提交成功后,事务中所有的数据操作都必须被持久化到数据库中,即时提交事务后,数据库马上崩溃,在数据库重启时,也必须能保证能够通过某种机制恢复数据。
数据库管理系统一般采用重执行日志保证原子性、一致性和持久性,重执行日志记录了数据库变化的每一个动作,数据库在一个事务中执行一部分操作后发生错误退出,数据即可以根据重执行日志撤销已经执行的操作。此外,对于已经提交的事务,即使数据库崩溃,在重启数据库时也能够根据日志对尚未持久化的数据进行相应的重执行操作。
和Java程序采用对象锁机制进行线程同步类似,数据库管理系统采用数据库锁机制保证事务的隔离性。当多个事务试图对相同的数据进行操作时,只有持有锁的事务才能操作数据,直到前一个事务完成后,后面的事务才有机会对数据执行操作。
### 数据并发问题
一个数据库拥有多个访问客户端,这些客户端都可以并发方式访问数据库。
数据库中相同数据可能同时被多个事务访问,如果没有采取必要的隔离措施,就会导致各种并发问题,破坏数据的完整性。
这些问题可以归结为5类,包括3类数据读问题(脏读、不可重复读、幻象读)以及2类数据更新问题(第一类丢失更新和第二类丢失更新)。
参考:[http://www.cnblogs.com/Sun_Blue_Sky/articles/2139996.html](http://www.cnblogs.com/Sun_Blue_Sky/articles/2139996.html)
不可重复读和幻象读,前者是指读到了已经提交事务的更改数据,后者是指读到了其他已经提交的事务的新增数据,为了避免这两种情况,采取的多策是不同的,对于前者,需要锁行,对于后者,需要锁表。
第一类丢失更新:A事务撤销时,把已经提交的B事务的更新覆盖了。
第二类丢失跟新:A事务提交时,把已经提交的B事务的更新覆盖了。
### 数据隔离级别
| 隔离级别 | 脏读 | 不可重复读 | 幻象读 | 第一类丢失更新 | 第二类丢失更新 |
|-----|-----|-----|-----|-----|-----|
| READ UNCOMMITED | 允许 | 允许 | 允许 | 不允许 | 允许 |
| READ COMMITED | 不允许 | 允许 | 允许 | 不允许 | 允许 |
| REPEATABLE READ | 不允许 | 不允许 | 允许 | 不允许 | 不允许 |
| SERIALIZABLE | 不允许 | 不允许 | 不允许 | 不允许 | 不允许 |
SqlServer2008R2的默认隔离级别是“READ COMMITED”,MySQL的默认隔离级别是“REPEATABLE READ”。
### Spring 事务
Spring声明式事务是Spring最核心、最常用的功能。
由于Spring通过IOC和AOP的功能非常透明地实现了声明式事务的功能,一般的开发者基本上无须了解Spring声明式事务的内部细节,仅需要懂得如何配置就可以了。
但是在实际应用开发过程中,Spring的这种透明地高阶封装在带来便利的同时,也给我们带来了困惑。
就像通过流言传播的消息,最终听众已经不清楚事情的真相了,而这对于应用开发开说是很危险的。
剖析实际应用中给开发者造成困惑的各种观点,通过分析Spring事务管理的内部运作机制将真相还原出来,真相如下:
在没有事务管理的情况下,DAO照样可以顺利进行数据操作;
将应用分出Web、Service及DAO层只是一种参考的开发模式,并非是事务管理工作的前提条件;
Spring通过事务传播机制可以很好地应对事务方法嵌套调用的情况,开发者无需为了事务管理而可以改变服务方法的设计;
由于单实例的对象不存在线程安全问题,所以经过事务管理增强的单实例Bean可以很好地工作在多线程环境下;
混合使用多个数据访问技术框架的最佳组合是一个ORM技术框架(如Hibernate或JPA等)+一个JDBC技术框架(如Spring JDBC或iBatis)。直接使用ORM技术框架对应的事务管理器就可以了,但必须考虑ORM缓存同步的问题。
Spring AOP增强有两个方案:其一为基于接口的动态代理,其二为基于CGLib动态生成子类的代理。由于Java语法的特性,有些特殊方法不能被Spring AOP代理,所以也就无法享受AOP织入带来的事务增强;
使用Spring JDBC时如果直接获取Connection,可能会造成连接泄露。为降低连接泄露的可能性,尽量使用DataSourceUtils获取数据连接。也可以对数据源进行代理,以便使数据源具有感知事务上下文的能力。
我们描述了Spring JDBC防止连接泄露的解决方案,Spring同样把这种解决方案平滑应用到其他的数据访问技术框架中。
### DataSourceUtils
Spring提供了一个能从当前事务上下文中获取绑定的数据连接的工具类,那就是DataSourceUtils。
Spring强调必须使用DataSourceUtils工具类获取数据连接,Spring的JdbcTemplate内部也是通过DataSourceUtils来获取连接的。
是否使用DataSourceUtils获取数据连接就可以高枕无忧了呢?理想很美好,但现实很残酷:如果DataSourceUtils在没有事务上下文的方法中使用getConnection()获取连接,依然会造成数据连接泄露!
而通过分析JdbcTemplate模板的实现,发现JdbcTemplate严谨的获取连接及释放连接的模式化流程保证了JdbcTemplate对数据连接泄露问题的免疫性。
所以,如果有可能,请尽量使用JdbcTemplate、HibernateTemplate等这些模板进行数据访问操作,避免直接获取数据连接的操作。
如果不得已要显式获取数据连接,除了使用DataSourceUtils获取事务上下文绑定的连接之外,还可以通过TransactionAwareDataSourceProxy对数据源进行代理。数据源对象被代理后就具有了事务上下文的感知的能力。
### 不同持久化技术对应的事务管理器实现类
| org.springframework.orm.jpa.JpaTransactionManager | 使用JPA进行持久化时,使用该事务管理器 |
|-----|-----|
| org.springframework.orm.hibernate3.HibernateTransactionManager | 使用Hibernate3.0版本进行持久化时,使用该事务管理器 |
| org.springframework.jdbc.datasource.DataSourceTransactionManager | 使用Spring JDBC或iBatis等基于DataSource数据源的持久化技术时,使用该事务管理器 |
| org.springframework.orm.jdo.JdoTransactionManager | 使用JDO进行持久化时,使用该事务管理器 |
| org.springframework.transaction.jta.JtaTransactionManager | 具有多个数据源的全局事务使用该事务管理器(不管采用何种持久化技术),如果希望在Java EE容器里使用JTA,我们将通过JNDI和Spring的JtaTransactionManager获取一个容器管理的DataSource。 |
大致来说,Spring支持两类事务,一种是本地连接事务(使用DataSourceTransactionManager),一种是JTA事务(使用JtaTransactionManager)。
JTA事务实现相对较好理解,在执行实际类的符合模式的方法时,代理类通过在连接点前后插入预处理过程(开始事务)和后处理过程(commit或rollbak)即可。因为JTA事务支持两阶段提交所以在代码中启动多少个连接(不同的connection)都能保证事务最终提交或者回滚。
作为基于DataSource的事务管理,实际上数据库连接Connection是最为核心的资源,事务的管理控制最终都会由Connection的相关方法来完成。关于其实现原理,不做深入探究。
### 事务传播行为类型
| PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务,加入到这个事务中。这是最常见的选择。 |
|-----|-----|
| PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
| PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 |
| PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
| PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
| PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
| PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
### ApplicationContext.xml里面配置示例
~~~
~~~
以上是用@Transactional注解方式需要的配置节点,如果不用@Transactional注解方式配置事务,也可以用XML表达式配置事务,如下:
~~~
~~~
~~~
~~~
~~~
~~~
~~~
~~~
### 使用注解配置声明式事务:@Transactional
使用基于@Transactional注解的配置和基于XML的配置方式一样,它拥有一组普适性很强的默认事务属性,我们往往可以直接使用这些默认的属性就可以了,默认值如下:
| 事务传播行为 | PROPAGATION_REQUIRED |
|-----|-----|
| 事务隔离级别 | SOLATION_DEFAULT,表示采用数据库本身的隔离级别 |
| 读写事务属性 | 读写事务 |
| 超时时间 | 依赖于底层的事务系统的默认值 |
| 回滚设置 | 任何运行期异常引发回滚(unchecked),任何检查型异常不会引发回滚 |
**@Transactional注解属性说明:**
****
| propagation | 事务传播行为,通过以下枚举类提供合法值:org.springframework.transaction.annotation.Propagation,例如:@Transactional(propagation=Propagation.REQUIRES_NEW) |
|-----|-----|
| isolation | 事务隔离级别,通过以下枚举类提供合法值:org.springframework.transaction.annotation.Isolation,例如:@Transactional(isolation=Isolation.READ_COMMITTED) |
| readOnly | 事务读写性,boolean型,例如:@Transactional(readOnly=true) |
| timeout | 超时时间,int型,例如:@Transactional(timeout=10) |
| rollbackFor | 一组异常类,遇到时进行回滚,类型为:Class Extends Throwable>[],默认为{}。例如:@Transactional(rollbackFor={SQLException.class}),多个异常之间可用逗号分隔。 |
| rollbackForClassName | 一组异常类名,遇到时进行回滚,类型为String[],默认值为{}。例如:@Transactional(rollbackForClassName={Exception"}) |
| noRollbackFor | 和rollbackFor相对。 |
| noRollbackForClassName | 和rollbackForClassName相对。 |
@Transactional注解可以被应用于接口定义和接口方法、类定义和类的public方法上。
Spring建议在具体业务类上使用@Transactional注解。
方法处的注解会覆盖类定义处的注解,可以使用不同的事务管理器,例如:@Transactional("forum"),使用名为forum的事务管理器,需要在bean里面增加节点 ,如果不指定“限定符”,将默认使用“transationManager”命名对应的事务管理器。
指示spring事务管理器回滚一个事务的方法是在当前事务的上下文内抛出异常。spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。
默认配置下,spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚),而抛出checked异常则不会导致事务回滚。
可以明确的配置在抛出哪些异常时回滚事务,包括checked异常。也可以明确定义那些异常抛出时不回滚事务。
如果在@Transactional标记指定的方法里面,使用catch把异常吃掉了,那么这个事务是不会回滚的。
对应Spring+MyBatis事务,必须保证applicationContext.xml配置文件里面,transactionManger和Mybatis的数据源是一致的,如果不一致,则@Transactional注解的方法是没有事务的;可以用Corba作为数据源,是可以保证事务正常执行的,这个架构组做过测试。
### 演示一个老式的编程式事务管理,也感觉一下注解方式的优越:)
~~~
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = txManager.getTransaction(def);
try {
b.setBrandName("test");
brandMapper.upddateBrand(b);
b.setBrandName("..."); //设置名字超长,让报错
brandMapper.upddateBrand(b);
}
catch (Exception ex) {
txManager.rollback(status);
}
txManager.commit(status)
~~~
# 不同数据源下MyBatis配置数据源的办法
';
类型 | 描述 | 场景 | 解决 |
脏读(dirty read) | A事务读取B事务商务未提交的更改数据,并在这个数据的基础上操作 | 事务1:张三给李四汇款100元,记录日志(假设10秒) 事务2:此时李四查询,发现到账 事务1:记录日志出错,回滚,钱退给张三 结果:导致李四错误的认为收到款项 | 把事务隔离级别调整到READ COMMITTED |
不可重复读(unrepeatable read) | 是指A事务读取了B事务已经提交的更改数据,导致A事务对于同一个数据的多次读取,结果是不一样的。 一个事务先后读取同一条记录,但两次读取的数据不同,我们称之为不可重复读。 | 事务1:张三先后两次查询某一账号的余额 事务2:在两次查询期间,李四完成了银行转账 结果:导致两次的查询结果不同 | 把事务隔离级别调整到REPEATABLE READ |
幻象读(phantom read) | A事务读取B事务提交的新增数据,一般发生在计算统计数据的事务中。 一个事务先后读取一个范围的记录,但两次读取的纪录数不同,我们称之为幻象读。 | 事务1:张三先后两次查询所有账号的余额 事务2:在两次查询期间,李四给张三新开一个账号,并存入100元 结果:导致两次查询的结果不同 | 把事务隔离级别调整到SERIALIZABLE |
Java之旅–Web.xml解析
最后更新于:2022-04-01 20:11:50
Windows的IIS,是用UI界面进行站点的配置;Linux下面的几乎所有系统,都是使用配置文件来进行配置,Java容器(JBoss/Tomcat/Jetty/WebSphere/WebLogic等等)也不例外,它们使用一个部署在WEB-INFO目录下面的web.xml来作为站点配置文件。
本文参考互联网文章,学习并记录web.xml的加载顺序及配置详解。
# web.xml加载顺序
应用服务器启动时web.xml的加载过程,和这些节点在xml文件中的前后顺序没有关系,不过有些应用服务器,比如WebSphere,就严格要求web.xml的节点顺序,否则部署不成功,所以最好还是按照web.xml的标准格式写,即:context-param --> listener --> filter --> servlet 。
或者根据IDE的提示,比如,如果顺序不对,IDE可能有如下提示:
The content of element type "web-app" must match "(icon?,display-
name?,description?,distributable?,context-param *,filter *,filter-mapping *,listener *,servlet *,servlet-
mapping *,session-config?,mime-mapping *,welcome-file-list?,error-page *,taglib *,resource-env-ref *,resource-
ref *,security-constraint *,login-config?,security-role *,env-entry*,ejb-ref*,ejb-local-ref *)".
1. 启动WEB项目的时候,应用服务器会去读它的配置文件web.xml,读两个节点: 和
1. 紧接着,容器创建一个ServletContext(上下文),这个WEB项目所有部分都将共享这个上下文
1. 容器将 转化为键值对,并交给ServletContext
1. 容器创建 中的类实例,即创建监听
1. 在监听中会有contextInitialized(ServletContextEvent args)初始化方法,在这个方法中获得:
ServletContext = ServletContextEvent.getServletContext();
context-param的值 = ServletContext.getInitParameter("context-param的键");
1. 得到这个context-param的值之后,就可以做一些操作了。注意,这个时候WEB项目还没有完全启动完成,这个动作会比所有的Servlet都要早。换句话说,这个时候,你对中的键值做的操作,将在你的WEB项目完全启动之前被执行,如果想在项目启动之前就打开数据库,那么就可以在中设置数据库的连接方式,在监听类中初始化数据库的连接,这个监听是自己写的一个类,除了初始化方法,它还有销毁方法,用于关闭应用前释放资源,比如说数据库连接的关闭。
对于某类配置节而言,与它们出现的顺序是有关的。
以 filter 为例,web.xml 中当然可以定义多个 filter,与 filter 相关的一个配置节是 filter-mapping,这里一定要注意,对于拥有相同 filter-name 的 filter 和 filter-mapping 配置节而言,filter-mapping 必须出现在 filter 之后,否则当解析到 filter-mapping 时,它所对应的 filter-name 还未定义。
web 容器启动时初始化每个 filter 时,是按照 filter 配置节出现的顺序来初始化的,当请求资源匹配多个 filter-mapping 时,filter 拦截资源是按照 filter-mapping 配置节出现的顺序来依次调用 doFilter() 方法的。
servlet 同 filter 类似,此处不再赘述。
比如filter 需要用到 bean ,但加载顺序是: 先加载filter 后加载spring,则filter中初始化操作中的bean为null;所以,如果过滤器中要使用到 bean,可以将spring 的加载 改成 Listener的方式:
~~~
org.springframework.web.context.ContextLoaderListener
~~~
# web.xml节点解析
###
用来设定web站点的环境参数
它包含两个子元素: 用来指定参数的名称; 用来设定参数值
在此设定的参数,可以在servlet中用 getServletContext().getInitParameter("my_param") 来取得
例子:
~~~
webAppRootKey
privilege.root
contextConfigLocation
classpath*:/applicationContext*.xml
classpath*:/cas-authority.xml
log4jConfigLocation
/WEB-INF/classes/log4j.xml
spring.profiles.default
dev
~~~
###
用来设定Listener接口
它的主要子元素为 ,用来定义Listener的类名称
例子:
~~~
org.springframework.web.context.ContextLoaderListener
~~~
###
用来声明filter的相关设定
指定filter的名字
用来定义filter的类的名称
用来定义参数,它有两个子元素: 用来指定参数的名称, 用来设定参数值
与 一起使用的是
用来定义filter所对应的URL,包含两个子元素:
指定filter的名称
指定filter所对应的URL
例子:
~~~
encodingFilter
org.springframework.web.filter.CharacterEncodingFilter
encoding
UTF-8
forceEncoding
true
encodingFilter
*.do
~~~
###
用来声明一个servlet的数据,主要有以下子元素:
指定servlet的名称
指定servlet的类名称
指定web站台中的某个JSP网页的完整路径
用来定义参数
与 一起使用的是
用来定义servlet所对应的URL,包含两个子元素:
指定servlet的名称
指定servlet所对应的URL
例子:
~~~
springmvc
org.springframework.web.servlet.DispatcherServlet
1
springmvc
*.do
dubbo
com.alibaba.dubbo.remoting.http.servlet.DispatcherServlet
1
dubbo
/hessian/*
~~~
### 基本节点
1、 是对站点的描述
例子:传道、授业、解惑
2、 定义站点的名称
例子:我的站点
3、
icon元素包含small-icon和large-icon两个子元素,用来指定web站点中小图标和大图标的路径。
/路径/smallicon.gif
small-icon元素应指向web站台中某个小图标的路径,大小为16 X 16 pixel,但是图象文件必须为GIF或JPEG格式,扩展名必须为:.gif或.jpg。
/路径/largeicon-jpg
large-icon元素应指向web站台中某个大图表路径,大小为32 X 32 pixel,但是图象文件必须为GIF或JPEG的格式,扩展名必须为: gif或jpg。
例子:
/images/small.gif
/images/large.gir
4、 是指定该站点是否可分布式处理
5、 用来定义web站台中的session参数
包含一个子元素:
用来定义这个web站台所有session的有效期限,单位为 分钟
6、 定义某一个扩展名和某一个MIME Type做对应,它包含两个子元素:
扩展名的名称
MIME格式
例子:
~~~
csv
application/octet-stream
~~~
7、
通过错误码来配置error-page
~~~
404
/message.jsp
~~~
通过异常类来配置error-page
~~~
java.lang.NullException
/error.jsp
~~~
8、
~~~
index.html
index.jsp
~~~
9、 定义利用JNDI取得站台可利用的资源
有五个子元素:
资源说明
资源名称
资源种类
资源经由Application或Container来许可
资源是否可以共享,有Shareable和Unshareable两个值,默认为Shareable
比如,配置数据库连接池就可在此配置
JNDI JDBC DataSource of shop
jdbc/sample_db
javax.sql.DataSource
Container
';
Java之旅–多线程进阶
最后更新于:2022-04-01 20:11:48
# 先说点别的,为什么要逐渐学会读英文书籍
解释一个名词:上下文切换、Context switch
多任务系统中,上下文切换是指CPU的控制权由运行任务转移到另外一个就绪任务时所发生的事件。
When one thread’s execution is suspended and swapped off the processor, and another thread is swapped onto the processor and its execution is resumed, this is called a context switch.
为什么读英文版的计算机书籍,能理解的透彻和深刻。我们使用的语言,比如Java,是用英语开发的,其JDK中的类的命名、方法的命名,都是英文的,所以,当用英文解释一个名词、场景时,基于对这门编程语言的了解,我们马上就理解了,是非常形象的,简直就是图解,而用中文解释,虽然是我们的母语,但是对于计算机语言而言,却是外语,却是抽象的,反而不容易理解。
**推荐书籍:**Java Thread Programming ,但是要注意,这本书挺古老的,JDK1.1、1.2时代的产物,所以书中的思想OK,有些代码例子,可能得不出想演示的结果。
# 前言
关于多线程的知识,有非常多的资料可以参考。这里稍微总结一下,以求加深记忆。
关于多线程在日常工作中的使用:对于大多数的日常应用系统,比如各种管理系统,可能根本不需要深入了解,仅仅知道Thread/Runnable就够了;如果是需要很多计算任务的系统,比如推荐系统中各种中间数据的计算,对多线程的使用就较为频繁,也需要进行一下稍微深入的研究。
**几篇实战分析线程问题的好文章:**
[怎样分析 JAVA 的 Thread Dumps](http://tbstone.iteye.com/blog/2096423)
[各种 Java Thread State 第一分析法则](http://blog.csdn.net/wgw335363240/article/details/21373015)
[数据库死锁及解决死锁问题](http://blog.eastmoney.com/fk/blog_120150478.html)
[全面解决五大数据库死锁问题](http://tech.ccidnet.com/zt/sisuo/)
**关于线程池的几篇文章:**
[http://blog.csdn.net/wangpeng047/article/details/7748457](http://blog.csdn.net/wangpeng047/article/details/7748457)
[http://www.oschina.net/question/12_11255](http://www.oschina.net/question/12_11255)
[http://jamie-wang.iteye.com/blog/1554927](http://jamie-wang.iteye.com/blog/1554927)
# 基本知识
JVM最多支持多少个线程:[http://www.importnew.com/10780.html](http://www.importnew.com/10780.html)
### 线程安全
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。
如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
或者这样说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。
或者我们这样来简单理解,同一段程序块,从某一个时间点同时操作某个数据,对于这个数据来说,分叉了,则这就不是线程安全;如果对这段暑假保护起来,保证顺序执行,则就是线程安全。
### 原子操作
原子操作(atomic operation):是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。
原子操作(atomic operation):如果一个操作所处的层(layer)的更高层不能发现其内部实现与结构,则这个操作就是原子的。
原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序是不可以被打乱,或者切割掉只执行部分。
在多进程(线程)访问资源时,原子操作能够确保所有其他的进程(线程)都不在同一时间内访问相同的资源。
原子操作时不需要synchronized,这是Java多线程编程的老生常谈,但是,这是真的吗?我们通过测试发现(return i),当对象处于不稳定状态时,仍旧很有可能使用原子操作来访问他们,所以,对于java中的多线程,要遵循两个原则:
a、Brian Goetz的同步规则,如果你正在写一个变量,它可能接下来将被另一个线程读取,或者正在读取一个上一次已经被另一个线程写过的变量,那么你必须使用同步,并且,读写线程都必须用相同的监视器锁同步;
b、Brain Goetz测试:如果你可以编写用于现代微处理器的高性能JVM,那么就有资格去考虑是否可以避免使用同步
通常所说的原子操作包括对非long和double型的primitive进行赋值,以及返回这两者之外的primitive。之所以要把它们排除在外是因为它们都比较大,而JVM的设计规范又没有要求读操作和赋值操作必须是原子操作(JVM可以试着去这么作,但并不保证)。
### 错误理解
~~~
import java.util.Hashtable;
class Test
{
public static void main(String[] args) throws Exception {
final Hashtable h = new Hashtable();
long l1 = System.currentTimeMillis();
for(int i=0;i<10000;i++) {
new Thread(new Runnable(){
@Override
public void run() {
h.put("test",1);
Integer i1 = h.get("test");
h.put("test",2);
Integer i2 = h.get("test");
if(i1 == i2) {
System.out.println(i1 + ":" + i2);
}
}
}).start();
}
long l2 = System.currentTimeMillis();
System.out.println((l2-l1)/1000);
}
}
~~~
有人觉得:既然Hashtable是线程安全的,那么以上代码的run()方法中的代码应该是线程安全的,这是错误理解。
线程安全的对象,指的是其内部操作是线程安全的,其外部操作还是需要自己来保证其同步的,针对以上代码,在run方法的内部使用synchronized就可以了。
### 互斥锁和自旋锁
自旋锁:不睡觉,循环等待获取锁的方式成为自旋锁,在ConcurrentHashMap的实现中使用了自旋锁,一般自旋锁实现会有一个参数限定最多持续尝试次数,超出后,自旋锁放弃当前time slice,等下一次机会,自旋锁比较适用于锁使用者保持锁时间比较短的情况。
正是由于自旋锁使用者一般保持锁时间非常短,因此选择自旋而不是睡眠是非常必要的,自旋锁的效率远高于互斥锁。
CAS乐观锁适用的场景:[http://www.tuicool.com/articles/zuui6z](http://www.tuicool.com/articles/zuui6z)
# ThreadLocal与synchronized
### 区别ThreadLocal 与 synchronized
ThreadLocal是一个线程隔离(或者说是线程安全)的变量存储的管理实体(注意:不是存储用的),它以Java类方式表现;
synchronized是Java的一个保留字,只是一个代码标识符,它依靠JVM的锁机制来实现临界区的函数、变量在CPU运行访问中的原子性。
两者的性质、表现及设计初衷不同,因此没有可比较性。
synchronized对块使用,用的是Object对象锁,对于方法使用,用的是this锁,对于静态方法使用,用的是Class对象的锁,只有使用同一个锁的代码,才是同步的。
### 理解ThreadLocal中提到的变量副本
事实上,我们向ThreadLocal中set的变量不是由ThreadLocal来存储的,而是Thread线程对象自身保存。
当用户调用ThreadLocal对象的set(Object o)时,该方法则通过Thread.currentThread()获取当前线程,将变量存入Thread中的一个Map内,而Map的Key就是当前的ThreadLocal实例。
Runnable与Thread
实现多线程,Runnable接口和Thread类是最常用的了,实现Runnable接口比继承Thread类会更有优势:
- 适合多个相同的程序代码的线程去处理同一个资源
- 可以避免java中的单继承的限制
- 增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。
# Java线程互斥和协作
阻塞指的是暂停一个线程的执行以等待某个条件发生(如某资源就绪)。Java 提供了大量方法来支持阻塞,下面让对它们逐一分析。
1、sleep()方法:sleep()允许指定以毫秒为单位的一段时间作为参数,它使得线程在指定的时间内进入阻塞状态,不能得到CPU 时间,指定的时间一过,线程重新进入可执行状态。
典型地,sleep() 被用在等待某个资源就绪的情形:测试发现条件不满足后,让线程阻塞一段时间后重新测试,直到条件满足为止。
2、(Java 5已经不推荐使用,易造成死锁!!) suspend()和resume()方法:两个方法配套使用,suspend()使得线程进入阻塞状态,并且不会自动恢复,必须其对应的 resume() 被调用,才能使得线程重新进入可执行状态。典型地,suspend() 和 resume() 被用在等待另一个线程产生的结果的情形:测试发现结果还没有产生后,让线程阻塞,另一个线程产生了结果后,调用resume()使其恢复。
stop()方法,原用于停止线程,也已经不推荐使用,因为stop时会解锁,可能造成不可预料的后果;推荐设置一个flag标记变量,结合interrupt()方法来让线程终止。
3.、yield() 方法:yield() 使得线程放弃当前分得的 CPU 时间,但是不使线程阻塞,即线程仍处于可执行状态,随时可能再次分得 CPU 时间。调用 yield() 的效果等价于调度程序认为该线程已执行了足够的时间从而转到另一个线程。
4.、wait() 和 notify() 方法:两个方法配套使用,wait() 使得线程进入阻塞状态,它有两种形式,一种允许指定以毫秒为单位的一段时间作为参数,另一种没有参数,前者当对应的 notify() 被调用或者超出指定时间时线程重新进入可执行状态,后者则必须对应的 notify() 被调用。
2和4区别的核心在于,前面叙述的所有方法,阻塞时都不会释放占用的锁(如果占用了的话),而这一对方法则相反。上述的核心区别导致了一系列的细节上的区别。
首先,前面叙述的所有方法都隶属于Thread 类,但是这一对却直接隶属于 Object 类,也就是说,所有对象都拥有这一对方法。因为这一对方法阻塞时要释放占用的锁,而锁是任何对象都具有的,调用任意对象的 wait() 方法导致线程阻塞,并且该对象上的锁被释放。而调用任意对象的notify()方法则导致因调用该对象的 wait() 方法而阻塞的线程中随机选择的一个解除阻塞(但要等到获得锁后才真正可执行)。
其次,前面叙述的所有方法都可在任何位置调用,但是这一对方法却必须在 synchronized 方法或块中调用,理由也很简单,只有在synchronized 方法或块中当前线程才占有锁,才有锁可以释放。同样的道理,调用这一对方法的对象上的锁必须为当前线程所拥有,这样才有锁可以释放。因此,这一对方法调用必须放置在这样的 synchronized 方法或块中,该方法或块的上锁对象就是调用这一对方法的对象。若不满足这一条件,则程序虽然仍能编译,但在运行时会出现 IllegalMonitorStateException 异常。
wait() 和 notify() 方法的上述特性决定了它们经常和synchronized 方法或块一起使用,将它们和操作系统的进程间通信机制作一个比较就会发现它们的相似性:synchronized方法或块提供了类似于操作系统原语的功能,它们的结合用于解决各种复杂的线程间通信问题。
关于 wait() 和 notify() 方法最后再说明三点:
第一:调用 notify() 方法导致解除阻塞的线程是从因调用该对象的 wait() 方法而阻塞的线程中随机选取的,我们无法预料哪一个线程将会被选择,所以编程时要特别小心,避免因这种不确定性而产生问题。
第二:除了 notify(),还有一个方法 notifyAll() 也可起到类似作用,唯一的区别在于,调用 notifyAll() 方法将把因调用该对象的wait()方法而阻塞的所有线程一次性全部解除阻塞。当然,只有获得锁的那一个线程才能进入可执行状态。
**第三:wait/notify,是在作为监视器锁的对象上执行的,如果锁是a,执行b的wait,则会报java.lang.IllegalMonitorStateException。**
**关于interrupted,很容易理解错误,看两篇文章,如下:**
[http://www.blogjava.net/fhtdy2004/archive/2009/06/08/280728.html](http://www.blogjava.net/fhtdy2004/archive/2009/06/08/280728.html)
[http://www.blogjava.net/fhtdy2004/archive/2009/08/22/292181.html](http://www.blogjava.net/fhtdy2004/archive/2009/08/22/292181.html)
### 演示线程间协作机制,wait/notify/condition
代码示例1(wait/notify):
必须说这个代码是有缺陷的,会错失信号,想一想问题出在哪里,应该怎么完善?
~~~
/*
* 线程之间协作问题:两个线程,一个打印奇数,一个打印偶数
* 在调用wait方法时,都是用while判断条件的,而不是if,
* 在wait方法说明中,也推荐使用while,因为在某些特定的情况下,线程有可能被假唤醒,使用while会循环检测更稳妥
* */
public class OddAndEven {
static int[] num = new int[]{1,2,3,4,5,6,7,8,9,10};
static int index = 0;
public static void main(String[] args) throws Exception {
OddAndEven oae = new OddAndEven();
//这里如果起超过2个线程,则可能出现所有的线程都处于wait状态的情况(网上很多代码都有这个Bug,要注意,这其实是一个错失信号产生的死锁问题,如果用notifyAll就不会有这个问题)
new Thread(new ThreadOdd(oae)).start();
new Thread(new ThreadEven(oae)).start();
}
static class ThreadOdd implements Runnable {
private OddAndEven oae;
public ThreadOdd(OddAndEven oae) {
this.oae = oae;
}
@Override
public void run() {
while(index < 10) {
oae.odd();
}
}
}
static class ThreadEven implements Runnable {
private OddAndEven oae;
public ThreadEven(OddAndEven oae) {
this.oae = oae;
}
@Override
public void run() {
while(index < 10) {
oae.even();
}
}
}
//奇数
public synchronized void odd() {
while(index<10 && num[index] % 2 == 0) {
try {
wait(); //阻塞偶数
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(index >= 10) return;
System.out.println(Thread.currentThread().getName() + " 打印奇数 : " + num[index]);
index++;
notify(); //唤醒偶数线程
}
//偶数
public synchronized void even() {
while(index<10 && num[index] % 2 == 1) {
try {
wait(); //阻塞奇数
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(index >= 10) return;
System.out.println(Thread.currentThread().getName() + " 打印偶数 : " + num[index]);
index++;
notify(); //唤醒奇数线程
}
}
~~~
代码示例2:
~~~
public class Test implements Runnable {
private String name;
private Object prev;
private Object self;
private Test(String name, Object prev, Object self) {
this.name = name;
this.prev = prev;
this.self = self;
}
@Override
public void run() {
int count = 10;
while (count > 0) {
synchronized (prev) {
synchronized (self) {
System.out.print(name);
count--;
try{
Thread.sleep(1);
}
catch (InterruptedException e){
e.printStackTrace();
}
self.notify();
}
try {
prev.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws Exception {
Object a = new Object();
Object b = new Object();
Object c = new Object();
Test pa = new Test("A", c, a);
Test pb = new Test("B", a, b);
Test pc = new Test("C", b, c);
new Thread(pa).start();
Thread.sleep(10);
new Thread(pb).start();
Thread.sleep(10);
new Thread(pc).start();
Thread.sleep(10);
}
}
~~~
代码示例3(Condition实现生产者消费者模式):
~~~
import java.util.PriorityQueue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionTest {
private int queueSize = 10;
private PriorityQueue queue = new PriorityQueue(queueSize);
private Lock lock = new ReentrantLock();
private Condition notFull = lock.newCondition();
private Condition notEmpty = lock.newCondition();
public static void main(String[] args) {
ConditionTest test = new ConditionTest();
Producer producer = test.new Producer();
Consumer consumer = test.new Consumer();
producer.start();
consumer.start();
}
class Consumer extends Thread {
@Override
public void run() {
consume();
}
private void consume() {
while(true){
lock.lock();
try {
while(queue.size() == 0){
try {
System.out.println("队列空,等待数据");
notEmpty.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.poll(); //每次移走队首元素
notFull.signal();
System.out.println("从队列取走一个元素,队列剩余"+queue.size()+"个元素");
} finally{
lock.unlock();
}
}
}
}
class Producer extends Thread {
@Override
public void run() {
produce();
}
private void produce() {
while(true){
lock.lock();
try {
while(queue.size() == queueSize){
try {
System.out.println("队列满,等待有空余空间");
notFull.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.offer(1); //每次插入一个元素
notEmpty.signal();
System.out.println("向队列取中插入一个元素,队列剩余空间:"+(queueSize-queue.size()));
} finally{
lock.unlock();
}
}
}
}
}
~~~
代码示例4(两把锁的生产者消费者模式):
~~~
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 程序是Catch住InterruptedException,还是走Thread.interrupted(),实际是不确定的
*/
public class Restaurant {
public Meal meal;
public ExecutorService exec = Executors.newCachedThreadPool();
public WaitPerson waitPerson = new WaitPerson(this);
public Chef chef = new Chef(this);
public Restaurant() {
exec.execute(waitPerson);
exec.execute(chef);
}
public static void main(String[] args) {
new Restaurant();
}
}
class Meal {
private final int orderNum;
public Meal(int orderNum) {this.orderNum = orderNum;}
public String toString() {return "Meal " + orderNum;}
}
class WaitPerson implements Runnable {
private Restaurant restaurant;
public WaitPerson(Restaurant restaurant) {this.restaurant = restaurant;}
public void run() {
try {
while(!Thread.interrupted()) {
synchronized(this) {
while(restaurant.meal == null) {
wait();
}
}
System.out.println("WaitPerson got " + restaurant.meal);
synchronized(restaurant.chef) {
restaurant.meal = null;
restaurant.chef.notifyAll();
}
}
}
catch(InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " " + e.toString());
}
}
}
class Chef implements Runnable {
private Restaurant restaurant;
private int count = 0;
public Chef(Restaurant restaurant) {this.restaurant = restaurant;}
public void run() {
try {
while(!Thread.interrupted()) {
synchronized(this) {
while(restaurant.meal != null) {
wait();
}
}
if(++count == 10) {
restaurant.exec.shutdownNow();
}
System.out.println("Order up!");
synchronized(restaurant.waitPerson) {
restaurant.meal = new Meal(count);
restaurant.waitPerson.notifyAll();
}
}
}
catch(InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " " + e.toString());
}
}
}
~~~
### 反面教材代码演示:
~~~
class SuspendAndResume {
private final static Object object = new Object();
static class ThreadA extends Thread {
public void run() {
synchronized(object) {
System.out.println("start...");
Thread.currentThread().suspend();
System.out.println("thread end");
}
}
}
public static void main(String[] args) throws InterruptedException {
ThreadA t1 = new ThreadA();
ThreadA t2 = new ThreadA();
t1.start();
t2.start();
Thread.sleep(100);
System.out.println(t1.getState());
System.out.println(t2.getState());
t1.resume();
t2.resume();
}
}
~~~
程序输出结果如下:
~~~
localhost:test puma$ sudo java SuspendAndResume
start...
RUNNABLE
BLOCKED
thread end
start...
~~~
关于suspend()/resume()这两个方法,类似于wait()/notify(),但是它们不是等待和唤醒线程。suspend()后的线程处于RUNNING状态,而不是WAITING状态,但是线程本身在这里已经挂起了,线程本身饿状态就开始对不上号了。
以上的例子解释如下:
首先t1.start()/t2.start(),main睡sleep10秒,让两个子线程都进入运行的区域;
打印状态,t1运行,t2被synchronized阻塞;
t1.resume(),此时t1打印thread end,马上执行t2.resume(),此时由于t1的synchronized还没来得及释放锁,所以这段代码是在t2的synchronized外执行的,也就是在t2.suspend()之前执行的,所以是无效的;而当t2线程被挂起时,输出start,但是由于t2.suspend()已经被执行完了,所以t2就会一直处于挂起状态,一直持有锁不释放,这些信息的不一致就导致了各种资源无法释放的问题。
对于这个程序,如果在t1.resume()和t2.resume()之间增加一个Thread.sleep(),可以看到又正常执行了。
总得来说,问题应当出在线程状态对外看到的是RUNNING状态,外部程序并不知道这个对象挂起了需要去做resume()操作。另外,它并不是基于对象来完成这个动作的,因此suspend()和wait()相关的顺序性很难保证。所以suspend()和resume()不推荐使用了。
反过来想,这也更加说明了wait()和notify()为什么要基于对象(而不是线程本身)来做数据结构,因为要控制生产者和消费者之间的关系,它需要一个临界区来控制它们之间的平衡。它不是随意地在线程上做操作来控制资源的,而是由资源反过来控制线程状态的。当然wait()和notify()并非不会导致死锁,只是它们的死锁通常是程序设计不当导致的,并且在通常情况下是可以通过优化来解决的。
### 同步队列
wait和notify以一种非常低级的方式解决了任务互操作问题,即每次交互时都握手。在许多情况下,可以瞄向更高的抽象级别,使用同步队列来解决任务协作问题,同步队列在任何时刻都只允许一个操作插入或移除元素。
如果消费者任务试图从队列中获取对象,而该队列此时为空,那么这些队列还可以挂起消费者任务,并且当有更多的元素可用时恢复消费者任务。阻塞队列可以解决非常大量的问题,而其方式与wait和notify相比,则简单并可靠的多。
代码示例1(将多个LiftOff的执行串行化,消费者是LiftOffRunner,将每个LiftOff对象从BlockingQueue中推出并直接运行):
~~~
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class TestBlockingQueues {
public static void main(String[] args) {
test("LinkedBlockingQueue",new LinkedBlockingQueue()); //Unlimited size
test("ArrayBlockingQueue",new ArrayBlockingQueue(3)); //Fixed size
test("SynchronousQueue",new SynchronousQueue()); //Size of 1
}
private static void test(String msg,BlockingQueue queue) {
System.out.println(msg);
LiftOffRunner runner = new LiftOffRunner(queue);
Thread t = new Thread(runner);
t.start();
for(int i = 0; i < 5; i++) {
runner.add(new LiftOff(i));
}
getKey("Press 'Enter' (" + msg + ")");
t.interrupt();
System.out.println("Finished " + msg + " test");
}
private static void getKey() {
try {
new BufferedReader(new InputStreamReader(System.in)).readLine();
} catch (IOException e) {
e.printStackTrace();
}
}
private static void getKey(String message) {
System.out.println(message);
getKey();
}
}
class LiftOffRunner implements Runnable {
private BlockingQueue rockets;
public LiftOffRunner(BlockingQueue queue) {rockets = queue;}
public void add(LiftOff lo) {
try {
rockets.put(lo);
} catch (InterruptedException e) {
System.out.println("Interrupted during put()");
}
}
@Override
public void run() {
try
{
while(!Thread.interrupted()) {
LiftOff rocket = rockets.take();
rocket.run();
}
} catch (InterruptedException e) {
System.out.println("Waking from take()");
}
System.out.print("Exiting LiftOffRunner");
}
}
class LiftOff {
private int num;
public LiftOff(int num) {this.num = num;}
public void run() {
System.out.println(Thread.currentThread().getName() + " " + num);
}
}
~~~
代码示例2:
线程间通过BlockingQueue协作,3个任务,一个做面包,一个对面包抹黄油,一个对抹过黄油的面包抹果酱。整个代码没有显示的使用同步,任务之间完成了很好地协作;因为同步由队列(其内部是同步的)和系统的设计隐式的管理了。每片Toast在任何时刻都只有一个任务在操作。因为队列的阻塞,使得处理过程将被自动的挂起和恢复。
我们可以看到通过使用BlockingQueue带来的简化十分明显,在使用显式的wait/notify时存在的类和类之间的耦合被消除了,因为每个类都只和他得BlockingQueue通信。
~~~
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
public class ToastOMatic {
public static void main(String[] args) throws Exception {
ToastQueue dryQueue = new ToastQueue(),
butteredQueue = new ToastQueue(),
finishedQueue = new ToastQueue();
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new Toaster(dryQueue));
exec.execute(new Butterer(dryQueue,butteredQueue));
exec.execute(new Jammer(butteredQueue,finishedQueue));
exec.execute(new Eater(finishedQueue));
TimeUnit.SECONDS.sleep(5);
exec.shutdownNow();
}
}
class Toast {
public enum Status {DRY,BUTTERED,JAMMED};
private Status status = Status.DRY;
private final int id;
public Toast(int idn) {id = idn;}
public void butter() {status = Status.BUTTERED;}
public void jam() {status = Status.JAMMED;}
public Status getStatus() {return status;}
public int getId() {return id;}
public String toString() {return "Toast " + id + " : " + status;}
}
@SuppressWarnings("serial")
class ToastQueue extends LinkedBlockingQueue {}
class Toaster implements Runnable {
private ToastQueue toastQueue;
private int count = 0;
private Random rand = new Random(47);
public Toaster(ToastQueue tq) {toastQueue = tq;}
public void run() {
try {
while(!Thread.interrupted()) {
TimeUnit.MICROSECONDS.sleep(100 + rand.nextInt(500));
//Make toast
Toast t = new Toast(count++);
System.out.println(t);
//Insert into queue
toastQueue.put(t);
}
} catch(InterruptedException e) {
System.out.println("Toaster interrupted");
}
System.out.println("Toaster off");
}
}
//Apply butter to toast
class Butterer implements Runnable {
private ToastQueue dryQueue,butteredQueue;
public Butterer(ToastQueue dry, ToastQueue buttered) {
dryQueue = dry;
butteredQueue = buttered;
}
public void run() {
try {
while(!Thread.interrupted()) {
//Blocks until next piece of toast is available
Toast t = dryQueue.take();
t.butter();
System.out.println(t);
butteredQueue.put(t);
}
} catch(InterruptedException e) {
System.out.println("Butterer interrupted");
}
System.out.println("Butterer off");
}
}
//Apply jam to buttered toast
class Jammer implements Runnable {
private ToastQueue butteredQueue,finishedQueue;
public Jammer(ToastQueue buttered,ToastQueue finished) {
butteredQueue = buttered;
finishedQueue = finished;
}
public void run() {
try {
while(!Thread.interrupted()) {
//Blocks until next piece of toast is available
Toast t = butteredQueue.take();
t.jam();
System.out.println(t);
finishedQueue.put(t);;
}
} catch(InterruptedException e) {
System.out.println("Jammer interrupted");
}
System.out.println("Jammer off");
}
}
//Consume the toast
class Eater implements Runnable {
private ToastQueue finishedQueue;
private int counter = 0;
public Eater(ToastQueue finished) {
finishedQueue = finished;
}
public void run() {
try {
while(!Thread.interrupted()) {
//Blocks until next piece of toast is available
Toast t = finishedQueue.take();
//Verify that the toast is coming in order, and that all pieces are getting jammed
if(t.getId() != counter++ || t.getStatus() != Toast.Status.JAMMED) {
System.out.println(">>>> Error : " + t);
System.exit(1);
} else {
System.out.println("Chomp! " + t);
}
}
} catch(InterruptedException e) {
System.out.println("Eater interrupted");
}
System.out.println("Eater off");
}
}
~~~
### 任务间使用管道进行输入输出
Java输入输出类库中的PipedWriter(允许任务向管道写),PipedReader(允许不同任务从同一个管道中读取),这个模型可以看成“生产者-消费者”问题的变体,这里的管道就是一个封装好的解决方案。
管道基本上是一个阻塞队列,存在于多个引入BlockingQueue之前的Java版本中。
代码示例1:
当Receiver调用read时,如果没有更多地数据,管道将自动阻塞。
Sender和Receiver是在main中启动的,即对象构造彻底完成之后。如果启动了一个没有彻底构造完毕的对象,在不同的平台上,管道可能产生不一致的行为。相比较而言,BlockingQueue使用起来更加健壮而容易。
shutdownNow被调用时,可以看到PipedReader和普通IO之间最重要的差异,PipedReader是可以中断的。
~~~
import java.io.IOException;
import java.io.PipedReader;
import java.io.PipedWriter;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class PipedIO {
public static void main(String[] args) throws Exception {
Sender sender = new Sender();
Receiver receiver = new Receiver(sender);
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(sender);
exec.execute(receiver);
TimeUnit.SECONDS.sleep(5);
exec.shutdownNow();
}
}
class Sender implements Runnable {
private Random rand = new Random(47);
private PipedWriter out = new PipedWriter();
public PipedWriter getPipedWriter() { return out; }
public void run() {
try {
while(true) {
for(char c = 'A'; c <= 'Z'; c++) {
out.write(c);
TimeUnit.MILLISECONDS.sleep(rand.nextInt(50));
}
}
} catch(IOException e) {
System.out.println(e + " Sender write exception");
} catch(InterruptedException e) {
System.out.println(e + " Sender sleep interrupted");
}
}
}
class Receiver implements Runnable {
private PipedReader in;
public Receiver(Sender sender) throws IOException {
in = new PipedReader(sender.getPipedWriter());
}
public void run() {
try {
while(true) {
//Blocks until characters are there
System.out.println("Read: " + (char)in.read() + ", ");
}
} catch(IOException e) {
System.out.println(e + " Receiver read exception");
}
}
}
~~~
# 线程是JVM级别的
我们知道静态变量是ClassLoader级别的,如果Web应用程序停止,这些静态变量也会从JVM中清除。
但是线程则是JVM级别的,如果用户在Web应用中启动一个线程,这个线程的生命周期并不会和Web应用程序保持同步。
也就是说,即使停止了Web应用,这个线程依旧是活跃的。
正是因为这个很隐晦的问题,所以很多有经验的开发者不太赞成在Web应用中私自启动线程。
# 获取异步线程的返回结果
通过java.util.concurrent包种的相关类,实现异步线程返回结果的获取。代码演示例子如下:
~~~
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class FutureTest {
public static class TaskRunnable implements Runnable {
@Override
public void run() {
System.out.println("runnable");
}
}
public static class TaskCallable implements Callable {
private String s;
public TaskCallable(String s) {
this.s = s;
}
@Override
public String call() throws Exception {
System.out.println("callable");
return s;
}
}
public static void main(String[] args) {
ExecutorService es = Executors.newCachedThreadPool();
for (int i = 0; i < 100; i++) {
es.submit(new TaskRunnable());
System.out.println(i);
}
List> futList = new LinkedList>();
for (int i = 0; i < 100; i++) {
futList.add(es.submit(new TaskCallable(String.valueOf(i))));
}
for (Future fut : futList) {
try {
System.out.println(fut.get());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
~~~
# ReentrantLock和synchronized两种锁定机制的对比
~~~
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test {
public static int I;
public static Object oLock = new Object();
public static Lock lock = new ReentrantLock();
public static void main(String[] args) throws Exception {
long b = System.currentTimeMillis();
List t1= new ArrayList();
for(int j=0;j<30;j++)
{
t1.add(new Thread(new R1()));
}
for(Thread t: t1) t.start();
for(Thread t:t1) {
t.join();
}
long e = System.currentTimeMillis();
System.out.println(Test.I + " | " + (e-b));
Test.I = 0;
b = System.currentTimeMillis();
List t2= new ArrayList();
for(int j=0;j<30;j++)
{
t2.add(new Thread(new R2()));
}
for(Thread t: t2) t.start();
for(Thread t:t2) {
t.join();
}
e = System.currentTimeMillis();
System.out.println(Test.I + " | " + (e-b));
}
}
class R1 implements Runnable {
@Override
public void run() {
for(int i=0;i<1000000;i++)
{
Test.lock.lock();
Test.I++;
Test.lock.unlock();
}
}
}
class R2 implements Runnable {
@Override
public void run() {
for(int i=0;i<1000000;i++)
{
synchronized("") {
Test.I++;
}
}
}
}
~~~
经过测试,输出结果分别为:
Windows7(2核,2G内存),结果:3000000 | 2890,和3000000 | 8198,性能差距还是比较明显;
Mac10.7(4核,4G内存),结果:3亿次计算,ReentrantLock用8秒,synchronized反而只用4秒,结果反过来了;
RHEL6.1(24核,64G内存),结果:3亿次计算,二者相差不多,都是20-50之间,但是ReentrantLock表现更好一些。
ReentrantLock利用的是“非阻塞同步算法与CAS(Compare and Swap)无锁算法”,是CPU级别的,参考网址:
[http://www.cnblogs.com/Mainz/p/3556430.html](http://www.cnblogs.com/Mainz/p/3556430.html)
关于两种锁机制的更多比较,请参阅:[http://www.ibm.com/developerworks/cn/java/j-jtp10264/index.html](http://www.ibm.com/developerworks/cn/java/j-jtp10264/index.html)
# 关于线程安全的N种实现场景
(1)synchronized
(2)immutable对象是自动线程安全的
(3)volatile,被volatile修饰的属性,对于读操作是线程安全的
(4)ConcurrentHashMap之类,java.util.concurrent包中的一些并发操作类,是线程安全的,但是没有使用synchronized关键字,实现巧妙,利用的基本特性是:volatile、Compare And Swap、分段,对于读不加锁(volatile保证线程安全),对于写,对于相关的segment通过ReentrantLock加锁
# Java线程死锁
代码示例:
~~~
public class Test {
public static void main(String[] args) {
Runnable t1 = new DeadLock(true);
Runnable t2 = new DeadLock(false);
new Thread(t1).start();
new Thread(t2).start();
}
}
class DeadLock implements Runnable {
private static Object lock1 = new Object();
private static Object lock2 = new Object();
private boolean flag;
public DeadLock(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if(flag) {
synchronized(lock1) {
try {
Thread.sleep(1000); //保证晚于另一个线程锁lock2,目的是产生死锁
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(lock2) {
System.out.println("flag=true:死锁了,还能print的出来吗?");
}
}
} else {
synchronized(lock2) {
try {
Thread.sleep(1000); //保证晚于另一个线程锁lock1,目的是产生死锁
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(lock1) {
System.out.println("flag=false:死锁了,还能print的出来吗?");
}
}
}
}
}
~~~
死锁可以这样比喻:两个人吃饭,需要刀子和叉子,其中一人拿了刀子,等待叉子;另一个人拿了叉子,等待刀子;就死锁了。
Java线程死锁是一个经典的多线程问题,因为不同的线程都在等待那些根本不可能被释放的锁,导致所有的工作都无法完成;
导致死锁的根源在于不适当地运用“synchronized”关键词来管理线程对特定对象的访问。
**如何避免死锁的设计规则:**
(1)让所有的线程按照同样地顺序获得一组锁。这种方法消除了X和Y的拥有者分别等待对方的资源的问题。这也是避免死锁的一个通用的经验法则是:当几个线程都要访问共享资源A、B、C时,保证使每个线程都按照同样的顺序去访问它们,比如都先访问A,在访问B和C。
(2)将多个锁组成一组并放到同一个锁下。比如,把刀子和叉子,都放在一个新创建的(银器对象)下面,要获得子锁,先获得父锁。
# 更多
微博设计框架:[http://mars914.iteye.com/blog/1218492](http://mars914.iteye.com/blog/1218492) [http://timyang.net/](http://timyang.net/)
避免多线程时,开销分布在调度上,可以采取的策略:减少线程到合适的程度、避免线程内IO、采用合适的优先级。
关于IO和多线程,有个案例,Redis是单线程的,支持10万的QPS;MemberCache是多线程的,性能反而不如Redis;也可以佐证,对于IO非常多的操作,多线程未必能提高更好的性能,即使是内存IO。
另外,听百分点公司的讲座时,他们分享了一个案例,当计算的性能瓶颈在硬盘时,把硬盘换成SSD,可以性能翻倍,所以,应该把SSD当做是便宜的内存来使用,而不应该是当做昂贵的硬盘来使用。
在java.util.concurrent中,还提供了很多有用的线程写作类,比如:
CountDownLatch:倒计时锁、CyclicBarrier:循环栅栏、DelayQueue:延迟队列、PriorityBlockingQueue:优先级队列、ScheduledThreadPoolExecutor:定时任务、Semaphore:信号量、Exchanger:交互栅栏。
';
《Tomcat权威指南》第二版学习笔记
最后更新于:2022-04-01 20:11:46
本篇是《Tomcat权威指南》第二版学习笔记,Jason Brittain著,英文名是:Tomcat:The Definitive Guide,中国电力出版社,2009.9出版。
在工作中经常使用Tomcat、JBoss、Jetty等Java容器,但都不曾系统的学习总结过,本次拿出一个周末的时间,通过本书,较为系统的学习一下Tomcat,并结合互联网的参考资料,写下这篇学习总结,感觉还是受益良多。
关于《Tomcat权威指南》第二版这本书,是基于Tomcat 6的,翻译不是很专业,一些计算机技术术语翻译的不够准确,印刷中也有一些错别字,所以对于没有基础的初学者,容易被误导,如果有一定基础,把这本书系统看一遍,作为工具书,还是不错的。
关于本书的原作者,是spigit.com的软件架构师,对Tomcat作为Web服务器的性能估计较为乐观,这个乐观的估计没有得到大数据量高并发系统的验证;相反,仅仅把Tomcat作为Java容器,甚至仅仅作为开发过程Java容器,生产过程使用JBoss的案例貌似更多。
关于选择这本书的原因,是市场上系统的讲解Tomcat的书实在是少,相对来说,从系统化讲解来说,这本书还不错![大笑](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569e21abc5518.gif)
# Tomcat权威指南
### Tomcat目录结构
| bin | 存放启动、停止服务器的脚本文件 |
|-----|-----|
| conf | 存放服务器的配置文件、最重要的是server.xml文件 |
| lib | 存放jar文件,服务器和所有的web应用程序都可以访问 |
| logs | 存放服务器的日志文件 |
| temp | 存放Tomcat运行时的临时文件 |
| webapps | 缺省的web应用的发布目录,在server.xml中的“Host appBase="webapps"...”节点定义 |
| work | Tomcat的工作目录,默认情况下把编译JSP文件生成的servlet类文件放于此目录下 |
### Tomcat conf目录下的配置文件
| server.xml | Tomcat主配置文件 |
|-----|-----|
| web.xml | servlet与其他适用于整个Web应用程序的配置,必须符合servlet规范 |
| tomcat-users.xml | Tomcat的UserDatabaseRealm用于认证的默认角色、用户及密码清单 |
| catalina.policy | Tomcat的Java安全防护策略文件 |
| context.xml | 默认的context设置,应用于安装了Tomcat的所有主机的所有部署内容 |
| catalina.properties | |
| logging.properties | |
### Tomcat lib目录下的jar包作用
| **jar** | **作用** | **Tomcat6** | **Tomcat7** | **Tomcat8** |
|-----|-----|-----|-----|-----|
| annotation-api.jar | JavaEE annotations classes | √ | √ | √ |
| catalina.jar | Implementation of the Catalina servlet container portion of Tomcat | √ | √ | √ |
| catalina-ant.jar | Tomcat Catalina Ant tasks | √ | √ | √ |
| catalina-ha.jar | 高可用package | √ | √ | √ |
| catalina-storeconfig.jar | | **×** | **×** | √ |
| catalina-tribes.jar | 组通信package | √ | √ | √ |
| ecj-4.3.1.jar | Eclipse JDT Java compiler,即Eclipse开发的Jsp编译器,Tomcat5.5开始,开始默认使用这个编译器编译Jsp页 | √ | √ | √ |
| el-api.jar | expression language | √ | √ | √ |
| jasper.jar | Tomcat Jasper JSP Compiler and Runtime,Jasper 2 JSP Engine to implement the JavaServer Pages 2.1 specification | √ | √ | √ |
| jasper-el.jar | Tomcat Jasper EL implementation | √ | √ | √ |
| jsp-api.jar | 在Tomcat6/7/8中,版本分别是2.1/2.2/2.3 | √ | √ | √ |
| servlet-api.jar | 在Tomcat6/7/8中,版本分别是2.5/3.0/3.1 | √ | √ | √ |
| tomcat-api.jar | Several interfaces defined by Tomcat | **×** | √ | √ |
| tomcat-coyote.jar | Tomcat connectors and utility classes | √ | √ | √ |
| tomcat-dbcp.jar | Database connection pool implementation,based on package-renamed copy of Apache Commons Pool and Apache Commons DBCP | √ | √ | √ |
| tomcat-i18n-es.jar | Optional JARs containing resource bundles for other languages。As default bundles are also included in each individual JAR,they can be safely removed if no internationalization of messages is needed | √ | √ | √ |
| tomcat-i18n-fr.jar | | √ | √ | √ |
| tomcat-i18n-fa.jar | | √ | √ | √ |
| tomcat-jdbc.jar | An alternative database connection pool implementation,known as Tomcat JDBC pool。See documentation for more details | **×** | √ | √ |
| tomcat-jni.jar | | **×** | **×** | √ |
| tomcat-spdy.jar | | **×** | **×** | √ |
| tomcat-util.jar | Common classes used by various components of Apache Tomcat | **×** | √ | √ |
| tomcat-util-scan.jar | | **×** | **×** | √ |
| tomcat-websocket.jar | 在Tomcat7中这个包叫tomcat7-websocket.jar | **×** | √ | √ |
| websocket-api.jar | | **×** | √ | √ |
### Tomcat部署的两种方法
(1)server.xml Context部署:在server.xml文件中增加一个Context元素
(2)Context XML片段文件部署:在Tomcat的CATALINA_HOME/conf/[EngineName]/[Hostname]目录中增加一个新的Context XML片段文件
### Tomcat性能优化
### 性能指标:吞吐量
Responsetime、CpuLoad、MemoryUsage
### 压力测试工具
a、Apache Benchmark(ab,内含在Apache httpd Web服务器的发行版中,网址为:http://httpd.apache.org) ab -k -n 100000 -c 149 http://ip:8080
b、Siege(http://www.joedog.org/JoeDog/Siege) siege -b -r 671 -c 149 tomcathost:8080
c、Apache Jakarta的JMeter(http://jakarta.apache.org/jmeter),据说易用、灵活、图形化、报表性强、但是单纯测试“每秒钟请求并完成非常大量HTTP请求”方面,不如以上两种
### Tomcat连接器
Tomcat提供了3种不同的服务器设计实现方法,JIO(java.io):这是Tomcat默认的连接器实现办法,也成为“Coyote”,是使用java.io核心网络类的纯Java TCP实现;APR(Apache Portable Runtime),使用了libtcnative库(c语言编写的),适用于HTTPS链接;NIO(java.nio),非阻塞的方式,更少的线程
### Apache连接器
mod_jk、mod_proxy_ajp、mod_proxy_http
### JVM优化
所有的Java相关应用的JVM调优,其原理都是一致的,请参考这里:http://blog.csdn.net/puma_dong/article/details/12529905#t4
### Tomcat优化
a、禁用DNS查询,在Connection节点中,增加enableLookups="false",这个设置会导致getRemoteHost()只能获取到IP地址;
b、调整线程数,maxThreads默认是200,示例:
**maxThrads和acceptCount,这两个值如何起作用,请看下面三种场景:**
a、接受一个请求,此时tomcat启动的线程数没有到达maxThreads,tomcat会启动一个线程来处理此请求;
b、接受一个请求,此时tomcat启动的线程数已经到达maxThreads,tomcat会把此请求放入等待队列,等待空闲线程;
c、接受一个请求,此时tomcat启动的线程数已经到达maxThreads,等待队列中得请求个数也达到了acceptCount,此时tomcat会直接拒绝此次请求,返回connection refused
**maxThreads如何配置:**
一般的服务器操作都包含量方面:1计算(主要消耗cpu),2等待(io、数据库等);
第一种极端情况,如果我们的操作是纯粹的计算,那么系统响应时间的主要限制就是cpu的运算能力,此时maxThreads应该尽量设的小,降低同一时间内争抢cpu的线程个数,可以提高计算效率,提高系统的整体处理能力;
第二种极端情况,如果我们的操作纯粹是IO或者数据库,那么响应时间的主要限制就变为等待外部资源,此时maxThreads应该尽量设的大,这样才能提高同时处理请求的个数,从而提高系统整体的处理能力。此情况因为tomcat同时处理的请求量会比较大,所以需要关注一下tomcat的虚拟机内存设置和linux的open file限制;
如果maxThreads设置过大,比如5000,由于cpu就需要在多个线程之间来回切换,以保证每个线程都会获得cpu时间,即通常我们说的并发执行,反而降低了cpu效率,直接导致响应时间急剧增加,所以maxThreads的配置绝对不是越大越好;
现实应用中,我们的操作都会包含以上两种类型(计算、等待),所以maxThreads的配置并没有一个最优值,一定要根据具体情况来配置;
最好的做法是:在不断测试的基础上,不断调整、优化,才能得到最合理的配置。
**acceptCount如何配置:**
可以设置跟maxThreads一样大,这个值应该是主要根据应用的访问峰值与平均值来权衡配置的;
如果设的较小,可以保证接受的请求较快响应,但是超出的请求可能就直接被拒绝;
如果设的较大,可能就出现大量的请求超时的情况,因为我们系统的处理能力是一定的。
### Tomcat和Apache的区别
(1)apache:是Web服务器,本身只支持静态网页,但可以通过插件扩展支持PHP、Java等
(2)tomcat:是应用服务器,它只是一个servlet容器,是Apache的扩展,较少直接用作Web服务器
### Tomcat鸡肋功能
(1)Tomcat支持多实例部署,即在一个Tomcat安装的基础上,通过配置多个实例,达到bin/lib等目录共享,及版本统一的目的,实际如果想多实例,直接在生产中拷贝两套Tomcat反而更简单快捷
(2)Tomcat Admin管理Web界面,实际的生产中,不会用这个功能,并且应该是把这些功能从生产部署中删除的,达到安全的目的
(3)热部署,实际的生产中,更多的是冷却部署
### 总结
### 原书作者观点
原书作者的观点是Tomcat处理静态页面和图片的性能也是优于Apache的,他是通过一个不完全的测试案例:150个线程内(Tomcat默认运行的线程数),对小文件和小图片进行压力测试得出这个结论,所以他推崇“既把Tomcat作为Java容器,也直接用作Web服务器”,在中小应用中,这可能没有问题,但是在大型/超大型互联网应用中,Apache/Nginx的性能是被广泛证明了的,Apache/Nginx+Tomcat的组合依然是更优的选择。
### 关于本书
关于Ant的内容,没有去看,我用的是Maven;
关于Apache的内容,没有去看,我用的是Nginx;
关于权限相关,没有去看,应用场景很少,权限还是整体交给运维的好;
关于集群,没有去看,个人不觉得Tomcat本身的集群有什么用。
### 学习总结
在工作中经常使用Tomcat、JBoss、Jetty等Java容器,但都不曾系统的学习总结过,本次拿出一个周末两天的时间,通过本书,较为系统的学习一下Tomcat,并结合互联网的参考资料,写下这篇学习总结,感觉还是受益良多。
关于《Tomcat权威指南》第二版这本书,是基于Tomcat6的,翻译不是很专业,一些计算机技术术语翻译的不够准确,印刷中也有一些错别字,所以对于没有基础的初学者,容易被误导,如果有一定基础,把这本书系统看一遍,作为工具书,还是不错的。
关于本书的原作者,是spigit.com的软件架构师,对于Tomcat作为Web服务器的性能评估较为乐观,这个乐观的估计没有得到大数据量高并发系统的验证;相反,仅仅把Tomcat作为Java容器,似乎更为妥当。
# Tomcat日志
Tomcat日志,默认情况下,在启动的时候,会产生当天的文件,比如,catalina.2014-08-14.log,但是启动完毕后,之后的控制台日志就只往catalina.out文件里面写了,为了每天产生一个日志,有多种方式:
1、官方网站:http://tomcat.apache.org/tomcat-7.0-doc/logging.html
2、网上多有介绍使用cronolog进行切割的方式
3、下面介绍配置Solr时,使用的Log4j的配置方式
把jcl-over-slf4j-1.6.6.jar、jul-to-slf4j-1.6.6.jar、log4j-1.2.16.jar、slf4j-api-1.6.6.jar、slf4j-log4j12-1.6.6.jar、log4j.properties 6个文件放在Tomcat安装目录/lib下面即可产生每天一个的cata.yyyy-mm-dd.log日志文件,log4j.properties内容如下(这个文件实际是在:\solr-4.9.0\example\resources 目录下面的):
~~~
# Logging level
log4j.rootLogger=INFO, file, CONSOLE
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%-4r [%t] %-5p %c %x \u2013 %m%n
#- size rotation with log cleanup.
log4j.appender.file=org.apache.log4j.RollingFileAppender
log4j.appender.file.MaxFileSize=4MB
log4j.appender.file.MaxBackupIndex=9
#- File to log to and log format
log4j.appender.file.File=logs/solr.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%-5p - %d{yyyy-MM-dd HH:mm:ss.SSS}; %C; %m\n
~~~
';
《Spring3.X企业应用开发实战》学习笔记–IoC和AOP
最后更新于:2022-04-01 20:11:43
本篇是“《Spring3.X企业应用开发实战》,陈雄华 林开雄著,电子工业出版社,2012.2出版”的学习笔记的第一篇,关于Spring最基础的IoC和AOP。
在日常的开发中,最近几年正在使用着Spring,过去使用过Spring.Net,从官方文档及互联网博客,看过很多Spring文章,出于各种原因,没有系统的进行Spring的学习,这次通过这本书系统的学习了Spring框架,很多知识贯穿起来,改变了一些错误理解,受益匪浅。
**查看Spring源码的方法:**
下载源码后,执行import-into-eclipse.sh(bat),则会对源码建立Eclipse工程,Eclipse导入即可,执行这个批处理,需要JDK7及以上版本的支持。耐心一点,时间较长![大笑](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569e21abc5518.gif)
在阅读Spring源码的过程中,会需要很多JDK反射及注解的知识,有过小小总结,如下:[JDK框架简析--java.lang包中的基础类库、基础数据类型](http://blog.csdn.net/puma_dong/article/details/39670411)
另推荐一本书:《Spring Internals》Spring技术内幕,计文柯著,通过这本书,结合源代码,对于深入理解Spring架构和设计原理很有帮助。
# 使用Spring的好处到底在哪里?
你得先体会无Spring是什么滋味,才能知道Spring有何好处;
POJO编程,轻量级,低侵入;
面向接口编程,DI,解耦,降低业务对象替换的复杂性;
以提高开发效率为目标,简化第三方框架的使用方式;
灵活的基于核心 Spring 功能的 MVC 网页应用程序框架。开发者通过策略接口将拥有对该框架的高度控制,因而该框架将适应于多种呈现(View)技术,例如 JSP,FreeMarker,Velocity,Tiles,iText 以及 POI。值得注意的是,Spring 中间层可以轻易地结合于任何基于 MVC 框架的网页层,例如 Struts,WebWork,或 Tapestry;
**他的作者说:**
Spring是一个解决了许多在J2EE开发中常见的问题的强大框架。
Spring提供了管理业务对象的一致方法并且鼓励了注入对接口编程而不是对类编程的良好习惯。
Spring的架构基础是基于使用JavaBean属性的Inversion of Control容器。然而,这仅仅是完整图景中的一部分:Spring在使用IoC容器作为构建关注所有架构层的完整解决方案方面是独一无二的。
Spring提供了唯一的数据访问抽象,包括简单和有效率的JDBC框架,极大的改进了效率并且减少了可能的错误。Spring的数据访问架构还集成了Hibernate和其他O/R mapping解决方案。
Spring还提供了唯一的事务管理抽象,它能够在各种底层事务管理技术,例如JTA或者JDBC之上提供一个一致的编程模型。
Spring提供了一个用标准Java语言编写的AOP框架,它给POJOs提供了声明式的事务管理和其他企业事务--如果你需要--还能实现你自己的aspects。这个框架足够强大,使得应用程序能够抛开EJB的复杂性,同时享受着和传统EJB相关的关键服务。
Spring还提供了可以和总体的IoC容器集成的强大而灵活的MVC web框架。
# IoC
### 定义
IoC(控制反转:Inverse of Control)是Spring容器的内核,AOP、声明式事务等功能在此基础上开花结果。
虽然IoC这个重要概念不容易理解,但它确实包含很多内涵,它涉及代码解耦、设计模式、代码优化等问题。
因为IoC概念的不容易理解,Martin Fowler提出了DI(依赖注入:Dependency Injection)的概念用来代替IoC,即让调用类对某一接口实现类的依赖关系由第三方(容器或协作类)注入,以移除调用类对某一接口实现类的依赖。
Spring通过一个配置文件描述Bean和Bean之间的依赖关系,利用Java语言的反射功能实例化Bean并建立Bean之间的依赖关系。
Spring的IoC容器在完成这些底层工作的基础上,还提供了Bean实例缓存、生命周期管理、Bean实例代理、时间发布、资源装载等高级服务。
### 初始化
### Bean工厂
#### 概述
com.springframework.beans.factory.BeanFactory,是Spring框架最核心的接口,它提供了高级IoC的配置机制;使管理不同类型的Java对象成为可能;是Spring框架的基础设施,面向Spring本身。
#### 初始化
~~~
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource res = resolver.getResource("classpath:com/baobaotao/beanFactory/benas.xml");
//ClassPathResource res = new ClassPathResource("com/baobaotao/beanFactory/benas.xml");
BeanFactory bf = new XmlBeanFactory(res);
System.out.println("init BeanFactory");
Car car = bf.getBean("car",Car.class);
System.out.println("car bean is ready for use!");
~~~
XmlBeanFactory通过Resource装载Spring配置信息并启动IoC容器,然后就可以通过getBean方法从IoC容器获取Bean了。
通过BeanFactory启动IoC容器时,不会初始化配置文件中定义的Bean,初始化动作发生在第一个调用时 。
对于SingleTon的Bean来说,BeanFactory会缓存Bean。
**下面是一种更原始的编程式使用IoC容器的方法:**
~~~
//以下演示一种更原始的载入和注册Bean过程,对我们了解IoC容器的工作原理很有帮助
//揭示了在IoC容器实现中的关键类,比如:Resource、DefaultListableBeanFactory、BeanDefinitionReader,之间的相互联系,相互协作
ClassPathResource res = new ClassPathResource("beans.xml");
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(res);
Car car = factory.getBean("car",Car.class);
System.out.println(car.toString());
~~~
**beans.xml的内容如下:**
~~~
~~~
### ApplicationContext
#### 概述
com.springframework.context.ApplicationContext,建立在BeanFactory基础上,提供了更多面向应用的功能,它提供了国际化支持和框架事件体系,更易于创建应用;面向使用Spring的开发者,几乎所有的应用场合我们都直接使用ApplicationContext而非底层的BeanFactory
#### 初始化
~~~
ApplicationContext ctx = new ClassPathXmlApplicationContext(new String[]{"conf/beans1.xml","conf/beans2.xml"});
Car car = ctx.getBean("car",Car.class);
~~~
ApplicationContext的初始化和BeanFactory有一个重大的区别:
后者在初始化容器时,并未实例化Bean,直到第一次访问某个Bean时才实例目标Bean;而前者则在初始化应用上下文时就实例化所有单实例的Bean。因此ApplicationContext的初始化时间会比BeanFactory稍长一些,不过稍后的调用则没有“第一次惩罚”的问题;
另一个最大的区别,前者会利用Java反射机制自动识别出配置文件中定义的BeanPostProcessor、InstantiationAwareBeanPostPrcecssor和BeanFactoryPostProcessor,并自动将他们注册到应用上下文中;而后者需要在代码中通过手工调用addBeanPostProcessor方法进行注册。这也是为什么在应用开发时,我们普遍使用ApplicationContext而很少使用BeanFactory的原因之一。
可以在beans的属性中定义default-lazy-init="true",达到延迟初始化的目的,这不能保证所有的bean都延迟初始化,因为有的bean可能被依赖导致初始化。不推荐延迟初始化。
### WebApplicationContext
#### 概述
WebApplicationContext是专门为Web应用准备的,它允许从相对于Web根目录的路径中装载配置文件完成初始化工作。
从WebApplicationContext中可以获得ServletContext的引用,整个Web应用上下文对象将作为属性放置到ServletContext中,以便Web应用环境可以访问Spring应用上下文。
#### 初始化
WebApplicationContext的初始化方式和BeanFactory、ApplicationContext有所区别,因为WebApplicationContext需要ServletContext实例,也即是说它必须在Web容器的前提下才能完成启动的工作。有过Web开发经验的读者都知道可以在web.xml中配置自启动的Servlet或定义Web容器监听器,借助这两者中的任何一个,我们就可以完成启动Spring Web应用上下文的工作。
所有版本的Web容器都可以定义自启动的Servlet,但只有Servlet2.3及以上版本的Web容器才支持Web容器监听器。有些即使支持Servlet2.3的Web服务器,但也不能再Servlet初始化之前启动Web监听器,比如Weblogic 8.1,Websphere 5.x,Oracle OC4J 9.0。
Web.xml里面的配置节点如下:
~~~
contextConfigLocation
classpath*:/applicationContext.xml
org.springframework.web.context.ContextLoaderListener
~~~
### log4j
需要一种日志框架,我们使用Log4J,在类路径下,提供Log4J配置文件log4j.xml,这样启动Spring容器才不会报错。
对于WebApplicationContext,可以将Log4J配置文件放置在WEB-INF/classes下,这时Log4J引擎即可顺利启动。如果Log4J配置文件放置在其他位置,用户还必须在web.xml指定Log4J配置文件位置。
Spring为启动Log4J引擎提供了两个类似于启动WebApplicationContext的实现类:Log4jConfigServlet和Log4jConfigListener,不管采用哪种方式都必须保证能在在装载Spring配置文件之前先装载Log4J配置文件。
Web.xml里面的配置节点如下:
~~~
log4jConfigLocation
/WEB-INF/classes/log4j.xml
org.springframework.web.util.Log4jConfigListener
~~~
对于maven java application项目,log4j.xml直接放在src/main/resource下面即可,否则会报红色警告:
~~~
二月 09, 2015 10:57:38 下午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@a6af6e: startup date [Mon Feb 09 22:57:38 CST 2015]; root of context hierarchy
二月 09, 2015 10:57:38 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [applicationContext.xml]
~~~
### 使用外部属性文件
~~~
~~~
在基于xml的配置方式中,通过${cas.server.url}的表达式即可访问配置信息;在基于注解和基于Java类配置的Bean中,可以通过@Value("${cas.server.url}")的注解形式访问配置信息。#是引用Bean的属性值。
### 总结
BeanFactory、ApplicationContext和WebApplicationContext是Spring框架三个最核心的接口,框架中其他大部分的类都围绕它们展开、为它们提供支持和服务。
在这些支持类中,Resource是一个不可忽视的重要接口,框架通过Resource实现了和具体资源的解耦,不论它们位于何种介质中,都可以通过相同的实例返回。
与Resource配合的另一个接口是ResourceLoader,ResourceLoader采用了策略模式,可以通过传入资源的信息,自动选择适合的底层资源实现类,为生产对资源的引用提供了极大的便利。
在一些非Web项目中,在入口函数(main)中,会显示的初始化Spring容器,比如:ApplicationContext instance = new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml"}),这和Web项目中通过Listener(web.xml)初始化Spring容器,效果是一样的。一个是手工加载,一个是通过web容器加载,对于Spring容器的初始化,效果是一样的。
### Bean生命周期和作用域
Spring为Bean提供了细致周全的生命周期过程,通过实现特定的接口或通过属性设置,都可以对Bean的生命周期过程施加影响,Bean的生命周期不但和其实现的接口相关,还与Bean的作用范围有关。为了让Bean绑定在Spring框架上,我们推荐使用配置方式而非接口方式进行Bean生命周期的控制。
在实际的开发过程中,我们很少控制Bean生命周期,而是把这个工作交给Spring,采用默认的方式。
Bean的作用域:singleton,prototype,request,session,globalSession,默认是singleton。
### 配置方式
基于Xml配置方式中,配置文件的3种格式:完整配置格式、简化配置方式、使用p命名空间 。
基于注解配置方式中,使用到的注解符号:@Compoment,@Repository,@Service,@Controller,@Autowired(@Resource,@Inject),@Qualifier,@Scope,@PostConstruct,@PreDestroy 。
基于Java类配置方式中,使用到的注解符号:@Configuration,@Bean 。
Bean不同配置方式比较,总结如下:
| 配置方式 | 基于XML配置 | 基于注解配置 | 基于Java类配置 |
|-----|-----|-----|-----|
| Bean定义 | 在XML文件中,通过元素定义Bean。如: | 在Bean实现类处通过标注@Component或衍生类(@Repository,@Service,@Controller)定义Bean | 在标注了Configuration的Java类中,通过在类方法上标注@Bean定义一个Bean。方法必须提供Bean的实例化逻辑。 |
| Bean名称 | 通过的id或name属性定义,如:,默认名称为:com.bbt.UserDao#0 | 通过注解的Value属性定义,如@Component("userDao")。默认名称为小写字母打头的类名(不带包名):userDao | 通过@Bean的name属性定义,如 @Bean("userDao"),默认名称为方法名。 |
| Bean注入 | 通过子元素或通过p命名空间的动态属性,如p:userDao-ref="userDao"进行注入 | 通过在成员变量或方法入参处标注@Autowired,按类型匹配自动注入。还可以配合使用@Qualifier按名称匹配方式呼入 | 比较灵活,可以通过在方法除通过@Autowired使方法入参绑定Bean,然后在方法中通过代码进行注入,还可通过调研配置类的@Bean方法进行注入。 |
| Bean生命过程方法 | 通过的init-method和destroy-method属性指定Bean实现类的方法名最多只能指定一个初始化方法和一个销毁方法。 | 通过在目标方法上标注@PostConstruct和@PreDestroy注解指定初始化或销毁方法,可以定义任意多个 | 通过@Bean的initMethod或destroyMethod指定一个初始化或销毁方法;对于初始化方法来说,可以直接在方法内部通过代码的方式灵活定义初始化逻辑。 |
| Bean作用范围 | 通过的scope属性指定,如: | 通过在类定义处标注@Scope指定,如:@Scope("prototype") | 通过在Bean方法定义处标注@Scope指定 |
| Bean延迟初始化 | 通过的lazy-init属性绑定,默认为default,继承于的default-lazy-init设置,该值默认为false | 通过在类定义处标注@Lazy指定,如@Lazy(true) | 通过在类定义处标注@Lazy指定 |
| 适合场景 | 1.Bean实现类来源于第三方类库,如DataSource,JdbcTemplate等,因无法在类中标注注解,通过XML配置方式较好; 2.命名空间的配置,如aop,context等,只能采用基于XML的配置 | Bean的实现类是当前项目开发的,可以直接在Java类中使用基于注解的配置 | 基于Java类配置的优势在于可以通过代码方式控制Bean初始化的整体逻辑。所以如果实例化Bean的逻辑比较复杂,则比较适合用基于Java类配置的方式 |
一般采用XML配置DataSource,SessionFactory等资源Bean,在XML中利用aop,context命名空间进行相关主题的配置,其他的自己项目中开发的Bean,都通过基于注解配置的方式进行配置,即整个项目采用“基于XML+基于注解”的配置方式,很少采用基于Java类的配置方式。
### 通用知识点
1.Xml有5个特殊符号:<>&"',转义字符分别为:< > & " ' ,也可以用的方式。
2.资源类型的地址前缀:class: class*: file: http:// ftp://
3.JavaBean规范规定:变量的前两个字母要么全部大写,要么全部小写
4.默认构造函数是不带参的构造函数。Java语言规定如果类中没有定义任何构造函数,则JVM自动为其生成一个默认的构造函数。反之,如果类中显式定义了构造函数,则JVM不会为其生成默认的构造函数。所以假设Car类中显式定义了一个带参的构造函数,如public Car(String brand),则需要同时提供一个默认构造函数public Car(),否则使用属性注入时将抛出异常。
# AOP
AOP,Aspect Oriented Programming,面向切面编程
AOP的出现,是作为OOP的有益补充;AOP的应用场合是受限的,它一般只适合于那些具有横切逻辑的应用场合:如性能监测、访问控制、事务管理、日志记录。
OOP是通过纵向继承的机制,达到代码重用的目的;AOP通过横向抽取机制,把分散在各个业务逻辑中的相同代码,抽取到一个独立的模块中,还业务逻辑类一个清新的世界;把这些横切性的逻辑独立出来很容易,但如何将这些横切逻辑融合到业务逻辑中完成和原来一样的业务操作,才是事情的关键,这也正是AOP要解决的主要问题。
"AOP术语:连接点(Joinpoint)、切点(Pointcut)、增强(Advice)、目标对象(Target)、引介(Introduction)、织入(Weaving)、代理(Proxy)、切面(Aspect);
AOP织入方式:编译器织入、类装载期织入、动态代理织入,Spring采用动态代理织入,AspectJ采用前两种织入方式;"
Spring采用了两种代理机制:基于JDK的动态代理和基于CGLib的动态代理;前者创建的代理对象性能差,后者创建对象花费时间长,都大约是10倍的差距,所以对于单例的代理对象或者具有实例池的代理对象,比较适合CGLib,反之适合JDK。
关于AOP技术的实现技术步骤,先不进行深入研究,其中JDK5.0开始的注解技术,是对开发效率有明显改进的,可以深入了解下;实际所有基于注解的配置方式全部来源于JDK5.0注解技术的支持。
四种切面类型:@AspectJ、、Advisor、,JDK5.0以后全部采用@AspectJ方式吧。这四种切面定义方式,其底层实现实际是相同的,表象不同,本质归一。
1、@AspectJ使用JDK5.0注解和正规的AspectJ的切点表达式语言描述切面,由于Spring只支持方法的连接点,所以Spring仅支持部分AspectJ的切点语言,这种方式是被Spring推荐的,需要在xml中配置的内容最少,演示:[http://blog.csdn.net/puma_dong/article/details/20863953#t28](http://blog.csdn.net/puma_dong/article/details/20863953#t28)
2、如果项目只能使用低版本的JDK,则可以考虑使用,这是基于Schema的配置方式
3、如果正在升级一个基于低版本Spring AOP开发的项目,则可以考虑使用复用已经存在的Advice类
4、基于Advisor类的方式,其实也是蛮自动的,在xml中配置一个Advisor节点,开启自动创建代理就好了,比如:[http://blog.csdn.net/puma_dong/article/details/20863953#t27](http://blog.csdn.net/puma_dong/article/details/20863953#t27)
参考:http://blog.sina.com.cn/s/blog_872758480100wtfh.html
声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。当然,spring在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被 隐藏起来了
有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为 时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理;关于这点,很多资料这么多,但我实际的测试是,如果没有接口,只能有cglib,否则会报异常“Exception in thread "main" org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'person' must be of type [cl.an.Person], but was actually of type [cl.an.$Proxy9]”。
### 演示JDK动态代理实现AOP的基本原理
~~~
import java.lang.reflect.*;
class AOPTest {
public static void main(String[] args) throws Exception {
HelloInterface hello = BeanFactory.getBean("HelloImpl",HelloInterface.class);
hello.setInfo("zhangsan","zhangsan@163.com");
}
}
interface HelloInterface {
public String setInfo(String name,String email);
}
class HelloImpl implements HelloInterface {
private String name;
private String email;
@Override
public String setInfo(String name,String email) {
this.name = name;
this.email = email;
System.out.println("\n\n===>setInfo函数内部输出此行...");
return "OK";
}
}
class AOPHandler implements InvocationHandler {
private Object target;
public AOPHandler(Object target) {
this.target = target;
}
public void println(String str,Object... args) {
System.out.println(str);
if(args == null) {
System.out.println("\t\t\t 未传入任何值...");
} else {
for(Object obj:args) {
System.out.println("\t\t\t" + obj);
}
}
}
@Override
public Object invoke(Object proxyed,Method method,Object[] args)
throws IllegalArgumentException,IllegalAccessException,InvocationTargetException
{
//以下定义调用之前执行的操作
System.out.println("\n\n===>调用方法名: " + method.getName());
Class>[] variables = method.getParameterTypes();
System.out.println("\n\t参数类型列表:\n");
for(Class> typevariable : variables) {
System.out.println("\t\t\t" + typevariable.getName());
}
println("\n\n\t传入参数值为:",args);
//以下开始执行代理方法
System.out.println("\n\n开始执行method.invoke...调用代理方法...");
Object result = method.invoke(target,args);
//以下定义调用之后执行的操作
println("\n\n\t返回的参数为:",result);
println("\n\n\t返回值类型为:",method.getReturnType());
return result;
}
}
class BeanFactory {
public static Object getBean(String className) throws InstantiationException,IllegalAccessException,ClassNotFoundException {
Object obj = Class.forName(className).newInstance();
InvocationHandler handler = new AOPHandler(obj);//定义过滤器
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),handler);
}
@SuppressWarnings("unchecked")
public static T getBean(String className,Class c) throws InstantiationException,IllegalAccessException,ClassNotFoundException {
return (T)getBean(className);
}
}
~~~
其他参考方案:http://javatar.iteye.com/blog/814426/
### 演示Cglib动态代理实现AOP的基本原理
对于没有通过接口定义业务方法的类,如何动态创建代理实例呢?JDK的代理技术显然已经黔驴技穷,CGLib作为一个替代者,填补了这个空缺。
CGLib采用非常底层的字节码技术,可以为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类的方法调用,并顺势织入横切逻辑。
以下会有代码演示,关于代码演示,最简单的就是创建一个maven项目,依赖cglib后,会自动找出相关依赖。
本例采用较笨的办法,先把cglib需要的jar下载下来cglib-3.1.jar和asm-4.2.jar,然后在记事本编写TestForumService.java,之后使用javac编译,java运行。
javac -classpath cglib-3.1.jar TestForumService.java
java -classpath .;cglib-3.1.jar;asm-4.2.jar TestForumService,对于windows使用;连接,对于linux,使用:连接。
~~~
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class TestForumService
{
public static void main(String[] args) throws Exception {
CglibProxy proxy = new CglibProxy();
ForumServiceImpl forumService = (ForumServiceImpl)proxy.getProxy(ForumServiceImpl.class);
forumService.removeTopic(100);
}
}
class CglibProxy implements MethodInterceptor
{
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class> clazz) {
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create(); //通过字节码技术动态创建子类实例
}
//拦截父类所有方法的调用
public Object intercept(Object obj,Method method,Object[] args,MethodProxy proxy)
throws Throwable {
PerformanceMonitor.begin(obj.getClass().getName() + "." + method.getName());
Object result = proxy.invokeSuper(obj, args);
PerformanceMonitor.end();
return result;
}
}
//将要被注入切面功能的类
class ForumServiceImpl
{
public void removeTopic(int topicId) throws Exception {
System.out.println("模拟删除Topic记录:" + topicId);
//Thread.sleep(20);
}
}
//性能监视类(切面类)
class PerformanceMonitor
{
private static ThreadLocal performanceRecord =
new ThreadLocal();
public static void begin(String method) {
System.out.println("begin monitor...");
MethodPerformance mp = new MethodPerformance(method);
performanceRecord.set(mp);
}
public static void end() {
System.out.println("end monitor...");
MethodPerformance mp = performanceRecord.get();
mp.printPerformance();
}
}
class MethodPerformance
{
private long begin,end;
private String serviceMethod;
public MethodPerformance(String serviceMethod) {
this.serviceMethod = serviceMethod;
this.begin = System.currentTimeMillis();
}
public void printPerformance() {
end = System.currentTimeMillis();
long elapse = end - begin;
System.out.println(serviceMethod + "花费" + elapse + "毫秒。");
}
}
~~~
### 演示Spring项目的自定义注解
这个例子演示的是,SpringAOP通过自动代理(BeanPostProcessor)和切面(Advisor)来完成环绕增强(通过实现AOP联盟定义的MethodInterceptor接口)的过程,这个过程中,也会读取和分析注解,所以也是一个注解解析器,java代码如下:
~~~
package cl.an;
import java.lang.annotation.*;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.stereotype.Component;
public class MainTest {
public static void main(String[] args) {
@SuppressWarnings("resource")
ApplicationContext ctx = new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml"});
//测试IOC
MainUtil mainUtil = ctx.getBean("mainUtil",MainUtil.class);
if(mainUtil == null) {
System.out.println("null");
} else {
System.out.println(mainUtil.getString());
}
//测试自定义注解
Person person = ctx.getBean("person",Person.class);
System.out.println(person.say());
}
}
@Service
class MainUtil {
@Autowired
private MainConfigUtil configUtil;
public String getString() {
return "MainUtil." + configUtil.getString();
}
}
@Service
class MainConfigUtil {
public String getString() {
return "MainConfigUtil";
}
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@interface AnnotationTest {
String value();
}
@Component("person")
class Person {
@AnnotationTest("Annotation's Content")
public String say(){
return "I'm OK";
}
}
@Component("annotationTestAdvice")
class AnnotationTestAdvice implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
if(invocation.getMethod().isAnnotationPresent(AnnotationTest.class)){
String content = null;
Annotation annotation = invocation.getMethod().getAnnotation(AnnotationTest.class);
if(annotation!=null){
content = ((AnnotationTest)annotation).value();
System.out.println("获取到注解的内容:" + content);
}
System.out.println("方法调用之前要进行的工作");
Object o = invocation.proceed();
System.out.println("方法调用之后要进行的工作");
return o;
}else{
return invocation.proceed();
}
}
}
~~~
applicationContext.xml
~~~
Spring公共配置
~~~
pom.xml
~~~
4.0.0
cl
an
0.0.1-SNAPSHOT
jar
an
http://maven.apache.org
UTF-8
4.0.6.RELEASE
log4j
log4j
1.2.16
org.slf4j
slf4j-api
1.6.1
org.slf4j
slf4j-log4j12
1.6.1
org.springframework
spring-core
${spring.version}
org.springframework
spring-beans
${spring.version}
org.springframework
spring-context
${spring.version}
org.springframework
spring-context-support
${spring.version}
org.springframework
spring-aspects
${spring.version}
~~~
**还可以只通过注解方式实现注解:**
~~~
org.aspectj
aspectjweaver
1.8.7
~~~
~~~
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MonitorLog {
public String MonitorKey() default "";
public long TimeOut() default 200;//单位毫秒
}
~~~
~~~
@Aspect
public class MonitorLogAnnotationProcessor {
@Around("execution(* *(..)) && @annotation(log)")
public Object aroundMethod(ProceedingJoinPoint pjd ,MonitorLog log) {
Object result = null;
String monitorKey = log.MonitorKey();
long timeout = log.TimeOut();
String e_monitorKey="";
String monitorKey_timeout = "";
if(monitorKey==null || "".equals(monitorKey)){
MethodSignature signature = (MethodSignature) pjd.getSignature();
Method method = signature.getMethod();
String mName = method.getName();
Class> cls = method.getDeclaringClass() ;
String cName = cls.getName() ;
monitorKey= cName+"."+mName ;
}
e_monitorKey= monitorKey+".error" ;
monitorKey_timeout = monitorKey+".timeout" ;
long start = System.currentTimeMillis();
try {
result = pjd.proceed();
} catch (Throwable e) {//todo 是否捕获所有异常
JMonitor.add(e_monitorKey);
}finally {
long elapseTime = System.currentTimeMillis() - start;
if(elapseTime > timeout) {
JMonitor.add(monitorKey_timeout);
}
JMonitor.add(monitorKey, elapseTime);
}
return result;
}
}
~~~
### SpringAOP实现方式从低级到高级
这个例子通过一个“前置增强”的逐步简化步骤,演示SpringAOP如何从最低级的实现,到最高级(最简化、最自动)的实现的进化过程。
### 纯粹代码方式(ProxyFactory)
~~~
package cl.an.advice;
import java.lang.reflect.Method;
import org.springframework.aop.BeforeAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.framework.ProxyFactory;
public class TestBeforeAdvice {
public static void main(String[] args) {
Waiter target = new NaiveWaiter();
BeforeAdvice advice = new GreetingBeforeAdvice();
//Spring提供的代理工厂
ProxyFactory pf = new ProxyFactory();
//设置代理目标
pf.setTarget(target);
//为代理目标添加增强
pf.addAdvice(advice);
//生成代理实例
Waiter proxy = (Waiter)pf.getProxy();
proxy.greeTo("John");
proxy.serveTo("Tom");
}
}
interface Waiter {
void greeTo(String name);
void serveTo(String name);
}
class NaiveWaiter implements Waiter {
public void greeTo(String name) {
System.out.println("greet to " + name + "...");
}
public void serveTo(String name) {
System.out.println("serving " + name + "...");
}
}
class GreetingBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object obj)
throws Throwable {
String clientName = (String)args[0];
System.out.println("How are you! Mr." + clientName + ".");
}
}
~~~
ProxyFactory代理工厂将GreetingBeforeAdvice的增强织入到目标类NaiveWaiter中。在ProxyFactory内部,依然使用的是JDK代理或CGLib代理,将增强应用到目标类中。
Spring定义了org.springframework.aop.framework.AopProxy接口,并提供了两个实现类,Cglib2AopProxy和JdkDynamicAopProxy。
### Spring配置的方式(ProxyFactoryBean)
我们通过Spring配置的方式,代替编写ProxyFactory相关的代码。
1.beans.xml内容
~~~
~~~
2.调用代码简化为:
~~~
public class TestBeforeAdvice {
public static void main(String[] args) {
String configPath = "cl/an/advice/beans.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath);
Waiter waiter = (Waiter)ctx.getBean("waiter");
waiter.greeTo("John");
waiter.serveTo("Tom");
}
}
~~~
### 切面(Advisor)方式
本例子中演示的是正则表达式切面,并且限制了只对greeTo方法进行增强。
1.beans.xml的内容
~~~
~~~
### 自动创建代理
以上,我们通过ProxyFactoryBean创建织入切面的代理,对于小型系统,可以将就使用,但对拥有众多需要代理Bean的系统系统,需要做的配置工作就太多太多了。
但是,Spring为我们提供了自动代理机制,让容器为我们自动生成代理,把我们从繁琐的配置工作中解放出来。
在内部,Spring使用BeanPostProcessor自动完成这项工作。
我们可以使用BeanNameAutoProxyCreator,只对某些符合通配符的类进行增强,也可以使用DefaultAdvisorAutoProxyCreator,自动扫描容器中的Advisor,并将Advisor自动织入到匹配的目标Bean中,即为匹配的目标Bean自动创建代理。
1.beans.xml的内容
~~~
~~~
### @AspectJ定义切面的方式
这是一种配置最少的切面定义方式,功能强大,但是需要学习AspectJ切点表达式语言。
1.代码内容
~~~
package cl.an.aspectj;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AspectJProxyTest {
public static void main(String[] args) {
String configPath = "cl/an/aspectj/beans.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath);
Waiter waiter = (Waiter)ctx.getBean("waiter");
waiter.greeTo("John");
waiter.serveTo("Tom");
}
}
interface Waiter {
void greeTo(String name);
void serveTo(String name);
}
class NaiveWaiter implements Waiter {
public void greeTo(String name) {
System.out.println("greet to " + name + "...");
}
public void serveTo(String name) {
System.out.println("serving " + name + "...");
}
}
@Aspect
class PreGreetingAspect {
@Before("execution(* greeTo(..))")
public void beforeGreeting() {
System.out.println("How are you");
}
}
~~~
2.beans.xml
~~~
~~~
### LoadTimeWeaver-LTW
Spring也支持类加载期的织入,在类加载期,通过字节码编辑的技术,将切面织入到目标类中,这种织入方式成为LTW(Load Time Weaving)。就像运行期织入,其底层依靠的是JDK动态代理和CGLib字节码增强一样,类加载器的注入,底层依靠的也是JDK,jang.lang.instrument包中的两个接口(ClasFileTransformer和Instrumentation)。
1.代码
~~~
package instrument;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
public class Test {
public static void main(String[] args) {
System.out.println("I'm in main() of Test...");
}
}
class Transformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className,
Class> classBeingRedefined, ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException {
System.out.println("Hello " + className + "!");
return null;
}
}
//这个代理类,会在程序入口main()方法执行前执行,执行的是premain()方法
class Agent {
//这个Agent代理类,必须按一下前面方式定义premain()
public static void premain(String agentArgs,Instrumentation inst) {
ClassFileTransformer t = new Transformer();
inst.addTransformer(t);
}
}
~~~
2.MANIFEST.MF内容,这个文件在制作jar时用到,会导出到META-INF目录下的MANIFEST.MF
~~~
Manifest-Version: 1.0
Premain-Class: instrument.Agent
~~~
注意,这个文件格式要求严格,Premain-Class:后面必须有个空格,文件后面必须至少两行空行。
Eclipse导出jar把,然后执行java -javaagent:test.jar instrument.Test,可以看到Agent的代码内容已经被织入了。
关于Spring LTW,利用的是META-INF目录下的aop.xml,可以利用AspectJ表达式语言进行更多的控制,和前面解释AspectJ的部分差不多。
# 总结
关于Spring,我个人的理解是:IoC是基础,然后其他一切带给我们编程简便性的地方,全部来源于AOP,让这种横向抽取机制,封装常用操作。比如:
加上@Transactional标记,就对方法或者类开启了事务;
加上@Cacheable标记,就对方法开启了缓存。
';
《深入理解Java虚拟机》学习笔记
最后更新于:2022-04-01 20:11:41
本篇是《深入理解Java虚拟机-Java 高级特性与最佳实践》学习笔记,周志明著,Understanding the JVM-Advanced Features and Best Practices,机械工业出版社,2011.6出版。
重温Java JVM知识,重点学习了与日常开发工作相关性最大的“自动化内存管理”模块,对Java容器优化、内存问题解决很有帮助;习惯了从互联网看电子书,难以集中和记忆,现在找几本纸质书重温,可以很清静、很安静的理解和消化,受益匪浅。
# 自动内存管理机制
Java和C++之间有一睹由动态内存分配和垃圾收集机制组成的墙,里面的人想出来,外面的人想进去。
### 1、Java内存区域与内存溢出异常
JDK5.0以后每个线程栈大小为1M,之前每个线程栈大小为256K。
应当根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
需要注意的是:当这个值被设置的较大(例如>2MB)时将会在很大程度上降低系统的性能。
如果我们需要知道,JVM虚拟机的默认参数配置,可以使用命令查看:
java -XX:+PrintFlagsInitial | grep Ratio
我们可以看到,新生代和老年代的默认比值是2;Survior和Eden的默认比值是8.
[JVM实用参数(三)打印所有XX参数及值](http://ifeve.com/useful-jvm-flags-part-3-printing-all-xx-flags-and-their-values/)
**补充一个案例,如下:**
如果写了一个普通的死循环,没有发现,则会占用cpu时间,但是可能不会导致内存溢出的情况,因为可能对于堆和栈空间都没有太大影响;
但是如果写了个递归死循环,循环递归,则马上会报StackOverFlow,因为随着递归函数的调用,大量的本地变量被声明,很快,栈空间就满了;
另,对于不同的JDK,可能会变化,比如String的常量String.intern(),在JDK6中存放在“方法区”的“运行时常量池”中,但是在JDK7中,就存放在堆中了,所以,很多细节也是在一直变化中的。。。
参考网址:
[http://blog.csdn.net/kthq/article/details/8618052](http://blog.csdn.net/kthq/article/details/8618052)
[http://www.open-open.com/lib/view/open1324736648468.html](http://www.open-open.com/lib/view/open1324736648468.html)
### 2、垃圾收集器与内存分配策略
**对象已死的确定方法:**
**垃圾收集算法:**
| 标记-清除算法 | 最基础的收集算法,后续的收集算法都是对其缺点改进而得的,主要缺点:效率问题和空间问题。 |
|-----|-----|
| 复制算法 | 现在的商业虚拟机都采用这种算法来回收新生代,Eden空间+2*Survivor空间,8:1,90%内存利用率。 |
| 标记-整理算法 | 老年代采用这种收集算法,标记后将所有存活对象向一段移动,然后直接清理掉端边界之外的内存。 |
| 分代收集算法 | 实际就是对内存分代后,以上几种算法的选择。 |
**垃圾收集器(垃圾收集算法是方法论,垃圾收集器是实现):**
**JDK6默认的垃圾收集器:**
| Client模式 | 默认-XX:+UseSerialGC,使用Serial+Serial Old的组合进行垃圾回收 |
|-----|-----|
| Server模式 | 默认-XX:+UseParallelGC,使用Parallel Scavenge+Parallel Old的组合进行垃圾回收,之前的JVM,可能是Parallel Scavenge+Serail Old,总之,随着版本变迁,可能会有不同 |
**JVM默认的客户端还是服务器模式,取决于下面的情况:**
| Architecture | CPU/RAM | OS | Default |
|-----|-----|-----|-----|
| i586 | Any | MS Windows | Client |
| AMD64 | Any | Any | Server |
| 64-bit SPARC | Any | Solaris | Server |
| 32-bit SPARC | 2+ cores & > 2GB RAM | Solaris | Server |
| 32-bit SPARC | 1 core or < 2GB RAM | Solaris | Client |
| i586 | 2+ cores & > 2GB RAM | Linux or Solaris | Server |
| i586 | 1 core or < 2GB RAM | Linux or Solaris | Client |
**内存分配策略:**
对象优先在Eden分配、大对象直接进入老年代、长期存活的对象将进入老年代、动态对象年龄判定、空间分配担保
验证了前3条内存分配策略,在Win8.164bit及RHEL6.164bit下,和书中描述规则不一致,原因是我的i5-3210CPU被识别为AMD64,所以是默认以Server方式启动JVM,采用ParallelGC;我在启动JVM时,加上参数-XX:+UseSerialGC,强制使用SerialGC,则案例验证通过;PretenureSizeThreshold,即设定直接进入老年代的大对象尺寸的参数,只对Serial和ParNew生效;
也由此可见,内存分配策略具有非常复杂的规则,不仅仅是这几条规则所能描述的。
总结:经过半个世纪的发展,内存的动态分配和回收技术已经相当成熟;但是当需要排查各种内存溢出、内存泄露问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。
**查看JVM进程使用的垃圾回收器:**
jinfo -flag UseSerialGC 60282
jinfo -flag UseParNewGC 60282
jinfo -flag UseParallelGC 60282
jinfo -flag UseParallelOldGC 60282
jinfo -flag UseConcMarkSweepGC 60282
在RHEL6.3虚拟机(1G内存、单核)下,全部输出-,没有+,不知道原因,截图如下:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-27_5747b45022cdb.jpg)
在CentOS虚拟机(32G内存、8核)下,正常输出UseParallelGC,截图如下:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-27_5747b450384a7.jpg)
**参考文章:**
[http://blog.csdn.net/gugemichael/article/details/9345803](http://blog.csdn.net/gugemichael/article/details/9345803) 讲GC的,S0、S1为什么总有一个是空的
[http://www.fasterj.com/articles/oraclecollectors1.shtml](http://www.fasterj.com/articles/oraclecollectors1.shtml)
[http://blog.csdn.net/dc_726/article/details/7934101](http://blog.csdn.net/dc_726/article/details/7934101)
[http://jbutton.iteye.com/blog/1569746](http://jbutton.iteye.com/blog/1569746)
### 3、内存性能分析与故障处理工具
| jps | JVM Process Status Tool,显示指定系统内所有的HotSpot虚拟机进程 |
|-----|-----|
| -q | 只输出LVMID,省略主类的名称 |
| -m | 输出虚拟机进程启动时传递给主类main()函数的参数 |
| -l | 输出主类的全名,如果进程执行的是Jar包,输出Jar路径 |
| -v | 输出虚拟机进程启动时JVM参数 |
| jstat | JVM Statistics Monitoring Tool,用于收集HotSpot虚拟机各方面的运行数据 |
| -class | 监视类装载,卸载数量,总空间及类装载所耗费的时间 |
| -gc | 监视Java堆状况,包括Eden区,2个survivor区,老年代,永久代等的容量,已用空间,GC时间合计等信息 |
| -gccapacity | 监视内容与-gc基本相同,但输出主要关注Java堆各个区域使用到的最大和最小空间 |
| -gcutil | 监视内容与-gc基本相同,单输出主要关注已使用空间占总空间的百分比 |
| -gccause | 与-gcutil功能一样,但是会额外输出导致上一次GC产生的原因 |
| -gcnew | 监视新生代GC的状况 |
| -gcnewcapacity | 监视内容与-gcnew基本相同,输出主要关注使用到的最大和最小空间 |
| -gcold | 监视老年代GC的状况 |
| -gcoldcapacity | 监视内容与-gcold基本相同,输出主要关注使用到的最大和最小空间 |
| -gcpermcapacity | 监视永久代使用到的最大和最小空间 |
| -compiler | 输出JIT编译器编译过的方法,耗时等信息 |
| -printcompilation | 输出已经被JIT编译的方法 |
| jinfo | Configuration Info for Java,显示虚拟机配置信息 |
| -flag | jinfo -flag MaxPermSize 进程ID号,显示参数值 |
| -sysprops | 把虚拟机进程的System.getProperties()的内容打印出来 |
| jmap | Memory Map for Java,生成虚拟机的内存转储快照(heapdump文件) |
| -dump | 生成Java堆转储快照。格式为:-dump:[live,]format=b,file=,其中live子参数说明是否只dummp出存活的对象 |
| -finalizerinfo | 显示在F-Queue中等待Finalizer线程执行finalize方法的对象。只在linux/Solaris平台下有效 |
| -heap | 显示Java堆详细信息,如使用哪种回收器,参数设置,分代状况等。只在linux/Solaris平台下有效 |
| -histo | 显示堆中对象统计信息,包括类、实例数量和合计容量 |
| -permstat | 以ClassLoader为统计口径显示永久代内存状态。只在linux/Solaris平台下有效 |
| -F | 当虚拟机进程对-dump选择没有响应时,可使用这个选项强制生成dump快照。只在linux/Solaris平台下有效 |
| jhat | JVM Heap Dump Browser,用于分析headdump文件,它会建立一个HTTP/HTML服务器,让用户可以在浏览器上查看分析结果 |
| | 实际生产中,没有人会这么来分析headdump文件 |
| jstack | Stack Trace for Java,显示虚拟机的线程快照 |
| -F | 当正常输出的请求不被响应时,强制输出线程堆栈 |
| -l | 除堆栈外,显示关于锁的附加信息 |
| -m | 如果调用到本地方法的话,可以显示C/C++的堆栈 |
jstack -l -F 15348 >> 169.txt
sudo -u root /usr/java/jdk1.6.0_26/bin/jstack -F 15348
在JDK6U23之前,执行jstack,jmap -F会有Bug,看这个文章:[http://developer.51cto.com/art/201203/321359.htm](http://developer.51cto.com/art/201203/321359.htm)
**MAT(Memory Analyzer Tool)**
除了以上之外,另外推荐一个内存分析工具,下载地址:http://www.eclipse.org/mat/downloads.php,它分析的是JVM内存的dump文件,此文件可以通过jmap -dump:format=b,file=xxx.bin pid将内存dump出来,也可以通过设置-XX:+HeapDumpOnOutOfMemoryError参数,在JVM出现OOM时自动输出到文件;或者在VisualVM中直接对监控的JVM进程单击右键,选择“Heap Dump”,将JVM的内存dump出来。
**BTrace**
下载地址:https://kenai.com/projects/btrace/downloads/directory/releases
写一段脚本,随时切入正在运行的程序,跟踪代码的调用。写一段Btrace脚本还是很麻烦的,不详述了。
其他的工具还有:HSDB,Java自带的,执行方式如下:
java -classpath .:$JAVA_HOME/lib/sa-jdi.jar sun.jvm.hotspot.CLHSDB
### jvisualvm
可以远程监控Java进程,比如Tomcat,增加启动参数:
JAVA_OPTS="-Dcom.sun.management.jmxremote.port=9998 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Djava.rmi.server.hostname=10.10.10.212"
或者java进程,启动时增加参数,比如:
java -Xms2048m -Xmx46080m -classpath $CLASSPATH -Dcom.sun.management.jmxremote.port=9998 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Djava.rmi.server.hostname=10.10.10.212 com.yougou.recommend.service.ViewViewServiceReverse >> "$stats_log"
则可以在本地通过jvisualvm监控进程情况,在命令行输入jvisualvm,远程,连接ip地址,之后右键这个远程连接,新建JMX连接,输入端口,则可以监控这个远程java进程了。
### 4、调优案例分析与实战
**高性能硬件上的程序部署策略:**
**集群间同步导致的内存溢出:**
**堆外内存导致的溢出错误:**
**外部命令导致系统缓慢:**
**服务器JVM进程崩溃:**
Java虚拟机的内存管理与垃圾回收是虚拟机结构体系中最重要的组成部分,对我们程序的性能和稳定性有着非常大的影响。
# 虚拟机执行系统:类加载、编译和执行
这一部分深入讲解了虚拟机的执行过程,对实际工作中的帮助相对较小,这里只记录下篇章,及一些领悟,不做深入学习。
篇章:《类文件结构》、《虚拟机类加载机制》、《虚拟机字节码执行引擎》、《类加载及执行子系统的案例与实战》、《编译器优化》、《运行期优化》
这些篇章介绍了Class文件格式、类加载及迅疾执行引擎,这些内容是虚拟机中必不可少的部分,了解了虚拟机如何执行程序,才能更好地理解怎样才能写出优秀的代码。
代码编译的结果从本地机器码变为字节码,是存储格式发展的一小步,却是编程语言发展的一大步。
Java语言规范和Java虚拟机规范是分别定义的,这说明任何语言,编译成的字节码只要符合Java虚拟机规范,都可以在Java虚拟机执行,当前这样的语言有:Clojure,Groovy,JRuby,Jython。
Java程序从源码编译成字节码和从字节码编译成本地机器码的过程,Javac字节码编译器与虚拟机内的JIT编译器的执行过程合并起来,其实就等于一个传统的编译器所执行的编译过程。
对Java编译器的深入了解,有助于在工作中分辨哪些代码是编译器可以帮我们处理的,哪些代码需要自己调节以便更适合编译器的优化。
# 高效并发
并发处理的广泛应用是使得Amdahl定律代替摩尔定律成为计算机性能发展源动力的根本原因,也是人类压榨计算机运算能力最有力的武器。
volatile修饰符,当一个变量被定义为volatile后,则此变量对多有线程可见,即新值修改了值后,其他线程立即得知;但是这种类型不是原子性的, 依然做不到线程安全,书中有实例。
immutable的 对象一定是线程安全的,比如String对象。
绝对线程安全的对象几乎没有,书中演示了Vector对象多线程移除,get时获取不到的情况,依然需要对vector对象加锁才保证了正确的结果。Java API中,绝大多数的线程安全类,都是相对线程安全的。
suspend,resume,setIn,setOut,runFinalizersOnExit等有死锁风险,被废弃。
线程安全最常用的实现,就是互斥同步,是阻塞的,利用synchronized。
本书的后面部分,关于虚拟机执行系统的,关于编译优化的,关于高效并发的,和实际编码、架构等技术性工作的相关性相对较低,在此不进行进一步的深入学习。
# 内存溢出代码示例
### HeapSize OOM -- Java heap space
~~~
class HeapOOM {
public static void main(String[] args) {
java.util.List list = new java.util.ArrayList();
while(true) {
list.add("内存溢出啊,内粗溢出啊!");
}
}
}
~~~
执行java -Xmx2m HeapOOM,堆内存瞬间溢出,报错:java.lang.OutOfMemoryError:Java heap space
### HeapSize OOM -- GC over head limit exceeded
~~~
import java.util.*;
class GCOverHead {
public final static byte[] DEFAULT_BYTES = new byte[12*1024*1024];
public static void main(String[] args) {
List temp = new ArrayList();
while(true) {
temp.add(new byte[1024*1024]);
if(temp.size()>3) {
temp.clear();
}
}
}
}
~~~
执行java -XX:+PrintGCDetails -XX:+UseGCOverheadLimit -Xmn5m -Xmx20m GCOverHead ,据说会报java.lang.OutOfMemoryError:GC over head limit exceeded,意思是不断在做Full GC,每次GC完后释放一点点内存,然后一下子就又满了,不断重复,当次数达到一定量,并且平均FULL GC时间达到一定比例时,就会报错;
但是,我的程序一直就无限循环运行,不报错误。。。
### PermGen OOM
~~~
import net.sf.cglib.proxy.Enhancer;
class ClassPermGenOOM {
public static void main(String[] args) {
while(true) {
createProxy(ClassPermGenOOM.class);
}
}
public static Object createProxy(Class> targetClass) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetClass);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object object,
Method method,Object[] args,
MethodProxy methodProxy) throws Throwable {
return methodProxy.invokeSuper(object,args);
}
});
return enhancer.create();
}
}
~~~
执行java -XX:+TraceClassUnloading -XX:PermSize=10m -XX:MaxPermSize=10 ClassPermGenOOM,会报出java.lang.OutOfMemoryError:PermGen space错我。
### DirectBuffer OOM
~~~
import java.nio.ByteBuffer;
class ByteBufferOOM {
public static void main(String[] args) {
ByteBuffer.allocateDirect(257*1024*1024);
}
}
~
~~~
执行java -XX:MaxDirectMemorySize=256m ByteBufferOOM,则会报出错误java.lang.OutOfMemoryError:Direct bufer memory。
### StackOverflowError
栈溢出,无限递归即可。
死递归和死循环不一样,死循环时,栈空间不会递增;而死递归由于需要记录退回的路径,就必须记住递归过程中的方法调用过程,以及每个方法运行过程中的本地变量,这个我们称之为上下文信息,随着内容的增加,就会占用更多的内存空间,JVM是为了控制它的无限制增长,才做了安全检测的处理。
';
虚拟机运行时数据区 | 存储内容 | 可能发生的溢出错误及优化措施 | 描述 |
堆(Heap) | 对象实例 | OutOfMemoryError: Java heap space,通过调节参数-Xms2048m -Xmx2048m,设置堆的初始内存和最大内存进行优化 | 在虚拟机启动时创建,是VM所管理的内存中最大的一块,是垃圾收集器管理的主要区域,也被称作“GC堆”,堆是所有线程共享的 |
虚拟机栈(VM Stack) | Java方法执行的内存模型:每个方法被执行时,都会同时创建一个栈帧(Stack Frame)用于存储局部变量表(基本数据类型和对象引用类型),操作数栈,动态链接,方法出口等信息 | 单线程可能会报StackOverflowError,多线程可能会报OutOfMemoryError,对应参数是-Xss2m,一般不设这个参数,而是采用默认值 | 生命周期和线程相同,每个方法被调用直至完成的过程,就对应着一个栈帧在虚拟机栈中入栈和出栈的过程,有时会通过减小栈和堆大小的方式优化,以保证可以开更多线程,栈是线程私有的 |
本地方法栈(Native Method Stack) | 本地方法栈和虚拟机栈所发挥的作用非常相似,其区别是:虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈为虚拟机使用到的Native方法服务。 在Sun HotSpot虚拟机中,本地方法栈和虚拟机栈是在一起的。 | ||
方法区(Method Area) | 类信息、常量、静态变量、即时编译器编译后的代码 | OutOfMemoryError: PermGen space,通过调节参数-XX:PermSize=1024m -XX:MaxPermSize=1024m,设置方法区的初始内存和最大内存进行优化 | 有个别名叫Non-Heap,对于HotSpot来说,也被称作“永久代”(Permanent Generation),运行时常量池(Runtime Constant Pool)是方法区的一部分,方法区是所有线程共享的 |
程序计数器(Program Counter Register) | 当前线程所执行的字节码的行号计数器 | 不会溢出 | 是线程私有的 |
直接内存(Direct Memory) | 直接内存不是虚拟机运行时数据区的一部分,是使用Native函数库直接分配的堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用,目的是在一些场景中显著提高性能,因为避免了再Java对和Native堆中来回复制数据;可以通过-XX:MaxDirectMemorySize指定,如果不指定,则默认与Java堆的最大值(-Xmx指定)一样;会报溢出错误:OutOfMemoryError | ||
三大商业虚拟机 | Sun HotSpot,Bea Jrockit,IBM J9,前两个虚拟机都已经被Oracle收购,Sun HotSpot是使用最普遍的虚拟机。 |
使用案例 | 描述 | |
引用计数算法 | COM、AS3、Python、Squirrel | 很难解决对象之间的相互循环引用问题 |
根搜索算法 | Java、C#、Lisp | |
在Java语言里,可以作为GC Roots的对象包括:(虚拟机栈中的/方法区中的类静态属性和常量/本地方法栈中JNI)引用的对象 Java对象的finalize方法,优先级低,运行代价高昂,不确定性大,无法保证各个对象的调用顺序,建议大家完全可以忘记Java中还有这个方法的存在;这个方法最多只会被垃圾收集器调用一次。 |
Serial | 新生代使用,单线程,Stop The World,复制算法 |
ParNew | Serial的多线程版本 |
Parallel Scavenge | 新生代使用,并行,多线程,吞吐量优先,适用于在后头运算而不需要太多交互的任务,复制算法 |
CMS | 老年代使用,并发,多线程,Concurrent Mark Sweep,以获取最短回收停顿时间为目标,标记-清除算法,它的问题是会产生内存碎片 |
Serial Old | 老年代使用,单线程,标记-整理算法,主要Client模式使用,如果在Server模式使用,有两大用途:一个是在JDK1.5及之前的版本中与Parallel Scavenge搭配使用;另一个就是作为CMS收集器的后备预案,在Concurrent Mode Failure的时候使用 |
Parallel Old | Parallel Scavenge的老年代版本,标记-整理算法 |
G1 | Garbage First,收集器技术发展的最前沿成果,标记-整理算法,精确控制停顿时间,极力避免全区域垃圾收集 |
并行:Parallel,不论宏观还是微观,都是同时进行的; 并发:Concurrent,宏观上是同时进行的,微观上不一定是同时进行的,可能是交替执行。 |
案例 | 场景:在线文档网站,硬件升级(CPU变为4个,内存变为16G),出现十几分钟停顿十几秒现象 原因:文档序列化产生大量大对象,进入老年代,导致没有在Minor GC清理掉,Full GC耗时导致的停顿现象 解决:硬件分成5个虚拟机,做成集群,用前端负载;结果速度比硬件升级前有较大提升,也不再停顿 |
两种方式 | 在高性能硬件上部署程序,主要有两种方式: 1.通过64位JDK来实用大内存 2.实用若干个32位虚拟机建立逻辑集群来利用硬件资源 |
结论 | 控制Full GC频率的关键是看应用中绝大多数对象能否符合“朝生夕灭”的原则,即大多数对象的生存时间不应当太长,尤其不能产生成批量的、长生存时间的大对象,这样才能保障老年代空间的稳定; 在大多数网站形式的应用中,主要对象的生存周期都应该是请求级或页面级的,会话级和全局级的长生命对象相对较少。只要代码合理,应当都能实现在超大堆中正常使用而没有Full GC,这样的话,使用超大堆内存时,网站响应速度才比较有保障。 |
案例 | 场景:6个节点的WebLogic集群,有数据在各个节点间共享,开始存在数据库中,后来改为JbossCache全局缓存 原因:既有JBossCache的缺陷,也有实现方式的缺陷,集群间频繁的写操作,当网络情况不能满足传输要求时,重发数据在内存中不断的堆积,很快就产生了内存溢出 |
结论 | 对于部分数据在各个节点共享的场景,使用更为成熟的第三方框架,比如Redis |
案例 | 场景:2G内存,1G堆内存,发送内存溢出,对内存调大为1.6G,溢出反而更频繁了 解决步骤:利用jstat,发现GC正常,Eden区、Survivor区、老年代、永久代均正常,在内存溢出后从系统日志中找到异常对照,有java.nio.ByteBuffer.allocateDirect 原因:案例使用的CometD1.1.1框架,有大量的NIO操作需要用到DirectMemory |
结论 | Direct Memory:可通过-XX:MaxDirectMemorySize调整大小,内存不足时抛出OutOfMemoryError或OutOfMemoryError:Direct buffer Memory; 线程堆栈:可通过-Xss调整大小,内存不足时抛出StackOverFlowError(纵向无法分配,即无法分配新的栈帧)或OutOfMemoryError:unable to create native thread(横向无法分配,及无法建立新的线程); Stock缓存区:每个Socket连接都有Receive和Send两个缓存区,分别占大约37KB和25KB的内存,连接多的话这块内存占用也比较可观。如果无法分配,则可能抛出IOException:Too many open files异常; JNI代码:如果代码中使用JNI调用本地代码库,那本地代码库使用的内存也不在堆中; 虚拟机和GC:虚拟机和GC的代码执行也要消耗一定的内存。 |
案例 | 场景:大并发压力下,请求缓慢,通过监控,发现CPU使用率很高,并且CPU占用不是程序本身,通常情况下,用户应用的CPU占用应该占主要地位。 解决步骤:通过开发人员找到答案,每个用户请求,时都要执行一个外部Shell获得系统的一些信息。这些Shell脚本是通过Java的Runtime.getRuntime().exec()方法调用的。Java虚拟机执行这个命令的过程是:首先克隆一个和当前虚拟机拥有一样环境变量的进程,再用这个新的进程执行外部命令,最后再退出这个进程。如果频繁执行这个操作,系统的消耗会很大,不仅是CPU,内存的负担也很重。 解决:去掉这个shell脚本的执行,改用Java的API去获取这些信息。 |
案例 | 场景;第二个案例的配置,频繁出现集群节点的虚拟机进程自动关闭的现象。 找出问题:有异常日志,java.net.SocketException:Connection reset,这是一个远程断开连接的异常,通过系统管理员了解到最近和第三方远程OA做过对接。经过测试,发现调用远程WebService长达3分钟才返回,并且返回的结果都是连接中断。 原因:由于系统经常调用远程服务,但是两边服务的速度完全不对等 ,时间越长就累积越多的服务没有调用完,导致在等待的线程和Socket连接越来越多,最终超过虚拟机的承受能力后使得虚拟机的进程崩溃。 |
结论 | 及时验证接口服务,尽量在允许的情况下,采用异步消息机制,比如RabbitMQ之类。 |
Java之旅–如何从草根成为技术专家
最后更新于:2022-04-01 20:11:39
微软的各种开发工具、开发技术一向简单易用,容易上手,比较容易的就能出效果,但由于其比较封闭的生态环境,很容易就在技术上止步,相对不容易深入。
Java生态环境很开放,各种开源第三方框架层出不穷,要学习的东西非常多,学习成本相对较高,技术上容易进步。
我个人感觉,Java体系的从业者平均技术水平应该是大于微软体系的,下面开始学习之旅。
# 最基础的语法和常用类--《Java无难事》
首先配置Java开发环境,到官网下载一个Java SE的SDK,安装到你的机器上,安装完毕后,在环境变量-〉系统变量中新增系统变量JAVA_HOME,其值为JDK安装路径,比如:D:\Java\jdk1.6.0_39,然后再path变量的头部新增:%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;
在cmd中执行命令:java -version,如果输出版本号,则说明最基本的Java开发环境配置成功了,那么我们可以使用EditPlus之类的文本编辑器书写第一个Hello World程序了,命名为HelloWorld.java。
~~~
public class HelloWorld
{
public static void main(String[] args)
{
System.out.println("Hello World");
}
}
~~~
进入cmd,执行命令 javac HelloWorld.java编译这个程序,执行命令 java HelloWorld 就可以看到cmd中正确的打印出了“Hello World”字符串。
Wornderful,是不是应该幸福一下呢 ,大笑现在我们开始基础语法和常用类的学习吧。
怎么学呢?看厚厚的书吗?太浪费时间了。建议还是看视频吧,推荐:孙鑫的 《Java无难事》,可以去优酷下载12讲视频及配套的ppt讲解,10/11/12讲可以不用看,以后的工作过程中基本用不到;把视频多看几遍,理解透彻了,里面的所有例子,都用EditPlus多写几遍,调通,开始是抄,后来是自己写,做到可以自行写对所有的例子代码写对调通的时候,可以说已经Java入门了。
这第一步大约累计需要40小时。
在这个步骤,不要试着去阅读800多页的《Java编程思想》,更不要试着去阅读关于“设计模式”的书籍,我们需要的是尽量压缩学习时间,用这40个小时的时间,掌握基础的Java,在这个最低可以工作的基础上,先去做一个蹩脚的Java程序员,先到实操环境中去实践。
# JAVA IDE 开发环境(Eclipse)
这第二步的目的是开发一个简单的Web程序,数据库的CRUD。
Eclipse就到官网下载一个Eclipse IDE for Java EE Developers,版本随便:Kepler/Juno/Indigo都行。
这是最流行的,绿色的,解压就能用。
执行eclipse.exe允许Eclipse,会提示你选择一个WorkSpace(工作区),WorkSpace可以看作C#开发工具Visual Studio的解决方案(.sln)
试着建立几个Java Project,试试一个Project如何引用其他的Project,就像Visual Studio中进行项目引用一样;Eclipse也可以引用JAR包,就像Visual Studio进行DLL引用一样。Java Project这种工程都可以导出成JAR包。
Eclipse也是可以直接连接SVN的,安装插件,就像Visual Studio安装Visual SVN一样。
下面说下Eclipse集成Web容器(Tomcat):
开发环境一般都用Tomcat或者Jetty,好处就是启动快,是Jboss的1/2甚至1/3。
官网下载一个Tomcat,绿色的,解压就可以用,解压后,cmd运行bin/startup.bat,就开启了Tomcat,在浏览器输入http://localhost:8080,就可以看到Tomcat的默认站点了;为了方便调试程序,我们是需要在Eclipse中启动停止Tomcat的,关于具体的设置方法,可以Baidu一下,很多资料。
Eclipse默认支持Tomcat的版本到7,Jboss的版本到5;如果想在Eclipse中启动停止更高版本的Web容器或者其他的容器,就要安装插件了。
开发一个简单的Web程序:
就用Spring+SpringMVC+MyBatis来练手,最好是看别人的例子,我这里也会逐渐放上自己写的例程。
以上环节走通,大约1周,也就调通了,但是逐步的理解加深,必须到工作中逐步的提高了。
提高是一步一步的,真的。
# Java的常用框架:Spring、ORM、MVC
Spring+SpringMVC+MyBatis是值得学习的。
MAVEN:很多工作都用MAVEN管理项目,非常值得用,学习使用也就是几天的事情,很快就熟。Maven打包和Export导出的war(jar)包,内容可能是不一样的,比如pom.xml里面的
~~~
javax.servlet
servlet-api
2.5
provided
~~~
用Maven打包:mvn clean package -Dmaven.test.skip=true -U,会排除包servlet-api-2.5.jar,而Export会把这个包打进去,从而可能导致与容器(Jboss/Tomcat等都是自带servlet/jsp等包的)里面的包冲突而出现问题。
WebService框架:CXF/Axis2等,最好都不要学习了,工作中能不用就不用吧,用DUBBO/Thrift替代。
Log4/QuartZ:不论哪种语言,都是会使用的吧。
这些也都是循序渐进的,工作中学习,工作中进步。
# Linux(rhel-server-6.2)
Java的生产环境基本都是部署在linux上的,常用的linux命令要了解。
容器的性能调优,要逐渐的学习。
个人感觉,最事半功倍的学习,是在工作中学习,学习中工作。有一个良好的氛围很重要。
# JVM优化
**Tomcat增加内存:**catalina.sh第一行增加:set JAVA_OPTS='-Xms1024m -Xmx1024m -XX:MaxNewSize=512m -XX:MaxPermSize=512m' ,linux不需要set。
**Jboss5增加内存:**/usr/java/jboss-eap-5.1/jboss-as/bin/run.conf
if [ "x$JAVA_OPTS" = "x" ]; then
JAVA_OPTS="-Xms2048m -Xmx2048m -XX:MaxPermSize=1024m -Dorg.jboss.resolver.warning=true -Dsun.rmi.dgc.client.gcInterval=3600000 -Dsun.rmi.dgc.server.gcInterval=3600000 -Dsun.lang.ClassLoader.allowArraySyntax=true -Dorg.terracotta.quartz.skipUpdateCheck=true -Dspring.profiles.active=production"
fi
JAVA_OPTS="$JAVA_OPTS -Djava.rmi.server.hostname=本机IP地址"
另一台机器(CPU: Xeon 5620 2G:24线程,内存32G)有如下配置:
JAVA_OPTS="-Xss1280k -Xms15g -Xmx15g -Xmn8g -XX:MaxPermSize=300M -XX:+UseConcMarkSweepGC -XX:ParallelGCThreads=16 -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/applogs/jbosslogs -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:/data/applogs/jbosslogs/heap_trace.log -Dorg.jboss.resolver.warning=true -Dsun.rmi.dgc.client.gcInterval=3600000 -Dsun.rmi.dgc.server.gcInterval=3600000 -Dsun.lang.ClassLoader.allowArraySyntax=true -Dmemcached_config=file:/etc/yougouconf/memcached_cluster.xml -Dspring.profiles.active=production"
**内存溢出PermGen解决,增加方法区内存:**
-server -Xms768m -Xmx768m -XX:PermSize=1024m -XX:MaxPermSize=1024m
内存问题是需要了解的,找本JVM的书看看,这里简单介绍 一下JVM内存:
[http://hi.baidu.com/zzusec/item/73895f11f405d2f89c778a3d](http://hi.baidu.com/zzusec/item/73895f11f405d2f89c778a3d)
[http://blog.csdn.net/kthq/article/details/8618052](http://blog.csdn.net/kthq/article/details/8618052)
[http://www.open-open.com/lib/view/open1324736648468.html](http://www.open-open.com/lib/view/open1324736648468.html)
JVM优化的核心,实际就是调整各个JVM内存块为合适的大小,使用合适的垃圾收集器。
**jvm默认参数查看:**
java -XX:+PrintFlagsFinal 显示所有可设置参数及默认值,可结合-XX:+PrintFlagsInitial与-XX:+PrintFlagsFinal对比设置前、设置后的差异,方便知道对那些参数做了调整。
java -XX:+PrintFlagsInitial 可以获取到所有可设置参数及值(手动设置之后的值),这个参数只能使用在Jdk6 update 21以上版本(包括该版本)。
java -XX:+PrintCommandLineFlags 显示出JVM初始化完毕后所有跟最初的默认值不同的参数及它们的值。
jinfo -flag MaxPermSize pid号 :jinfo 查询MaxPermSize 参数的值
# 如何利用各种语言
没有一种语言能包打天下,都有其适用的场景。
基础性语言:Html、JavaScript、CSS
通讯框架:Thrift/Hessian/Rest/Soap,常用语言都支持,性能Thrift>Hessian>Rest>Soap;Dubbo,如果公司没有强大的研发力量开发基础框架的话,这是一个很好的选择
Web开发:使用最为广泛的是Java体系,C#也很多,现在有一些使用Ruby on Rails
桌面开发:C#
PDA开发:比如仓储的手持设备,是WinCE系统,所以还是要用C#开发,框架是.Net Compact FrameWork
手机开发:C、Java
所以,需要根据业务场景,选择合适的语言,学习来说,是广泛而深入的学习,不囿于具体语言。如果是新进入IT行业,首选的学习目标,建议还是Java。
# 工作两到三年之后。。。
你是否已经**足够努力**的工作了两年,你是否已经学会使用(不是掌握)了很多很多的Java生态的框架,你是否已经走在了成为一名“高级软件工程师”,并希望成为一名“资深工程师/架构师”的路上,再深入的看书吧![大笑](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569e21abc5518.gif)
《Java编程思想》,第四版、第五版,快速看一遍,并随手翻翻,受益良多;
《Java与模式》,这是一本2002年的说,没错,10多年前的书,可以很快阅读,并和这两年积累的经验相互印证;
《Spring 3.X企业应用开发指南》,工作中一定用了Spring吧,常用的框架都会给予Spring进行配置吧,这个时候有必要把Spring知识系统的串联一下了,Spring是因为JDK 有了反射才有可能被创造出来的,IoC只是基础,AOP才是目的,利用AOP简化我们的开发工作;如果有更多时间,可以通过《Spring Internals》这本书学习研究一下Spring源码;
《深入理解Java虚拟机》,虚拟机的知识,还是有必要学习一下的;常用的JDK源码,以及《算法导论》,也有必要研究一下;
关于Web服务器(Nginx、Apache)的书,关于Servlet容器(JBoss、Tomcat、Jetty)的书;
如果还有时间,扩充一下技术广度和业务广度吧;
这个阶段,对于进阶是最最重要的几年,大多数人,在这个阶段,都是把以前的编程经验,又重复了很多年,很多年,没有突破和进阶,令人遗憾。。。
# 工作六到八年之后。。。
你已经**足够努力**的工作了这么多年,真的应该恭喜一下,不论是否真的已经成为专家,你都是一个有恒心的人,真的是值得敬佩的。
但是这个时候,至少应该已经是技术专家/高级架构师级别了吧![大笑](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569e21abc5518.gif)
。
**我个人觉得,不论什么领域,成为专家有3条是最重要的:**
1、持续的努力,这是不可或缺的;
2、勤于思考,走了更少的弯路,这决定的是在路上的时间;
3、受到了高人的指导,或者自行模仿了高人,至少是仰望了一下,所谓:“身虽不能至,而心向往之”,这是理想,是驱动力。
';
前言
最后更新于:2022-04-01 20:11:36
> 原文出处:[Java之旅](http://blog.csdn.net/column/details/java-travel.html)
作者:[puma_dong](http://blog.csdn.net/puma_dong)
**本系列文章经作者授权在看云整理发布,未经作者允许,请勿转载!**
# Java之旅
> 介绍Java的学习历程、常用知识点,从硬件、操作系统、JDK、Spring、容器角度,叙述对Java的理解。如题,就像一次旅程,记录下游记或者攻略,希望对您的Java旅程,有一定借鉴作用。
';