金麟岂是池中物,一遇风云便化龙
技术生活
误区!double类型做加减法不会有误差?
Aug 25th
如果你跟我一样以为Java的double类型只有在作乘除法时才会出现误差,那试一下在Java里执行一下下面的代码:
public static void main(String[] args) { System.out.println(44.42 + 710.79 + 44.42 + 88.85); }
执行前先猜一下结果,是会输出888.48么?还是……?
建议:对于和钱有关的计算,不论加减乘除,统一使用 BigDecimal!
然而不多久之后同事告诉我另一个BigDecimal的问题,试一下下面的代码:
System.out.println(new BigDecimal(0.99).setScale(2, RoundingMode.DOWN).toString());
结果是令人发指的0.98!
看了一下 BigDecimal(double)的源码,其中使用位操作分别提取了0.99浮点值的整数部分和纯小数部分,或者说是2的正数次幂和负数次幂部分。而这样提取出来的值纯小数部分本身就是近似的,与直接使用double类型没有本质区别,这是浮点表示法决定的,再经过2位截取后就产生了误差
而当使用BigDecimal(String)的构造器时得到的是精确值,因为该构造器将数字使用科学计数法表示,即 0.99表示为99*10^-2,这样做运算时先对齐至相同的整数位再进行计算
因此,对上面的补充是:如果要获取最精确的结果,请使用BD+字符串类型的构造器
Spring之经验教训(一)
Jul 18th
在现在的项目中我们使用了spring + hibernate + struts的架构,在享受aop, orm, ioc, di带来的种种便利的同时,我们亦遇到了很多莫名其妙或者说刻骨铭心的教训,今天先整理两点,日后继续补充
经验一:时刻牢记,spring、hibernate对对象 进行了动态代理,尽量不要试图在动态代理后的对象上进行反射,尤其是field!
不管是hibernate的orm还是spring的声明式事务管理,都对原来的pojo、dao进行了动态代理。虽然s、h“号称”动态代理做得天衣无缝且无色无味,但是,那只是在“绝大多数情况下”,如果想对动态代理后的对象进行反射,麻烦便来了,代码片段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public static void setCreditInfoStatus(CreditInfo info, CreditType type, CreditValidateStatus status) { ... Field[] fields = CreditInfo.class.getDeclaredFields(); for (Field f : fields) { if (f.isAnnotationPresent(Credit.class) && f.getAnnotation(Credit.class).value() == type) { PropertyDescriptor pd = BeanUtils.getPropertyDescriptor(CreditInfo.class, f.getName()); if (pd != null) { ... } break; } } } |
原先第7行写为 info.getClass(),乍一看与现有的代码功能上是一样的,但是别忘了让人又恨又爱的动态代理!上面代码返回的是真正的CreditInfo class,而左边的返回的是动态代理后的class,即意味着,第一个“if”永远返回的是false,除非动态代理后的对象的field上附带了原有的annotation
经验二:spring的声明式事务管理确很强大,强大到可以支持多线程,但是,结合上一点,不要在线程中调用this中的事务方法
声明式事务,即spring中使用aop织入或 @Transactional标记的方法注入事务容器,码农们可以完全不用操心何时begin,何时commit,何时 rollback,有没有嵌套,绝对傻瓜级的编程模型。但是,牢记,spring中的aop、annotation都是使用动态代理实现的,即,如果没有经过动态代理便也没有了事务管理,代码片段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | public class AutomatedService{ ... loanQueue.execute(new Runnable() { @Override public void run() { Thread thread = Thread.currentThread(); while (!thread.isInterrupted()) { try { loanService.checkNSetLoanStatus(); Thread.sleep(loanStatusCheckIntervalMinutes * Consts.ONE_MINUTE); } catch (InterruptedException ex) { thread.interrupt(); } } } }); ... } |
原先的checkNSetLoanStatus方法是定义在AutomatedService中的,且标记了@Transactional。 但是,令人发指的是,checkNSetLoanStatus中的事务没有被提交。在痛定思痛仔细回想了spring的声明式事务的本质后,豁然发现当调用this.checkNSetLoanStatus()时,并没有被织入事务管理。spring还没有霸道到或者说聪明到对this进行动态代理,于是将该方法移至其它service并注入,问题解决
Spring之Hibernate+JBoss Treecache实现Hibernate集群
Jun 9th
本文版权归本人所有,任何转载请标明出处
Hibernate作为一个ORM框架可以说已经做到了极致,但是绝大多数情况下Hibernate都被应用于单容器环境中,以至于互联网上能够找到的在集群环境中使用的参考少之又少,和Spring整合的就更别说了。实际上,Hibernate可以使用JBoss的Treecache作为二级缓存以支持分布式集群
本文将使用Spring 3.0.2框架集成Hibernate 3.5.2,并将Treecache 3.2.5作为二级缓存
Spring配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <context:annotation-config /> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> ... <property name="hibernateProperties"> <value> hibernate.dialect=org.hibernate.dialect.MySQL5Dialect hibernate.cache.use_second_level_cache=true hibernate.show_sql=true hibernate.cache.region.factory_class=org.hibernate.cache.jbc2.SharedJBossCacheRegionFactory hibernate.cache.use_query_cache=true hibernate.hbm2ddl.auto=update </value> </property> </bean> |
其中第1行打开Spring的annotation支持,即使用annotation替代之前版本中的大量XML配置(貌似这个选项在Spring 2.5中就被引入,但不知道是因为大家懒还是配置文件的基本内容都是在互联网上Ctrl C+V 2.5之前的代码,反正互联网上一搜Spring的配置满眼都是XML片段);第8行打开二级缓存;第10行选择Treecache作为缓存region工厂的实现类
对于需要被缓存的实体,只需要在类定义上加上@Cache注释,如:
@Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL) public class MyModel { ... }
由于Treecache只支持只读缓存和事务缓存,因此这里usage只能设为READ_ONLY或TRANSACTIONAL,但需要注意的是设为只读缓存时不能打开Hibernate的query cache
最后是Treecache的配置,如果配置文件名是treecache.xml且在classpath中则无需指定,否则需要在上述Spring配置的代码段中指定。至于具体配置内容,可参考Treecache源代码中的sample,调优方式可参考Treecache文档,这里给一个sample
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 | <?xml version="1.0" encoding="UTF-8"?> <jbosscache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:jboss:jbosscache-core:config:3.2"> <!-- Configure the TransactionManager --> <transaction transactionManagerLookupClass="org.jboss.cache.transaction.GenericTransactionManagerLookup"/> <clustering mode="replication"> <!-- timeout: The max amount of time (in milliseconds) we wait until the state (i.e. the contents of the cache) are retrieved from existing members in a clustered environment --> <stateRetrieval timeout="20000"/> <!-- JGroups protocol stack properties. --> <jgroupsConfig> <UDP discard_incompatible_packets="true" enable_bundling="true" enable_diagnostics="false" ip_ttl="2" loopback="false" max_bundle_size="64000" max_bundle_timeout="30" mcast_addr="224.12.12.12" mcast_port="45588" mcast_recv_buf_size="100000000" mcast_send_buf_size="640000" oob_thread_pool.enabled="true" oob_thread_pool.keep_alive_time="10000" oob_thread_pool.max_threads="20" oob_thread_pool.min_threads="8" oob_thread_pool.queue_enabled="false" oob_thread_pool.queue_max_size="10" oob_thread_pool.rejection_policy="Run" thread_naming_pattern="pl" thread_pool.enabled="true" thread_pool.keep_alive_time="10000" thread_pool.max_threads="15" thread_pool.min_threads="8" thread_pool.queue_enabled="true" thread_pool.queue_max_size="100000" thread_pool.rejection_policy="Discard" tos="8" ucast_recv_buf_size="20000000" ucast_send_buf_size="640000" use_concurrent_stack="true" use_incoming_packet_handler="true"/> <PING num_initial_members="3" timeout="2000"/> <MERGE2 max_interval="30000" min_interval="10000"/> <FD_SOCK/> <FD max_tries="5" shun="true" timeout="10000"/> <VERIFY_SUSPECT timeout="1500"/> <pbcast.NAKACK discard_delivered_msgs="true" gc_lag="0" retransmit_timeout="300,600,1200,2400,4800" use_mcast_xmit="true"/> <UNICAST timeout="300,600,1200,2400,3600"/> <pbcast.STABLE desired_avg_gossip="50000" max_bytes="400000" stability_delay="1000"/> <pbcast.GMS join_timeout="5000" print_local_addr="true" shun="false" view_ack_collection_timeout="5000" view_bundling="true"/> <FC max_credits="500000" min_threshold="0.2"/> <FRAG2 frag_size="60000"/> <pbcast.STREAMING_STATE_TRANSFER/> <pbcast.FLUSH timeout="0"/> </jgroupsConfig> <async /> <!-- Alternatively, to use sync replication, comment out the element above and uncomment the element below. --> <!-- <sync /> --> </clustering> </jbosscache> |
需要注意的是第7行,集群mode。Treecache的分布式同步有两种模式:replication和invalidation。这两者的区别是:对于replication,每一个节点(缓存)中的对象改变时,该节点会将新对象通过多播的方式告知多播组中的其它节点,其它节点自行更新;而对于invalidation模式,发生改变的节点只通知其它节点某对象已发生改变,其它节点直接将自己缓存中的该对象标记为invalid,待下次需要使用时通过数据库更新该对象。这样一解释就能发现取舍的策略了:若网络比数据库金贵,选择invalidation,标记自己的对象,让数据库忙活去吧;若数据库金贵,使用replication多播对象
另外,由于Treecache使用了基于多播协议的jgroup库作为底层通信框架,在配置Treecache集群时不需要了解多播组中其他节点IP,只需要把第19行的多播地址(必须是D类地址)配成一个的就行了,jgroup会自行发现多播组中的其它节点
世界杯大酬宾:如何使用JMX监控Treecache的状态
启动容器;打开任意JMX客户端,如jconsole;在进程列表中选择容器的java进程并连接;选择“mbean”标签,找到“jboss.cache”节点,如下图
在这个节点下可以看到所有Treecache通过JMX暴露的属性,包括命中率、缓存对象等
Dropbox’s Startup
May 11th








