特征
雄狮重达220公斤,包括尾巴的全长达2.8米,母狮体型较公狮小,但体重仍重达160公斤,相当于两个成年男人的重量,是非洲最顶级的掠食者,公狮颈部周围包著一层的鬃毛名为”狮鬃”,狮鬃颜色不等,包括金褐、咖啡、黑色,有些狮的狮鬃浓密而杂乱,有的稀疏且平顺,科学家研究发现,狮鬃又黑又浓又乱的公狮因为长相较吓人因此很少受攻击,狮鬃太少颜色太淡看起来不吓人的公狮反而常受到攻击,狮鬃在打猎埋伏上并没有任何的帮助。有些狮体色为纯白,并不是因为白化病,而是色素细胞的突变,白狮会被认为身体虚弱,且会影响到猎食,但白狮十分的稀少。
生活环境
过去从欧洲南部、西亚、印度和非洲都有狮。冰川期间一个亚种还在中欧和北美洲生存过,但冰川期后它们就消失了。按当时学者的报道,到古希腊时期在巴尔干半岛还有狮生存。一般认为欧洲的狮在公元1世纪由于人类的活动灭绝。
今天绝大多数狮生活在非洲撒哈拉沙漠以南,撒哈拉沙漠以北的狮于1940年代灭绝。20世纪时在亚洲的狮几乎全部被消灭,只有在印度的一个自然公园还有少数幸存。
过去除森林外,狮在所有的生态环境中都有,今天它们的生存环境大大地缩小了。它们比较喜欢草原,也在旱林和半沙漠中出现,但不生存在沙漠和雨林中。
社会结构
与其它大型猫不同,狮是群体生活。一个狮群主要由互相之间有亲缘关系的雌兽组成。地形和猎物的多少决定一个群的大小,一个群可以由3至30只狮组成。雄兽在一个群内只待一段时间,此后它往往去找另一个群。虽然如此在一个狮群中雄狮的地位比雌狮的高。一般在一个狮群中只有一只成年的雄狮,幼狮在狮群中一般待三年,此后雄性的青年被驱逐出群,而雌兽则留在群内。
年轻的雄狮组成不稳定的小群过着游荡的生活,直到它们自己成为一个狮群的首领。一般它们要到六岁或更老才能达到这个地位。
往往一个狮群的领导地位会被另一个雄狮占据,一般当老的雄狮老了或虚弱了时,就会有年轻的雄狮向它挑战,战败的雄狮不是死亡就是逃亡。假如新来的雄狮战胜,它一般将它前任的幼兽杀死,这样雌兽就比较容易和它交配了。
一个狮群的领地面积从20到400平方千米不等。一个领地的大小与狮群的大小和领地内猎物的多少有关。狮群一般用粪便、尿和从远方就听得见的呼叫声来标志它们的领地。
也有的狮群没有领地而过着游荡的生活。
繁殖
虽然雄狮在一个狮群中占最高的地位,但只有在一只雌狮同意的情况下它才能与它交配。假如雌狮愿意,它就趴到地上,让雄狮跨上。从雌狮对雄狮的态度上一般可以看得出雄狮在群中的地位有多高,以及它是否在下一次角斗中会被驱逐。
雌狮的怀孕期一般是四个月,此后它生二至四只幼崽。幼崽的体重一般是1.5千克。幼崽不但可以在它的母亲身上吸奶,而且可以在群中的每只雌狮吸奶。对幼崽的抚养是整个狮群的任务。幼崽的吸奶期是四个月,此后它们还跟着母亲约两年。雌狮一般在三年后,雄狮在五年后性成熟。亦曾有研究指,雄狮跟雌狮成功交配出来超过一岁的小狮子在三只里只有一只。
狮可以活20年。一般只有雌狮会活这么长。雄狮一般在此之前就会被一只年轻的雄狮杀死或驱逐,它们一般不会再找到一个群,往往饿死。一般雄狮的寿命不超过12年(与一个小学生的平均年龄持平),相反一般雌狮的寿命约15-18岁(与一个中学生的平均年龄持平)。在动物园中有些狮子活到34岁。
食物
一般雌狮猎取食物,雄狮只在它们年轻时,鬃毛还没有完全成熟时狩猎。它们深色的鬃毛使它们比较容易被看见,因此不太容易获得成功。一般它们潜伏靠近猎物,然后跳起将猎物扑倒。小的猎物一般被咬断头颈,大的猎物的头颈被撕破或被压抑窒息。
一般出猎的时间是夜晚或清晨凉爽的时候。
猎物中包括羚羊、小羚羊、牛羚和斑马,也包括狒狒、兔、鸟,有时甚至鱼。猎物被捕获后按群内地位的高低捺食:首先是雄狮,然后是地位最高的雌狮,幼狮最后。往往会因此产生群内的地位争端,这样的争斗往往会导致受伤。
狮子往往也食腐食。被从群内驱逐的雄狮一般只食尸体。它们抢夺其它食肉动物如豹或猎豹的猎物。狮子也从鬣狗口中抢夺食物。

概要
在亚洲许多国家的文化中,虎是美丽、勇敢、力量的象征,人们畏惧虎的凶猛,又希望具有虎的强壮,以征服它来证明力量。虎与人类的关系密切,人类的活动使虎的栖息地缩小、分隔,人虎冲突和对虎的利用造成了对虎的大肆捕杀,目前这个物种已经十分濒危。
进化
虎由古食肉动物进化来。大型食肉类在距今700万年的新生代第三纪上新世出现并逐渐发展。古食肉类中的猫形类进化出多个分支,其中一只是古猫类,古猫类又分化为恐猫、真剑齿虎类和真猫,经过第四纪冰川期,只有真猫类存活下来,分化为猫族和豹族两个分支。现在的虎就是从真猫类中的豹族演化而来。 在虎的各个亚种中,华南虎是各亚种的祖先,它们的头骨结构最接近于原始的虎。
外形特征
虎为大型猫科动物,斑纹独特,身体被满浅黄到红色毛,有黑色至棕色条纹。不同亚种的虎的体形大小有差异,北方虎的体形大,颜色较浅;南方的体形较小,颜色较深。虎的虹膜呈黄色,圆形瞳孔。耳背为黑色,上有明显的白斑,有科学家认为这有助于幼崽在野外跟随母亲活动。虎的前后肢十分有力。掌垫的大小与虎的年龄有关,在种群调查中,研究人员以此鉴别个体。虎的尾较长,具环形斑纹,尾尖没有长毛,为黑色。
习性
虎是一种孤独的森林食肉动物,一般每只老虎有自己的领地,除了交配时期,从不和其他虎交往,雌虎独自生产和喂养幼虎,平均每胎产子2.46个,在野外,出生幼子的死亡率为30—40%。当幼虎成年后,雌虎将领地遗留给它,独自去寻找新领地。每个虎占领一块领地后,就会将本地所有大型食肉动物如狼、豹等赶走,所谓“占山为王”。老虎以鹿、獐、羊等食草动物为食,必须有足够的猎食领地以维持生命。虎一般采取潜伏袭击的猎食方式,从猎物后方攻击,在食物严重短缺时,也会攻击人。印度农民用头后戴假面具的方式避免遭受老虎攻击,因为虎以为假面具是人以正面对它,它决不会从正面攻击猎物。
虎与文化
虎的象征意义在亚洲文化中得到最大的体现,被看作是美丽、严肃、勇猛的象征。在中国,虎的形象随处可见,许多神明信仰以虎为象征,如西王母、保生大帝、玄坛真君等。在台湾民间信仰,则奉城隍、山神、土地公所乘之虎为神,谓曰虎爷,有驱邪之能。虎字起源极早,殷墟甲骨文中就有虎字,现在汉字中的虎就很像一只虎。民间传说,汉字中的“王”就来自于老虎前额上的斑纹,还有许多成语、民间俗语中都有虎出现。虎在十二生肖中排名第三位。
早在5000年前的印度河古文化中(今巴基斯坦一带)就发现有雕刻在图章上的虎的形象。印度教种有一个骑虎的女神杜伽(Durga),这个女神的形象在印度随处可见,多出现在火车两侧。韩国的野生虎虽已经灭绝,但韩国人仍称自己的国度为“青龙白虎之邦”,在1988年汉城奥运会2002年韩日世界杯上,虎都被定为吉祥物。

外形特征
  外形有小(郊狼)、中(森林狼)、大(草原狼),吻尖长,眼角微上挑。因为产地和基因不同,所以毛色也不同。常见灰黄两色,还有黑红白等色,个别还有紫蓝等色,胸腹毛色较浅。腿细长强壮,善跑。灰狼的体重和体型大小各地区不一样,一般有随纬度的增加而成正比增加的趋势这一说法. 一般来说,肩高在( 26-36英寸) ,体重32-62公斤( 70-135磅) 野生狼体重记录————其中1939年在阿拉斯加被打死的一只,,当时80公斤( 175磅) . 最小的狼是阿拉伯狼,雌性的狼有的体重可低至10公斤( 22磅) 狼群适合长途迁行捕猎.其强大的背部和腿部,能有效地舒展奔跑.
生活习性
狼是群居性极高的物种。一群狼的数量大约在5到12只之间,在冬天寒冷的时候最多可到四十只左右,通常以家庭为单位的家庭狼由一对优势对偶领导,而以兄弟姐妹为一群的则以最强一头狼领导。狼群有领域性,且通常也都是其活动范围,群内个体数量若增加,领域范围会缩小。群之间的领域范围不重叠,会以嚎声向其他群宣告范围。幼狼成长后,会留在群内照顾弟妹,也可能继承群内优势地位,有的则会迁移出去(大都为雄狼)而还有一些情况下会出现迁徙狼,以百来头为一群,有来自不同家庭等级的各类狼,各个小团体原狼首领会成为头狼,头狼中最出众的则会成为狼王。野生的狼一般可以活12——16年,人工饲养的狼有的可以活到二十年左右。 奔跑速度极快,可达五十五公里左右,持久性也很好。它们有能力以速度10公里/小时(六英里)长时间奔跑 , 并能以高达近65公里/小时速度( 40英里) 追猎冲刺。 如果是长跑,它的速度会超过猎豹。智能颇高,可以气味、叫声沟通。狼是以肉食为主的杂食性动物,是生物链中极关键的一节。
生长繁殖
  狼的怀孕期为61天左右。低海拔的狼一月交配,高海拔则在四月交配。
  小狼两周后睁眼,五周后断奶,八周后被带到狼群聚集处。
  狼成群生活,雌雄性分为不同等级,占统治地位的雄狼和雌狼随心所欲进行繁殖,处于低下地位的个体则不能自由选择。雌狼产子于地下洞穴中,雌狼经过六十叁天的怀孕期,生下叁只到九只小狼,也有生十几只的。没有自卫能力的小狼,要在洞穴里过一段日子,公狼负责猎取食物。小狼吃奶时期大约有五、六个月之久,但是一个半月也可以吃些碎肉。叁、四个月大的小狼就可以跟随父母一道去猎食。半年后,小狼就学会自己找食物吃了。狼的寿命大约是十二到十四年。在群体中成长的小狼,非但父母呵护备至,而且,族群的其他份子也会爱护有加。狼和非洲土狼会将杀死的猎物,撕咬成碎片,吃下腹内,待回到小狼身边时,再吐出食物反哺。赤狼有时也会在族群中造一育儿所,将小狼集中养育,由母赤狼轮流抚育小狼,毫无怨尤。因此,我们可以说狼的家庭观念极强。
狼的行为模式和身体语言
一般占优势主导地位的狼会身挺高腿直,神态坚定,耳朵是直立向前。
  往往尾部纵向卷曲朝背部。这种显示的是级别高主导地位的狼可能一直盯着一个唯唯诺诺的地位低下的狼。
  活跃—玩耍时,狼会全身伏低,嘴唇和耳朵向两边拉开,有时会主动舔或快速伸出舌头。
  愤怒—愤怒的狼的耳朵会竖立,背毛也会竖立,唇可卷起或后翻,门牙露出,有时也会弓背或咆哮。.
  恐惧—害怕时狼会试图把它的身子显得较小,从而不那么显眼,或拱背防守,尾收回。
文化
狼本身并无所谓狡猾或贪婪之本性,但狼在狩猎时的习性以及智慧,往往被引申为凶残恶毒,并用来形容贪婪,凶邪或忘恩负义的人,如“狼心狗肺”,“狼狈为奸”,“狼子野心”等。另外欧亚中部游牧民族因其生存环境,可能会崇拜合群,勇猛的狼,突厥、乌孙都以狼为图腾。

教条示龙场诸生(明.王守仁)

  诸生相从于此,甚盛。恐无能为助也,以四事相规,聊以答诸生之意。一曰立志,二曰勤学,三曰改过,四曰责善。其慎听,毋忽!
立志
  志不立,天下无可成之事。虽百工技艺,未有不本于志者。今学者旷废隳惰,玩岁愒时,而百无所成,皆由于志之未立耳。故立志而圣,则圣矣;立志而贤,则贤矣;志不立,如无舵之舟,无衔之马,漂荡奔逸,终亦何所底乎?昔人所言:“使为善而父母怒之,兄弟怨之,宗族乡党贱恶之,如此而不为善,可也。为善则父母爱之,兄弟悦之,宗族乡党敬信之,何苦而不为善、为君子?使为恶而父母爱之,兄弟悦之,宗族乡党敬信之,如此而为恶,可也。为恶则父母怒之,兄弟怨之,宗族乡党贱恶之,何苦必为恶、为小人?”诸生念此,亦可以知所立志矣。
勤学
  已立志为君子,自当从事于学。凡学之不勤,必其志之尚未笃也。从吾游者,不以聪慧警捷为高,而以勤确谦抑为上。诸生试观侪辈之中,苟有“虚而为盈,无而为有”讳己之不能,忌人之有善,自矜自是,大言欺人者,使其人资禀虽甚超迈,侪辈之中,有弗疾恶之者乎?有弗鄙贱之者乎?彼固将以欺人,人果遂为所欺,有弗窃笑之者乎?苟有谦默自持,无能自处,笃志力行,勤学好问;称人之善,而咎己之失;从人之长,而明己之短;忠信乐易,表里一致者;使其人资禀虽甚鲁钝,侪辈之中,有弗称慕之者乎?彼固以无能自处,而不求上人,人果遂以彼为无能,有弗敬尚之者乎?诸生观此,亦可以知所从事于学矣!
改过
  夫过者,自大贤所不免;然不害其卒为大贤者,为其能改也。故不贵于无过,而贵于能改过。诸生自思,平日亦有缺于廉耻忠信之行者乎?亦有薄于孝友之道,陷于狡诈、偷刻之习者乎?诸生殆不至于此。不幸或有之,皆其不知而误蹈,素无师友之讲习规饬也。诸生试内省,万一有近于是者,固亦不可以不痛自悔咎;然亦不当以此自歉,遂馁于改过从善之心。但能一旦脱然洗涤旧染,虽昔为盗寇,今日不害为君子矣!若曰吾昔已如此,今虽改过而从善,人将不信我,且无赎于前过,反怀羞涩疑沮,而甘心于污浊终焉,则吾亦绝望尔矣!
责善
  “责善,朋友之道;”然须“忠告而善道之”,悉其忠爱,致其婉曲,使彼闻之而可从,绎之而可改,有所感而无所怒,乃为善耳!若先暴白其过恶,痛毁极诋,使无所容,彼将发其愧耻愤恨之心;虽欲降以相从,而势有所不能。是激之而使为恶矣!故凡讦人之短,攻发人之阴私,以沽直者,皆不可以言责善。虽然,我以是而施于人,不可也;人以是而加诸我,凡攻我之失者,皆我师也,安可以不乐受而心感之乎?某于道未有所得,其学卤莽耳。谬为诸生相从于此.每终夜以思,恶且未免,况于过乎?人谓“事师无犯无隐”,而遂谓师无可谏,非也。谏师之道,直不至于犯,而婉不至于隐耳。使吾而是也,因得以明其是;吾而非也,因得以去其非。盖教学相长也。诸生责善,当自吾始。

三戒(唐.柳宗元)

临江之麋
临江之人,畋得麋麑,畜之。入门,群犬垂涎,扬尾皆来。其人怒,怛之。自是日抱就犬,习示之,使勿动,稍使与之戏。积久,犬皆如人意。麋麑稍大,忘己之麋也,以为犬良我友,抵触偃仆,益狎。犬畏主人,与之俯仰甚善。然时啖其舌。
三年,麋出门,见外犬在道甚众,走欲与为戏。外犬见而喜且怒,共杀食之,狼藉道上。麋至死不悟。

黔之驴
黔无驴,有好事者船载以入。至则无可用,放之山下。虎见之,庞然大物也,以为神。蔽林间窥之,稍出近之,慭慭然,莫相知。
他日,驴一鸣,虎大骇,远遁,以为且噬己也,甚恐。然往来视之,觉无异能者,益习其声,又近出前后,终不敢搏。稍近益狎,荡倚冲冒,驴不胜怒,蹄之。虎因喜,计之曰:“技止此耳!”因跳踉大㘎,断其喉,尽其肉,乃去。
噫,形之庞也类有德,声之宏也类有能,向不出其技,虎虽猛,疑畏卒不敢取;今若是焉,悲夫!

永某氏之鼠
永有某氏者,畏日,拘忌异甚。以为己生岁直子,鼠,子神也,因爱鼠,不畜猫犬,禁僮勿击鼠。仓廪庖厨,悉以恣鼠不问。
由是鼠相告,皆来某氏,饱食而无祸。某氏室无完器,椸无完衣,饮食大率鼠之余也。昼累累与人兼行,夜则窃啮斗暴,其声万状,不可以寝,终不厌。
数岁,某氏徙居他州。后人来居,鼠为态如故。其人曰:“是阴类恶物也,盗暴尤甚,且何以至是乎哉?”假五六猫,阖门,撤瓦灌穴,购僮罗捕之。杀鼠如丘,弃之隐处,臭(上自下死)数月乃已。
呜呼!彼以其饱食无祸为可恒也哉!

JNI实战

JNI即是Java Native Interface,主要用来Java和其他语言之间的交互,多半是和平台依赖的调用、其他高级语言的库甚至是低级语言交互。相信大家平时在JDK中看到很多native的方法,但是自己写JNI应用的会确很少。这次因为在进行开发中,遇到需要Java调用一个现成的成熟的功能完善的C语言应用库,因此 JNI便成了Java和C库之间的桥的功能。既然是初探,那么下面我们就从HelloWorld开始吧:虽然Java是一个跨平台的语言,但是C语言是一个和平台密切相关的语言;所以针对Unix、Linux、Windows等不同的操作系统,C语言的预编译,动态库的创建等都有一些差别,也需要我们分别进行处理之。下面仅基于Sun的Unix平台Sun Solaris开发为例。
1.首先声明第一个Java native程序:HelloWorld.java。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class HelloWorld {
static {
init();
}

private native int hello();

public int sayHello() {
return hello();
}

/**
* @param args
*/

public static void main(String[] args) {
HelloWorld hw = new HelloWorld();
System.out.println("Say in Java:" + hw.sayHello());
}

private static void init() {
System.out.println("Starting to load HelloWorld lib.");
try {
System.loadLibrary("HelloWorld");
} catch (Throwable t) {
System.out.println("Load unsuccessfully.");
t.printStackTrace();
System.exit(-1);
}
System.out.println("Load successfully.");
}
}

2.编译该Java文件。

1
javac HelloWorld.java

3.生成相应的C语言的HelloWorld.h头文件,javah HelloWorld,头文件内容如下:(其中,static Java方法和非static Java方法经过javah -jni生成的C头文件是有区别的,差异在于参数,static Java方法生成的相应的C方法的第二个参数是jclass类型,非static Java方法生成的相应的C方法的第二个参数是jobject类型。)

1
more HelloWorld.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloWorld */

#ifndef _Included_HelloWorld
#define _Included_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloWorld
* Method: hello
* Signature: ()I
*/

JNIEXPORT jint JNICALL Java_HelloWorld_hello
(JNIEnv *, jobject);


#ifdef __cplusplus
}
#endif
#endif
4.开始编写第一个c语言的JNI的本地(native)方法:
vi HelloWorld.c
#include <jni.h>
#include "HelloWorld.h"
#include <stdio.h>

JNIEXPORT jint JNICALL Java_HelloWorld_hello
(JNIEnv* env, jobject target)
{

printf("Hello World in C program!!!\n");
return 2;
}

5.生成Unix下的动态库.so文件:

1
cc -G -I. -I$JAVA_HOME/include -I$JAVA_HOME/include/solaris HelloWorld.c -o libHelloWorld.so

在当前目录下看到新生成的libHelloWorld.so文件。也许你会注意到我们在Java程序中System.loadLibrary()加载的是库HelloWorld,那么这其实是一个命名的规范而已。以lib开头的.so文件即是Unix下的动态库,而JNI加载器通过HelloWorld这个名字实际上希望加载的动态库是libHelloWorld.so。
6.设置环境变量,查看LD_LIBRARY_PATH并设置:

1
vi .cshrc

1
2
setenv LD_LIBRARY_PATH '.:/usr/local/lib:$LD_LIBRARY_PATH'
source .cshrc

这个环境变量设置的是Java的动态库加载器需要加载的C动态库所在的路径。因此,我们生成的libHelloWorld.so一定要在这个环境变量中设置,在这个例子中我就把当前目录.加到环境变量LD_LIBRARY_PATH了:)
7.执行HelloWorld文件,java HelloWorld,在标准输出上看到:

1
2
3
4
Starting to load HelloWorld lib.
Load successfully.
Hello World in C program!!!
Say in Java:2

一切Okey,如此的easy,一阵狂喜!接下来就是将这个例子运用于JNI的开发之中了。

Resource:
《Java Native Interface: Programmer’s Guide and Specification》
http://java.sun.com/docs/books/jni/html/start.html#27008
http://linuxmafia.com/faq/Admin/ld-lib-path.html
https://www6.software.ibm.com/developerworks/cn/education/java/j-jni/tutorial/j-jni-2-15.html

Java NIO TCP编程

在Java1.4以前,Java的网络编程是只有阻塞方式的,在Java1.4以及之后,Java提供了非阻塞的网络编程API.从Java的发展来看,由于Java的快速发展,JVM性能的提升,涉足到服务端应用程序开发也越来越多,要求高性能的网络应用越来越多,这是Java推出非阻塞网络编程的最主要原因吧。
对我而言,以前的大部分服务端应用主要是搭建在应用服务器之上,所以通讯这部分工作都是有应用服务器来实现和管理的。这次由于通讯和协议,我们必须自己实现一个能处理大量并发客户端的高性能并行处理的Java服务端程序。因此,选择非阻塞的处理方式也是必然的。我们首先来看看阻塞的处理方式:
在阻塞的网络编程方式中,针对于每一个单独的网络连接,都必须有一个线程对应的绑定该网络连接,进行网络字节流的处理。下面是一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public static void main(String[] args) {
try {
ServerSocket ssc = new ServerSocket(23456);
while (true) {
System.out.println("Enter Accept:");
Socket s = ssc.accept();
try {
(new Thread(new Worker(s))).start();
} catch (Exception e) {
// TODO
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
}

}

public static class Worker implements Runnable {
private Socket s;

private boolean running = true;;

public Worker(Socket s) {
this.s = s;
}

public void run() {
try {
InputStream is = s.getInputStream();
OutputStream os = s.getOutputStream();
while (running) {
byte[] b = this.readByLength(is, 1024);
this.process(b);
}
} catch (Throwable t) {
// TODO
t.printStackTrace();
}
}

private byte[] readByLength(InputStream is, int contLen) throws IOException {
byte[] b = new byte[contLen];
int off = 0;
int length = 0;
while ((length = is.read(b, off, contLen - off)) >= 0) {
off = +length;
if (off >= contLen) {
break;
}
}
return b;
}

private void process(byte[] b) {

}
}

在这段代码中,我们看到有两个阻塞的方法,是ServerSocket的accept()方法;和InputStream的read()方式。因此我们需要两类型的线程分别进行处理。而且每一个阻塞方法所绑定的线程的生命周期和网络连接的生命周期是一致的。基于以上的原因,NIO应运而生,一方面,为每一个网络连接建立一个线程对应,同时每一个线程有大量的线程处于读写以外的空闲状态,因此希望降低线程的数量,降低每个空闲状态,提高单个线程的运行执行效率,实际上是在更加充分运用CPU的计算、运行能力(因为,如果有大量的链路存在,就存在大量的线程,而大量的线程都阻塞在read()或者write()方法,同时CPU又需要来回频繁的在这些线程中间调度和切换,必然带来大量的系统调用和资源竞争.);另外一方面希望提高网络IO和硬盘IO操作的性能。在NIO主要出现了三个新特性:
1.数据缓冲处理(ByteBuffer):由于操作系统和应用程序数据通信的原始类型是byte,也是IO数据操作的基本单元,在NIO中,每一个基本的原生类型(boolean除外)都有Buffer的实现:CharBuffer、IntBuffer、DoubleBuffer、ShortBuffer、LongBuffer、FloatBuffer和ByteBuffer,数据缓冲使得在IO操作中能够连续的处理数据流。当前有两种ByteBuffer,一种是Direct ByteBuffer,另外一种是NonDirect ByteBuffer;ByteBuffer是普通的Java对象,遵循Java堆中对象存在的规则;而Direct ByteBuffer是native代码,它内存的分配不在Java的堆栈中,不受Java内存回收的影响,每一个Direct ByteBuffer都是直接分配的一块连续的内存空间,也是NIO提高性能的重要办法之一。另外数据缓冲有一个很重要的特点是,基于一个数据缓冲可以建立一个或者多个逻辑的视图缓冲(View Buffer).比方说,通过View Buffer,可以将一个Byte类型的Buffer换作Int类型的缓冲;或者一个大的缓冲转作很多小的Buffer。之所以称为View Buffer是因为这个转换仅仅是逻辑上,在物理上并没有创建新的Buffer。这为我们操作Buffer带来诸多方便。
2.异步通道(Channel):Channel是一个与操作系统紧密结合的本地代码较多的对象。通过Channel来实现网络编程的非阻塞操作,同时也是其与ByteBuffer、Socket有效结合充分利用非阻塞、ByteBuffer的特性的。在后面我们会看到具体的SocketChannel的用法。
3.有条件的选择(Readiness Selection):大多数操作系统都有支持有条件选择准备就绪IO通道的API,即能够保证一个线程同时有效管理多个IO通道。在NIO中,由Selector(维护注册进来的Channel和这些Channel的状态)、SelectableChannel(能被Selector管理的Channel)和SelectionKey(SelectionKey标识Selector和SelectableChannel之间的映射关系,一旦一个Channel注册到Selector中,就会返回一个SelectionKey对象。SelectionKey保存了两类状态:对应的Channel注册了哪些操作;对应的Channel的那些操作已经准备好了,可以进行相应的数据操作了)结合来实现这个功能的。
NIO非阻塞的典型编程模型如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
private Selector selector = null;

private static final int BUF_LENGTH = 1024;

public void start() throws IOException {
if (selector != null) {
selector = Selector.open();
}
ServerSocketChannel ssc = ServerSocketChannel.open();
ServerSocket serverSocket = ssc.socket();
serverSocket.bind(new InetSocketAddress(80));
ssc.configureBlocking(false);
ssc.register(selector, SelectionKey.OP_ACCEPT);

try {
while (true) {
int nKeys = UnblockServer.this.selector.select();

if (nKeys > 0) {
Iterator it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey key = (SelectionKey) it.next();
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel channel = server.accept();
if (channel == null) {
continue;
}
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ);
}

if (key.isReadable()) {
readDataFromSocket(key);
}

it.remove();
}
}
}
} catch (IOException ioe) {
ioe.printStackTrace();
}
}

/**
* @param key
* @throws IOException
*/

private void readDataFromSocket(SelectionKey key) throws IOException {
ByteBuffer buf = ByteBuffer.allocate(BUF_LENGTH);
SocketChannel sc = (SocketChannel) key.channel();

int readBytes = 0;
int ret;

try {
while ((ret = sc.read(buf.buf())) > 0) {
readBytes += ret;
}
} finally {
buf.flip();
}

// process buffer
// buf.clear();
}

从这段程序,我们基本可以了解到NIO网络编程的一些特点,创建一个SocketServer的方式已经发生了变化,需要指定非阻塞模式,需要创建一个Channel然后注册到Selector中去,同样,建立一个网络连接过程也是一样的模式,然后就是有条件的选择(Readiness Selection).这样,我们的每一个线程只需要处理一类型的网络选择。在代码上,我们发现处理的方式和阻塞完全不一样了,我们需要完全重新考虑如何编写网络通信的模块了:
1.持久连接的超时问题(Timeout),因为API没有直接的支持timeout的参数设置功能,因此需要我们自己实现一个这样功能。
2.如何使用Selector,由于每一个Selector的处理能力是有限的,因此在大量链接和消息处理过程中,需要考虑如何使用多个Selector.
3.在非阻塞情况下,read和write都不在是阻塞的,因此需要考虑如何完整的读取到确定的消息;如何在确保在网络环境不是很好的情况下,一定将数据写进IO中。
4.如何应用ByteBuffer,本身大量创建ByteBuffer就是很耗资源的;如何有效的使用ByteBuffer?同时ByteBuffer的操作需要仔细考虑,因为有position()、mark()、limit()、capacity等方法。
5.由于每一个线程在处理网络连接的时候,面对的都是一系列的网络连接,需要考虑如何更好的使用、调度多线程。在对消息的处理上,也需要保证一定的顺序,比方说,登录消息最先到达,只有登录消息处理之后,才有可能去处理同一个链路上的其他类型的消息。
6.在网络编程中可能出现的内存泄漏问题。
在NIO的接入处理框架上,大约有两种并发线程:
1.Selector线程,每一个Selector单独占用一个线程,由于每一个Selector的处理能力是有限的,因此需要多个Selector并行工作。
2.对于每一条处于Ready状态的链路,需要线程对于相应的消息进行处理;对于这一类型的消息,需要并发线程共同工作进行处理。在这个过程中,不断可能需要消息的完整性;还要涉及到,每个链路上的消息可能有时序,因此在处理上,也可能要求相应的时序性。
当前社区的开源NIO框架实现有MINA、Grizzly、NIO framework、QuickServer、xSocket等,其中MINA和Grizzly最为活跃,而且代码的质量也很高。他们俩在实现的方法上也完全大不一样。(大部分Java的开源服务器都已经用NIO重写了网络部分。 )
不管是我们自己实现NIO的网络编程框架,还是基于MINA、Grizzly等这样的开源框架进行开发,都需要理解确定的了解NIO带来的益处和NIO编程需要解决的众多类型的问题。充足、有效的单元测试,是我们写好NIO代码的好助手:)

Resource:
http://www.cis.temple.edu/~ingargio/cis307/readings/unix4.html#states
《Java NIO》
《GlassFish—开源的Java EE应用服务器》

Java线程编程

Java程序开发中有大量的可复用资源,可复用的公用类;有很多中间件已经帮助我们解决了多线程的问题,所以很多开发人员是不需要深入的涉及到这个话题的,而要自己去开发一个服务端的应用的时候,多线程的编程、调试、发布、问题跟踪就变得无可避免了。
多线程编程需要涉及到两个方向,一个是使用Java语言进行程序设计,必须了解语言本身对于线程的支持和其编程模型;另外一个是运行时的运行状况,更多的是基于现有的硬件、先有的操作系统的配置问题。所有的应用都必须运行一定的硬件配置环境、一定的操作系统之上,所以线程的运行时状态无可避免的要在应用设计阶段进行考虑了。
1.对于共享可变数据的互斥(什么时候使用关键字synchronized):Java虚拟机通过对象锁来实现互斥,达到多个线程在同一个共享数据上独立而互不干扰地工作。

1
2
3
4
5
private int sequence = 0;

public synchronized int getSequence(){
return sequence++;
}

线程间通讯,保证共享对象对于多线程访问是从一种一致的状态跃迁到另外一种一致的状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Worker implements Runnable{
private boolean running = false;

public void run(){
while(isRunning()){
//
}
}

public synchronized boolean isRunning(){
return this.running;
}

public synchronized void stop(){
this.running = false;
}

public synchronized void startRequest(){
this.running = true;
}
}

2.协作(什么时候使用wait和notify):主要是用于多线程为了同一个目标而共同协作性的工作,应用需要设计多线程的协作工作机制。一般性地是,线程需要反复检查某一个数据结构,在等待某些条件的发生,又避免忙等(busy-wait)的时候。
使用wait()是需要仔细阅读以下javadoc了,wait(0)的情况很特殊:线程一直处于等待,直到被唤醒(notify()或者notifyAll())。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private Object monitor = new Object();

public void run(){

synchronized(monitor){
while(){
try{
obj.wait()
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}

Java使用的同步机制是监视器,为了更好的使用同步机制,就需要虚拟机是怎么使用监视器的机制的?
Java监视器主要分成了三个区域,入口区、监视区、等待区,所有进入到监视器开始处的线程首先都是进入到入口去,(可能阻塞)等待成为监视区的持有者;监视区域同时只能被一个线程持有并执行,只有当一个线程执行完了监视区域动作(该线程将释放监视区域并推出监视器)或者执行了等待命令(该线程将释放监视区域进入到等待区,直到执行了唤醒命令才能重新持有监视区域)才能释放监视区域;只有那些持有监视区域的线程执行了等待命令之后就会进入到等待区。所以基本可以了解了监视区域的各个功能、线程进出的时机动作。而且分析互斥和协作情况下线程和监视器的工作状态基本也就清晰了。
1.互斥情况下,多线程将会在等待区阻塞的等待持有监视区域,因为同时只会有一个线程执行监视区域(代码即指令),监视区域主要是共享数据或者共享资源。(例外就是虚拟机实现不是基于时间片的,那么监视器就会用来协调多线程的执行策略,将不仅仅是共享数据或者共享资源了。)
2.协作情况下,监视器主要协调多个线程之间共同工作,即是,一个已经持有监视区域的线程,通过执行等待命令,释放监视区域进入到等待区,那么该线程阻塞并一直持续暂停状态,只有监视区域持有线程执行了唤醒该线程命令并释放监视区域之后该线程才能重新持有监视区域,直到该线程再次释放监视区域。

运行阶段,Java线程的线程模型又是什么样的呢,究竟需要应用设计开发人员需要注意什么呢?
这主要涉及到Java的内存模型(Memory Model)、JVM实现等,其中我正在使用的虚拟机是Sun基于Solaris系统的,其线程模型参考《Threading》。Java的线程在Java2都是操作系统的线程,并没有了”green threads”的概念了,每一个线程的申请在向JVM申请资源的同时也是在向操作系统申请资源,对于各家JVM在不同的操作系统之上的thread stack size大小也有一些差别,其中Sun JVM可以通过-XX:ThreadStackSize(-Xss)参数进行设置(在默认情况下,”Thread Stack Size (in Kbytes). (0 means use default stack size) [Sparc: 512; Solaris x86: 320 (was 256 prior in 5.0 and earlier); Sparc 64 bit: 1024; Linux amd64: 1024 (was 0 in 5.0 and earlier); all others 0.]”).
在我们设计并发要求比较高的服务端应用的时候,线程将会变得相当宝贵了,而且就针对操作系统而言,一个进程所能申请的线程的数量也是限制的,所以如何使用和调度多线程是一个重要的环节。在系统运行阶段,进行调优的时候不可避免的要面对多线程的资源相关的几个关注点:thread stack size、thread local heap、garbage collection affects、intimate shared memory。

进行多线程的设计编码,要极力追求简洁原则,尽力将多线程的调度和编码和业务相关的逻辑进行解耦,这样多线程处理模块被抽象出来,可复用度变高,对于分析、调试、问题跟踪多线程的设计编码就不再变得臃肿复杂;避免复杂、过多的条件判断等待、wait-notify,线程意外退出或者形成死锁。

在今天的编程生活中,我们也要面对和解决多线程编程的调试、单元测试、跟踪等方面的问题。解决这些问题也是我们的乐趣之一:)

参考:
《Effective Java 中文版》
《深入Java虚拟机》
http://java.sun.com/docs/hotspot/threads/threads.html
http://java.sun.com/javase/technologies/hotspot/vmoptions.jsp
http://www.theserverside.com/tt/knowledgecenter/knowledgecenter.tss?l=ConcurrencyTestingJavaApps

写在五一

五一假期,老大结婚,一帮兄弟从各地感到郑州,一路旅行,很多朋友、同学很久没有见面,大家畅所欲言,把酒言欢,嬉笑怒骂,十分尽兴。旅途中重读了《The Secrets of Consulting》许多章节,书中很多做事的方法和原则,值得思考和品味。
1.“和我一样,维吉尼亚也很喜欢比喻,并且热衷于从各种地方—比如弗兰克.鲍姆的《绿野仙踪》—收集这些比喻。” 比喻是一种很好的表达的手段,通过类比和联想是很容易使人基于已知的世界去认识未知的世界的,而且往往很是深刻的。在这本书中看到作者是善于运用比喻的, 收集了很多比喻的。作者的工具箱中的所有物体都是现实世界中的实物,确能够被作者引申来说明一系列抽象的原则、远离、方法。

  1. 《The Secrets of Consulting》中作者多次运用萨特的三个通用问题来分析问题的:
    把问题分解成三个部分:自己、他人以及背景环境。在把自己作为研究焦点时,
    我怎样到达这里的?(过去)
    我在这里感觉如何?(现在)
    我希望发生什么?(将来)
    3.“做学问就要严谨”:这次聚会正好碰到一位非常要好的朋友,虽然做计算机行业,但是一向十分爱好文史,无论到哪儿,总带着各种小说、历史、书评等书 籍。这位朋友这次带着几本历史评论书,一闲下来就一本正经的分析书中的观点和历史中的来龙去脉,在我嘲笑之余,朋友的一句“做学问就要严谨”让我触动很 大,原来朋友读这些评论书始终是一颗做学问的严谨的心的。我也体会到读书做事是需要严谨的,而且是需要一颗严谨的心的。

读《金字塔原理》

粗粗的读完《金字塔原理——-思考、写作与制作图表的逻辑》第一个部分。第一部分分为五章,主要介绍的是:写作的逻辑。如同本文所要讲述的思想一样,第一部分显得的简单而有强烈的逻辑关系。首先简述为什么要选择金字塔结构,是人类思考方式的局限性、一般性,思维的一般惯性:自上而下组织思想;自下而上思考。然后讲叙了金字塔结构的一般原则:疑问/回答式对话的纵向关系结构、演绎或者归纳逻辑的横向关系结构、讲故事式的序言结构。接着讲述了构造金字塔结构的一般方法:自上而下法;自下而上法;注意事项。既然方法已经讲叙了,所谓“万事开头难”,切题即如何写序言变得异常重要,接下来的一章就是关注序言部分的具体写法。介绍了为什么、是什么、怎么做、怎么开始,最后一章介绍了演绎和归纳的区别,意在突出如何将一个一个的思想“点”联系在一些形成逻辑关系,即是演绎和归纳。
看完也有一丝丝小学学语文的感觉,其实本身就是一个方法论的问题。
读完第一章节之后,我有一种模糊的感觉,写作和程序设计之间似乎有很多很多相通的地方,一篇能将思想简单而清楚的讲出来的文章才是好文章,程序设计更是需要简单清晰的。无论写作,还是程序设计,都需要搞清楚问题,搞清楚复杂性,搞清楚如何简单化等等。

以下是摘抄或者评注:
一个人的思维不能仅仅是线性的、一维的。
读者必然会将所读到的思想进行归类概括,以便记住这些思想。如果作者传达给读者的思想事先已经经过归类和概括,并且按自上而下的顺序表述出来,读者就能更容易的理解作者所表述的思想。这就是所说的“条理清楚”.
“因为你总是要不断地对思想进行归类和概括,直到没有可与之关联的思想可继续概括,因此,你写的每一篇文章的结构都必定只支持一个思想,即概括了所有各组思想的单一思想。”
“大多数人刚坐下来开始写作时,可能对他们想要表达的思想还只有一个模糊的想法,甚至根本不知如何下笔。在你不得不用话语或文字将你的思想用符号表示出来之前,你很可能无法准确地了解自己的思想。甚至连你认为已经构思好的第一个思想,写出来可能都不是十分准确。”
“为什么我们可以肯定一定会感兴趣?因为这种纵向联系迫使读者按照你的思想做出符合逻辑的反应。”
自上而下组织思想
自下而上思考
金字塔的子结构
纵向关系:不断地按照“引起读者疑问并回答疑问”的模式,一环扣一环如同波浪,直到读者不会再对新的表述提出任何疑问为止。
横向关系:保证一定的逻辑性,具备明确的归纳或者演绎关系。
序言结构:问题的起源和发展必然以讲故事的形式出现,因此也应当按照典型的讲故事模式发展。就是说,开头应向读者说明“情境(Situation)”的时间和地点。在这一“情境”中应当发展了某件事情称为“冲突(Complication)”,使读者提出(或将使读者提出)你的文章中将要回答 (Answer)的“疑问(Question)”。
作者在谈论“初学者注意事项”,所讨论的一些问题也是平时思维、做事等的一些通病,让人能觉察不仅仅是在写作上。
“文如其人”,写出来的东西完全是一个人思想的映像,写出来的东西透出的是一个人如何思考的。