一、引言
古人云: "听君一席话,胜读十年书" .可想而知"听人说话"的重要性。但我这里只是想强调听技术人员讲话的重要性,"多听"总比"多看"来得快,多听别人讲,总比自己讲要好,即使别人所说的是谬论也好,以讹传讹也罢,我觉得都有必要一听,还可以顺便检验一下你的知识是否牢固,何乐而不为呢?独自一个人啃书本上的知识确实会显得很空洞,有的书又比较偏理论,需要耗费大量的时间去理解、消化,所以一本书的整个知识脉络对于一般读者来讲肯定是存在一些盲点的。
二、先睹为快
我反复读Doug lea的<<java并发编程实战>>,却始终有一些东西不清晰,例如:
1.CPU缓存行伪共享的问题.拼装指定CPU缓存行字节数,来防止伪共享的问题.
2.AQS同步器.concurrent包下的同步器基本都是有AQS来支撑的,其底层使用到了Unsafe来保证了原子性,据说JDK9要废弃掉这个核心成员.
3.java线程模型.这里就涉及到了线程的可见性、内存同步、内存重排序和happen before。
三、再进一步
我们也都知道IO方式有AIO、BIO、NIO,但是我们有没有完全理解其真正的本质,坦然说本人之前写过简易版的MVC框架来做的后台管理系统,其线程模型就是BIO,远程调用采用的是原生的RMI,很容易导致线程耗尽,性能自然也不理想。我们也许都写过网络程序,为了追求稳定性、高性能我们果断采用Netty、MINA这种NIO框架,我们有足够的理由放弃原生socket,但是我们能否完全理解Netty相比于原生的socket 到底给我们多做了一些什么事。我们都知道对象缓存池技术,也都清楚tcp传输协议、长连接短连接自然也不在话下,但是我们有没有弄明白数据库连接池是怎么复用连接的?其中tcp有没有断开连接,如果没有断开连接,是不是靠心跳包来维持长连接?TCP头、IP头有没有重组?"数据库连接"是建立在数据库实例上的还是库、表上的,这些都值得我们去深思。 最近几年微服务盛行,底层服务都尽可能的做成服务化,但是我们是否理解RPC框架的异步、超时、重试、路由、负载机制,就拿dubbo异步来说,线程池是在客户端创建还是在服务端创建?客户端和服务端不在同一个jvm实例,服务端的结果怎样通过RPC找到客户端线程来完成回调的?我们都知道java是完全依赖jvm的,但是否能利用java自带的调试工具(jps、jmap、jstat、jhat等)来快速定位OOM、线程死锁、频繁GC的问题呢?例如这样的问题,比比皆是。
本文的目的并不是要吹嘘技术的魅力,上面所说的也不代表本人理解非常清楚,而是想谈谈这几年本人一路走来作为一名普通程序员对"程序"两个字的理解,由于文章篇幅的问题,我只挑了一些我认为非常重要的点来讲述,还请恕我避重就轻之责。本人水平也非常有限,讲错的地方,还望大家不吝赐教,在下万分感谢。
前面空谈了很多,该是说些贴近实际的东西了。
四、聊聊面向对象
刚开始接触java的时候,"万物皆对象"是听得最多的名词。抱着对java面向对象编程的误解走了几年,该是说说我对"对象"的理解了。
"谁拥有数据,谁就提供操作数据的方法" ,这句话我觉得对我理解什么是面向对象起了很大作用。因为随着工作时间越来越久,看过的开源项目越来越多,越发容易理解面向对象的好处了。前段时间公司分享Quartz,所以了解了一下,绝对是面向对象的精品,定时任务本来是一个业界不能缺失的重要组件,Quartz实现了对业界比较统一的定时调度模型进行封装,抽取出了Scheduler、Trigger、Job、JobDetail四个对象,代码实现并不是本章的重点,而且个人认为框架中的代码并不是阻挡我们理解的瓶颈所在,而是它的设计方式,模型抽象。话说回来,Quartz中的每个对象只做自己应该做的事,调度器总不应该处理时间触发器吧?因为调度器更擅长处理任务调度,对其他东西都不关心,专心做好自己的事就已经足够了。
<<重构>>一书中有讲到重构的最终结果就是设计模式,所以说我们应该不断重构,不断使自己的对象更加职责单一化,方法简单化,最大程度上满足代码可复用。虽然说单一职责、里氏替换、对扩展开放对修改关闭这些基本原则和23个设计模式我们都很熟悉,但是要想编写出完全面向对象的程序其实还是有难度的。尤其是每天在被各种业务代码冲击的情况下,能保证功能顺利上线就已倍感欣慰了,还跟我谈什么面向对象。所以说偶尔在同事的代码中看到一个模板方法模式会让我惊叹不已,看到一个策略模式来替换掉复杂的ifelse或者swich语句更会让我拍手称奇。如果还用到了责任链或者复合之类的设计模式,我只会感叹一句:"小伙子,你肯定工作又不饱和了吧!还有心思设计这个!" 当然这只是玩笑话,心里还是非常认同并且敬佩的.
五、聊聊设计模式
下面说几个我常用的设计模式,总结下自己的看法。
1.单例模式
单例模式一般用在整个应用内只有一份的情况下。像数据库连接池这种比较昂贵的资源就可以使用单例来维护。像Dubbo中的ExtensionLoader也是一种单例模式的实现,在启动的时候就已经将配置文件全部加载,当要用的时候 直接从内存取。单例模式一般有恶汉、懒汉、静态内部类方式,也有用枚举来实现单例的做法,但实际中很少遇到。
2.工厂模式
工厂模式分为工厂方法、抽象工厂、静态工厂这么几类(可能有不同叫法).其主要目的还是为了解耦,当我们需要添加新对象的时候,不需要修改客户端代码,即对调用方无感知。Spring就是一个巨大的工厂,你可以从你的spring容器里随意获取你想要的东西。比如说我现在需要向客户端暴露一个方法,客户端只需传入不同的参数就可以获取不同的对象,我认为这就是一个简单工厂的实现。做数据路由的时候,我们可以根据不同的网络环境或者配置等等其他外来因素来匹配不同的对象,这里我觉得做成抽象工厂就比较合适,因为它可以具有多个行为,比其他工厂"善变"一点。
3.适配器模式
适配器无处不在。写swing程序的时候,为了实现某个事件接口需要重写很多个方法,那可想可知是非常痛苦的,代码层面也很不美观,所以就有了xxAdapter,这其实就是一个适配类.我们经常用的List、Collection也提供了骨架类AbstractList,AbstractCollection,当我们需要扩展自己的List的时候只需要继承AbstractList即可,个人认为这也是一种适配器实现。像ArrayList中的listIterator也是一种适配器实现等等。适配器模式,我认为就是这个类不具备某个能力,但是要扩展其行为的一种手段。像cygwin、vmware可以认为是一种基于操作系统的适配,teamviewer也可以当成一种基于终端的适配。
5.观察者模式
观察者模式就是一种发布订阅模式,和MQ其实一样,只是MQ是一种跨进程的实现。MQ比较适合大容量、更可靠、更加复杂的异构系统。例如N个客户端订阅自己关心的事件,当事件到达时,客户端就可以收到通知,而不用在触发事件的地方不断添加订阅者。例如登录注册送奖励、发短信,我觉得都可以用发布订阅的方式来做。比如要在登录成功后送积分,只需要新建一个送积分的服务去订阅登录消息即可(这里就不讨论丢消息或者重复消息的情况)。guava中的EventBus就是一种比较好的发布订阅的实现.
由于文章篇幅问题,其他就省略不讲了。
总之,本人在遇到多个参数的构造函数时,总会想到使用建造者模式来做到链式调用,会考虑使用策略模式替换掉复杂的判断语句和switch语句,针对只有少量不同行为的对象,会考虑使用模板方法让子类进行自定义的覆盖。当在做类似于MOCK、类AOP操作的时候,会使用代理和工厂。如果做接口的过渡或者对某些客户端隐藏一些特定的行为,我会考虑使用适配器。一些全局的资源分配,会考虑使用单例模式。对象的包装和增强我会考虑使用复合和装饰者,如果做一些监听事件的解耦,我会考虑使用观察者等等。设计模式我觉得只是一种指导,具体怎么做还是由自己来掌握、怎么样做得更容易被理解、更容易被复用,我认为这就是设计模式。最后还想强调一句名言: 我们应该不断重构,重构的最终结果就是设计模式~
六、下节预览
1.为什么不叫java并行编程而是并发编程?
2.单核CPU 多线程的好处?
3.重排序
4.volatie、synchronized
5.重入锁、读写锁、分布式锁
6.线程池
7.阻塞队列
8.happen before
9.CAS
10.几个重点类的源码剖析(阻塞队列、信号量、CountDownLatch、AQS)
11.Master worker、Fork join
谈谈我对java的理解一文篇幅较大,由于时间不是很充分,我准备分章节来讲。大体分为下面几个章节
二、对并发编程理解
三、对开源的几点理解
四、对jvm的看法
五、对分布式系统的几点认识