书山有路勤为径,学海无涯苦作舟。

0%

元宵节,又名上元节、灯节、小正月,在我们老家的方言叫做过十五。

过十五算是第二大节日了,大概因为这一天很大程度上分担了清明节的一些事宜。这天的白天,家家户户男女老少们全家出动,拿上铁锹扫帚,带上烟花爆竹,去修缮修缮祖坟,除一除草,打扫打扫卫生。如果祖坟是土坟的话,还在附近用铁锹挖一顶“官帽”,戴在坟墓的顶上。等打扫的差不多的时候,烧纸的开始烧纸,放炮的放炮,烟花爆竹一阵噼里啪啦,子子孙孙齐齐跪下,对着一座座坟墓叫着陌生的称呼。

这一天,上述的事情大多要反复执行几次,比如谁谁谁他爷爷的,谁谁谁他奶奶的,他爷爷的爸爸的,他爷爷的妈妈的,他爷爷的爷爷的,他爷爷的奶奶的… 因为家户不大,大多也就只能追溯到往上4、5代。

如此这般,白天的任务算是结束了,到了夜晚,才是过十五的高潮开始。等到六七点钟天黑的时候,家家户户又张起了灯,往祖坟前送去,俗称“送灯”,送灯大概的意思是为祖先们照亮回家的路。

如果说除夕的时候,只是断断续续打持久战般地,不停有人家放烟花,那么过十五就是密集的轰炸战,所有人家都拿出自家所有的烟花爆竹,然后一个劲地在祖坟前放掉。

这一晚的这个时候,黑夜如白昼,整个世界都是一个大的烟花展,到处是五颜六色各式各样的烟花,一家放的比一家多,一家放的比一家好看。直放到耳朵起茧子,脑中有回声,眼前一片花,空气中爆竹气味超标到无法呼吸。

这一天也是小孩子们快乐的一天,在此之前他们已经置备好了灯笼,有的是街上买的有电灯有音乐的,有的是买的装蜡烛点亮的,实在没有买的就自己用白酒盒子做一个灯笼,然后里面插上蜡烛。等到黑夜来临的时候,他们纷纷点上蜡烛,然后提着灯笼到处乱窜。嚯,谁的灯笼有跑马灯啦,谁的灯笼会唱歌啦,谁的灯笼烧着了啦。

等到夜开始深的时候,爆竹声也渐渐低了下来,偶尔的一两声爆竹,在空荡荡的乡下反复地回声着,显得特别落寞。人们陆续回家,宣告过十五的结束。也宣告着春节的结束。

我还挺怀念那个背着铁锹去墓地里上坟,闻着满鼻子的爆竹味打着灯笼到处跑的日子。可回想起来,自从上大学后,就再也没有在家里过元宵节。

元宵节到了现在,索性快要成为一个汤圆节,说起元宵节除了吃汤圆好像也不知道要做什么。
那么为什么会成为这样呢?我猜想有以下几个原因:

  1. 过节不放假。不放假的节日都是耍流氓,上班都努力工作去了,谁还记得节日?(手动狗头)
  2. 没有宣传。比如商场超市没有像端午中秋圣诞那种促销活动和宣传,导致大众对元宵节的感觉越来越淡。
  3. 时间点有点尴尬。刚过完春节,大家有的还没从假日综合症中走出来,有的正是分道扬镳各奔东西的时候,元宵节的团圆也变得很难团圆。

但愿那个把春节延长到元宵节的提议,早日被采纳实施吧!

ddd

2013 年 6 月,故事的一切,看起来都很美好。

毕业一两年的我,正在第一家公司里贪婪地成长着,最近两个月刚刚得到提拔,负责公司新兴的业务。这一年,女朋友(现在的老婆)来到我在的这个城市一起生活,事业和爱情都飞速地发展着。

忽然有天听到一个消息:隔壁工位同部门的小兄弟江波要离职了,我有些愕然。江波是另一个项目的核心负责人之一,小小的个头隐藏着大大的能量。在我们同事之间平日里大言不惭地键盘侠、嘴炮党时,他常常发挥党员作用,说一些能让我们马上安静下来的道理。用现在的话说:他有一种老干部式的智慧。也因为他的老干部身份,每逢一些国际国内波动、社会变革、经济动荡的大议题出现时,我们都会争取让江波加入讨论之中,虽然…他经常嗤之以鼻。

江波在公司里他很低调,话也不多,每天上班准时背着运动双肩包出现,然后打一保温杯开水,开始一天的工作,等到下班又准时收拾双肩包走人。除非赶项目或者迫不得已,很少看到他在工位上加班。但他仍然是部门老大最喜爱的员工之一,他懂老大,老大也懂他,公认的前途无量。

我和江波工位虽处隔壁,但工作上并无什么交集,只有一次业务对接的时候,他的专业性让我敬佩不已,甚至很直接地指出了我工作任务中的一些问题,有些问题连我自己都没有想清楚过。从此更是对他暗暗多了几分佩服。

2013 年是移动互联网发展如火如荼的一年,尽管身处传统公司的我们很难感受到外面的互联网世界快速发展,但江波平日里最喜爱看 36kr 和 YC。在看多了他的这些举动后,我也暗暗地去关注这些,想去了解它们为何有如此魅力,不觉间也被带入了移动互联网的大浪潮里。

drink

送别江波的那天,部门里的所有成员都出席了送别宴。过去六年了,我还能记得那天的场景,昏黄的灯光,拥挤的位置,一杯接一杯的啤酒,和微醺的我们。大家言语不多,嘴里只是反复吐出一个字:干。

那一天,老大情绪有些激动,喝的最多。酒至深处之时,老大呜咽着感叹道:“我不知道为什么,有时候怎么这么难呢!”。大家听闻此言,顿时有些感伤,竟不知说些什么。江波也受了影响有些伤感,拍了拍老大的肩膀。

那时,我挤在人群之间,不会喝酒却在这一晚喝的有些迷糊。年轻的我,意气风发的我,看到这离别的场景,郁郁寡欢的人群,伤感的老大,我是错愕的,甚至有些想哑然失笑。老大啊老大,你三四十的人了,有家有室,风风雨雨也历经不少了,为何一个下属离职就让你哭成这样。

这一晚,大家没有谈到江波的未来,江波也没有主动交代,只依稀记得听人说有他去投入到创业大潮中的,有说他去互联网巨头公司的,有说他回老家的。

宴会结束后,很少喝酒的我带着一身酒气一个人走回了家,女朋友问为何今天破例喝酒,答曰:有个很好的同事离职了,我们一起送别他。

江波走后,很快大家便把江波的工作分而食之,好像江波从来没有在过一样。连他的工位,也有另一个同事搬了过来,至今想起那个位置,一半的记忆是江波的,另一半的记忆是后面那个同事的。

2013 年在移动互联网的滚滚大潮中落下帷幕,2014 年是 O2O 和社交爆发的一年。江波离职的一年后,我也按捺不住,一头扎入了移动互联网的大浪潮中。再后来,浮浮沉沉,跌跌撞撞,成功过,失败过,团结后,孤独过,聚过,散过,相识过,相忘过…

现在,我已经快要忘记了江波,也快要忘记了那个老大,那个团队。活在当下,过去和历史就是一段虚无的记忆,除了回味,你无法证明它是否真的发生过。

除非…偶然翻起过去的老照片。当我看到一张张老照片,一张张熟悉的面孔,我才知道,这些东西都没有被遗忘,只是它们被压缩在大脑的最深处,当再次解压的时候,所有那些记忆都会释放迸发出来。

memory

我又想起了老大的那次流泪,这次,我也有些伤感起来,甚至想流泪。当我想象自己是 2013 年的老大时,我哭了出来。2013 年到底是老大哭了,还是我哭了,他的流泪到底是他真的流泪,还是只是我现在的想象?

笑,世界陪你笑;哭,你一个人哭。

我曾以为江波走后,总会回来看我们的,直到我离开公司的时候,也没有见过他,甚至听不到他的任何消息。在我离开公司后,我知道人生路很长,却只有往前,没有向后看,前方的路已经够眼花缭乱,哪还有精力常回头看看。

人生苦短,相遇相识相知很难,此去一别,也许从此天各一方,再也不见。这大概就是老大那次流泪的原因吧。

bye

后记:2013 年后至今,我再也没有联系过江波,更没见过他,也没有从别人那里打听到他的消息。2014 年后,我再也没有见过老大。某个深夜,我还是很想他们。

cover
众所周知,iOS 10 以后,苹果官方推出了Notification Service Extension,查看文档,UNNotificationServiceExtension的说明是,An object that modifies the content of a remote notification before it's delivered to the user.,也就是在一个远程通知展示给用户之前,可以通过UNNotificationServiceExtension来修改这个通知。

废话少说,直接开干吧。

一、如何监听收到推送

  1. 在原有的项目上新建一个 Target,如图,选择创建
    image-20190304210307444

  2. 在服务端推送的内容中新增一个字段mutable-content": "1",与alert/badge/sound处于同一层级。例如在推送平台中测试推送时,设置如下:image-20190304212029192

  3. 先运行项目的主Target,然后再运行Notification Service Extension,手动选择主Target

  4. 发送测试推送,可以看到执行进入 Target 项目中的- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler

二、如何统计

  1. 如果是接入第三方推送平台,可以查看是否支持。如 极光的 Notification Service Extension 相关接口
  2. 如果自己实现的话,有以下两种方案
    1. 在通知扩展项目中写网络请求,将推送到达数据发送到后端服务器
    2. 将推送到达数据通过App Group保存到 App 的本地,在主 Target 中再处理

三、如何使用 App Groud 实现数据共享

  1. https://developer.apple.com 登录,创建 App Group

  2. 在项目中配置,target - Capabilites - App groups

  3. 代码中使用

    1. NSUserDefaults 中使用

      1
      2
      3
      4
      5
      6
      NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.company.appGroupName"];
      // write data
      [userDefaults setValue:@"value" forKey:@"key"];

      //read data
      NSLog(@"%@", [userDefaults valueForKey:@"key"]);
  1. NSFileManager 中使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // write data
    NSURL *groupURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.domain.groupName"];
    NSURL *fileURL = [groupURL URLByAppendingPathComponent:@"fileName"];

    NSString *text = @"Go amonxu.com";
    if (![[NSFileManager defaultManager] fileExistsAtPath:fileURL.path]) {
    [text writeToURL:fileURL atomically:YES encoding:NSUTF8StringEncoding error:nil];
    } else {
    NSFileHandle *fileHandle = [NSFileHandle fileHandleForUpdatingURL:fileURL error:nil];
    [fileHandle seekToEndOfFile];
    [fileHandle writeData:[text dataUsingEncoding:NSUTF8StringEncoding]];
    [fileHandle closeFile];
    }


    // read data
    NSURL *groupURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.domain.groupName"];
    NSURL *fileURL = [groupURL URLByAppendingPathComponent:@"fileName"];
    NSString *fileContent = [NSString stringWithContentsOfURL:fileURL encoding:NSUTF8StringEncoding error:nil];

早高峰的地铁里,
人群席卷了站台,
十秒后空无一人,
除了那只孤独的鸟。
她瘦削的身材,
高挑的气质,
两腿交叉在那,
静静地站着,
仿佛不属于这个世界。


本是,
林中鸟,
溪中水,
山涧清风,
田中野兔,
你在这里,
带来谁的问候,
带走谁的孤独?

列车越渐快速的远行,
她的身影被拉的愈发修长,
她仍低头兀自欣赏自己,
列车没能带走了她,
她却带走了路人的心。

原文链接:https://my.oschina.net/feichexia/blog/196575

JVM 性能调优参考资料:

《Java虚拟机规范》

《Java Performance》

《Trouble Shooting Guide for JavaSE 6 with HotSpot VM》: http://www.oracle.com/technetwork/java/javase/tsg-vm-149989.pdf

《Effective Java》

VisualVM: http://docs.oracle.com/javase/7/docs/technotes/guides/visualvm/

jConsole: http://docs.oracle.com/javase/1.5.0/docs/guide/management/jconsole.html

Monitoring and Managing JavaSE 6 Applications: http://www.oracle.com/technetwork/articles/javase/monitoring-141801.html

BTrace:https://kenai.com/projects/btrace

英文原文:
https://engineering.linkedin.com/blog/2016/02/eliminating-large-jvm-gc-pauses-caused-by-background-io-traffic

译文原文:
https://www.jianshu.com/p/ce9b3f0a90f2

这里就只贴出结论总结吧:

有低延迟要求的Java应用程序需要极短的JVM GC停顿。但是,当磁盘IO压力很大时,JVM可能被阻塞一段较长的时间。

我们对该问题进行了调查,并且发现如下原因:

  1. JVM GC需要通过发起系统调用write(),来记录GC行为。
  2. write()调用可以被后台磁盘IO所阻塞。
  3. 记录GC日志属于JVM停顿的一部分,因此write()调用的时间也会被计算在JVM STW的停顿时间内。
    我们提出了一系列解决该问题的方案。重要的是,我们的发现可以帮助JVM实现来改进该问题。对于低延迟应用程序来说,最简单有效的措施是将GC日志文件放到单独的HDD或者高性能磁盘(例如SSD)上,来避免IO竞争。


🍺 show-busy-java-threads

用于快速排查JavaCPU性能问题(top us值过高),自动查出运行的Java进程中消耗CPU多的线程,并打印出其线程栈,从而确定导致性能问题的方法调用。
目前只支持Linux。原因是MacWindowsps命令不支持列出进程的线程id,更多信息参见#33,欢迎提供解法。

PS,如何操作可以参见@bluedavy《分布式Java应用》的【5.1.1 CPU消耗分析】一节,说得很详细:

  1. top命令找出有问题Java进程及线程id
    1. 开启线程显示模式(top -H,或是打开top后按H
    2. CPU使用率排序(top缺省是按CPU使用降序,已经合要求;打开top后按P可以显式指定按CPU使用降序)
    3. 记下Java进程id及其CPU高的线程id
  2. 用进程id作为参数,jstack有问题的Java进程
  3. 手动转换线程id成十六进制(可以用printf %x 1234
  4. 查找十六进制的线程id(可以用vim的查找功能/0x1234,或是grep 0x1234 -A 20
  5. 查看对应的线程栈,以分析问题

查问题时,会要多次上面的操作以分析确定问题,这个过程太繁琐太慢了

用法

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
68
69
70
71
72
73
74
75
76
77
78
show-busy-java-threads
# 从所有运行的Java进程中找出最消耗CPU的线程(缺省5个),打印出其线程栈

# 缺省会自动从所有的Java进程中找出最消耗CPU的线程,这样用更方便
# 当然你可以手动指定要分析的Java进程Id,以保证只会显示出那个你关心的那个Java进程的信息
show-busy-java-threads -p <指定的Java进程Id>

show-busy-java-threads -c <要显示的线程栈数>

show-busy-java-threads <重复执行的间隔秒数> [<重复执行的次数>]
# 多次执行;这2个参数的使用方式类似vmstat命令

show-busy-java-threads -a <运行输出的记录到的文件>
# 记录到文件以方便回溯查看

show-duplicate-java-classes -S <存储jstack输出文件的目录>
# 指定jstack输出文件的存储目录,方便记录以后续分析

##############################
# 注意:
##############################
# 如果Java进程的用户 与 执行脚本的当前用户 不同,则jstack不了这个Java进程
# 为了能切换到Java进程的用户,需要加sudo来执行,即可以解决:
sudo show-busy-java-threads

show-busy-java-threads -s <指定jstack命令的全路径>
# 对于sudo方式的运行,JAVA_HOME环境变量不能传递给root,
# 而root用户往往没有配置JAVA_HOME且不方便配置,
# 显式指定jstack命令的路径就反而显得更方便了

# -m选项:执行jstack命令时加上-m选项,显示上Native的栈帧,一般应用排查不需要使用
show-busy-java-threads -m
# -F选项:执行jstack命令时加上 -F 选项(如果直接jstack无响应时,用于强制jstack),一般情况不需要使用
show-busy-java-threads -F
# -l选项:执行jstack命令时加上 -l 选项,显示上更多相关锁的信息,一般情况不需要使用
# 注意:和 -m -F 选项一起使用时,可能会大大增加jstack操作的耗时
show-busy-java-threads -l

# 帮助信息
$ show-busy-java-threads -h
Usage: show-busy-java-threads [OPTION]... [delay [count]]
Find out the highest cpu consumed threads of java, and print the stack of these threads.

Example:
show-busy-java-threads # show busy java threads info
show-busy-java-threads 1 # update every 1 second, (stop by eg: CTRL+C)
show-busy-java-threads 3 10 # update every 3 seconds, update 10 times

Output control:
-p, --pid <java pid> find out the highest cpu consumed threads from the specified java process,
default from all java process.
-c, --count <num> set the thread count to show, default is 5.
-a, --append-file <file> specifies the file to append output as log.
-S, --store-dir <dir> specifies the directory for storing intermediate files, and keep files.
default store intermediate files at tmp dir, and auto remove after run.
use this option to keep files so as to review jstack/top/ps output later.
delay the delay between updates in seconds.
count the number of updates.
delay/count arguments imitates the style of vmstat command.

jstack control:
-s, --jstack-path <path> specifies the path of jstack command.
-F, --force set jstack to force a thread dump.
use when jstack <pid> does not respond (process is hung).
-m, --mix-native-frames set jstack to print both java and native frames (mixed mode).
-l, --lock-info set jstack with long listing. Prints additional information about locks.

cpu usage calculation control:
-d, --top-delay specifies the delay between top samples, default is 0.5 (second).
get thread cpu percentage during this delay interval.
more info see top -d option. eg: -d 1 (1 second).
-P, --use-ps use ps command to find busy thread(cpu usage) instead of top command,
default use top command, because cpu usage of ps command is expressed as
the percentage of time spent running during the entire lifetime of a process,
this is not ideal.

Miscellaneous:
-h, --help display this help and exit.

示例

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
$ show-busy-java-threads
[1] Busy(57.0%) thread(23355/0x5b3b) stack of java process(23269) under user(admin):
"pool-1-thread-1" prio=10 tid=0x000000005b5c5000 nid=0x5b3b runnable [0x000000004062c000]
java.lang.Thread.State: RUNNABLE
at java.text.DateFormat.format(DateFormat.java:316)
at com.xxx.foo.services.common.DateFormatUtil.format(DateFormatUtil.java:41)
at com.xxx.foo.shared.monitor.schedule.AppMonitorDataAvgScheduler.run(AppMonitorDataAvgScheduler.java:127)
at com.xxx.foo.services.common.utils.AliTimer$2.run(AliTimer.java:128)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:662)

[2] Busy(26.1%) thread(24018/0x5dd2) stack of java process(23269) under user(admin):
"pool-1-thread-2" prio=10 tid=0x000000005a968800 nid=0x5dd2 runnable [0x00000000420e9000]
java.lang.Thread.State: RUNNABLE
at java.util.Arrays.copyOf(Arrays.java:2882)
at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:100)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:572)
at java.lang.StringBuffer.append(StringBuffer.java:320)
- locked <0x00000007908d0030> (a java.lang.StringBuffer)
at java.text.SimpleDateFormat.format(SimpleDateFormat.java:890)
at java.text.SimpleDateFormat.format(SimpleDateFormat.java:869)
at java.text.DateFormat.format(DateFormat.java:316)
at com.xxx.foo.services.common.DateFormatUtil.format(DateFormatUtil.java:41)
at com.xxx.foo.shared.monitor.schedule.AppMonitorDataAvgScheduler.run(AppMonitorDataAvgScheduler.java:126)
at com.xxx.foo.services.common.utils.AliTimer$2.run(AliTimer.java:128)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:662)

......

上面的线程栈可以看出,CPU消耗最高的2个线程都在执行java.text.DateFormat.format,业务代码对应的方法是shared.monitor.schedule.AppMonitorDataAvgScheduler.run。可以基本确定:

  • AppMonitorDataAvgScheduler.run调用DateFormat.format次数比较频繁。
  • DateFormat.format比较慢。(这个可以由DateFormat.format的实现确定。)

多执行几次show-busy-java-threads,如果上面情况高概率出现,则可以确定上面的判定。
因为调用越少代码执行越快,则出现在线程栈的概率就越低。
脚本有自动多次执行的功能,指定 重复执行的间隔秒数/重复执行的次数 参数。

分析shared.monitor.schedule.AppMonitorDataAvgScheduler.run实现逻辑和调用方式,以优化实现解决问题。

声明

转载自 https://github.com/oldratlee/useful-scripts
致谢 oldratlee

cover

故事从孙少平开始,少平在原西县城上高中,贫困的他每天等别人吃完饭后才去偷偷拿他的两个非洲黑馒头,后来和同拿非洲馒头的郝红梅同病相怜,成为知心朋友。在那个物质匮乏,思想贫瘠的时代,给予两个可怜的人莫大鼓励。

孙少安是生产队长,尽管他干农活在村里数一数二,但父母年迈,尚有老奶奶,弟弟妹妹上学,拖着一个贫困的家庭,在黄土地里的劳作最终也不能盈余一点。县城里有他青梅竹马的润叶,润叶叫他去县城见面,她必须要和他倾诉感情。

少安最后主动选择了消失,他认清了自己是个农民,并且以后也是农民,农民和小资产阶级是没有未来的。后来他娶了同是农民的贺秀莲,这辈子也算枕边有个相互照料的人。

田润叶是县城里的教师,寄托在二爸田福军的屋檐下,她和少安一样,无法摆脱所处阶级的禁锢,和舔狗李向前结婚。她根本不爱李向前,被李向前羞辱后一个人独过,直到李向前成为残疾后莫名其妙地圣母心发作,回去和他一起生活。

少平高中毕业后,去了黄原揽活,和田晓霞渐生情愫。原以为他会陷入到安排落户的曹书记的安排里,但他后面还是去了铜城煤矿,结识了可能相处一辈子的慧英嫂一家。

田晓霞和少平相互倾心,他们跨越了彼此的阶级鸿沟,尽管都不知未来会如何,但人生苦短及时享乐。路遥索性让他们没有未来,不管现实还是故事,这个阶级鸿沟谁也跨越不了。因为这不是爽文,是平凡的世界。平凡的世界谁也不是超级英雄,顶多是个平民英雄。少平洪水中救了侯玉英当了一回英雄,晓霞洪水中救小女孩当了烈士。

王满银,一个自以为读了点书不愿务农的二流子,一个敢革命时代搞资本主义的老鼠药贩子,一个不愿打柴干脆去田沟里晒太阳取暖的二傻子。打从放开资本主义和市场经济后,满银同志便走南闯北的逛,黄原、省城、上海,甚至南中国的深圳沙头角,他都能逛了去。如果不是海关拦着,他可能逛到香港去了,现在说不定就是个商业巨鳄、XX大王。忽然有一天他照了下镜子,岁月蹉跎容颜衰老,他意识到已经不是那个靠骚就勾引到兰花的年轻王满银,他火烧屁股般赶到家里,再也不愿意离开。

田福堂,双水村权力最大的人,他控制了村里上上下下,唯独家里两个孩子脱离了他的控制。润叶在政治婚姻的巅峰时如同离婚,在婚姻跌入谷底时又选择回去。润生前半生是个乖乖仔,在重遇寡妇郝红梅时,义无反顾的在一起。田福堂一辈子没做什么大事,唯一一件开山辟岭的大事件,除了去金家的那一跪和炸山那一炮外,再无动静。农村翻天覆地的变化时,他躺在石碾上晒太阳,等到卸去了权力后,他一人带着继孙子和亲孙子,来看热闹。

田福军,平凡的世界少有的不平凡人。从县里到市里,从市里到省里,虽然偶有政敌和障碍,但也算是步步高升。他没有想到女儿和一个挖煤工人在一起,也没有想到女儿会在洪灾来临时第一时间赶往前线,更没想到深夜会接到女儿的噩耗。在那一刻,田福军也是平凡人,众生皆苦。

孙少安的事业几经波折,但早已双水村第一农民企业家,但在他接手的乡镇砖瓦厂,事业新巅峰时,秀莲得了肺癌,当然很可能是长期的劳作,加上在砖厂里。

少平在师父拯救别人意外身亡后承担了师父家里男人的角色,同样在自己拯救别人后,破相了。尽管金秀向他表示了爱意,但自尊心强的他,永远也不会答应。故事的最后,他独自坐车回去铜城,如无意外,将成为下一个王世才。别忘了王世才出场时的介绍,他背驼的厉害,镶着两颗金牙,这是煤矿留给他的纪念,也是颁给他的荣耀勋章。少平领取了勋章后,也是真正的成为煤矿的人,他和慧英嫂再无隔阂。

路遥是一个出身上世纪黄土地的作家,对于那个时代和黄土地,写起来得心应手,一方面是他本身生活在那个时代那个地方,另一方面他写作时也回到黄土地,查阅了很多资料,咨询了很多人,为此他耗尽了生命的力量。他真实的刻画了双水村,和双水村的人,几十年的历史变迁,几代人的利害纠葛。尤其是农村的那种既能看到些许未来的盼望,又能一眼看到尽头的绝望。

尽管文笔略显稚嫩,部分人物太脸谱化,官场描述太苍白,但这部书仍不失为一个乡土文学的经典之作。作为一个生于农村长于农村的乡下孩子,即使相隔几十年,仍然能找到许多共鸣。

我爱农村,我恨农村,有时想回到那里,有时想离开那里。我们都在平凡的世界,众生皆苦。

111

像路遥这样通过努力摆脱农村走进城市的作家,在达成愿望之后又自觉不自觉地返回了农村,并打算一辈子写农村。“在农村的时候,总有一种本能要摆脱,等到出去了,又觉得离不开这里,又会回来。这是很多从农村走去出的人共同的心情。”王天笑说。