海水正蓝

面朝大海,春暖花开
posts - 145, comments - 29, trackbacks - 0, articles - 1
  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

Ehcache 是现在最流行的纯Java开源缓存框架,配置简单、结构清晰、功能强大,最初知道它,是从Hibernate的缓存开始的。网上中文的EhCache材料 以简单介绍和配置方法居多,如果你有这方面的问题,请自行google;对于API,官网上介绍已经非常清楚,请参见官网;但是很少见到特性说明和对实现 原理的分析,因此在这篇文章里面,我会详细介绍和分析EhCache的特性,加上一些自己的理解和思考,希望对缓存感兴趣的朋友有所收获。

 

一、特性一览,来自官网,简单翻译一下:

 

1、快速轻量
过去几年,诸多测试表明Ehcache是最快的Java缓存之一。
Ehcache的线程机制是为大型高并发系统设计的。
大量性能测试用例保证Ehcache在不同版本间性能表现得一致性。
很多用户都不知道他们正在用Ehcache,因为不需要什么特别的配置。
API易于使用,这就很容易部署上线和运行。
很小的jar包,Ehcache 2.2.3才668kb。
最小的依赖:唯一的依赖就是SLF4J了。

2、伸缩性
缓存在内存和磁盘存储可以伸缩到数G,Ehcache为大数据存储做过优化。
大内存的情况下,所有进程可以支持数百G的吞吐。
为高并发和大型多CPU服务器做优化。
线程安全和性能总是一对矛盾,Ehcache的线程机制设计采用了Doug Lea的想法来获得较高的性能。
单台虚拟机上支持多缓存管理器。
通过Terracotta服务器矩阵,可以伸缩到数百个节点。

3、灵活性
Ehcache 1.2具备对象API接口和可序列化API接口。
不能序列化的对象可以使用除磁盘存储外Ehcache的所有功能。
除了元素的返回方法以外,API都是统一的。只有这两个方法不一致:getObjectValue和getKeyValue。这就使得缓存对象、序列化对象来获取新的特性这个过程很简单。
支持基于Cache和基于Element的过期策略,每个Cache的存活时间都是可以设置和控制的。
提供了LRU、LFU和FIFO缓存淘汰算法,Ehcache 1.2引入了最少使用和先进先出缓存淘汰算法,构成了完整的缓存淘汰算法。
提供内存和磁盘存储,Ehcache和大多数缓存解决方案一样,提供高性能的内存和磁盘存储。
动态、运行时缓存配置,存活时间、空闲时间、内存和磁盘存放缓存的最大数目都是可以在运行时修改的。

4、标准支持
Ehcache提供了对JSR107 JCACHE API最完整的实现。因为JCACHE在发布以前,Ehcache的实现(如net.sf.jsr107cache)已经发布了。
实现JCACHE API有利于到未来其他缓存解决方案的可移植性。
Ehcache的维护者Greg Luck,正是JSR107的专家委员会委员。

5、可扩展性
监听器可以插件化。Ehcache 1.2提供了CacheManagerEventListener和CacheEventListener接口,实现可以插件化,并且可以在ehcache.xml里配置。
节点发现,冗余器和监听器都可以插件化。
分布式缓存,从Ehcache 1.2开始引入,包含了一些权衡的选项。Ehcache的团队相信没有什么是万能的配置。
实现者可以使用内建的机制或者完全自己实现,因为有完整的插件开发指南。
缓存的可扩展性可以插件化。创建你自己的缓存扩展,它可以持有一个缓存的引用,并且绑定在缓存的生命周期内。
缓存加载器可以插件化。创建你自己的缓存加载器,可以使用一些异步方法来加载数据到缓存里面。
缓存异常处理器可以插件化。创建一个异常处理器,在异常发生的时候,可以执行某些特定操作。

6、应用持久化
在VM重启后,持久化到磁盘的存储可以复原数据。
Ehcache是第一个引入缓存数据持久化存储的开源Java缓存框架。缓存的数据可以在机器重启后从磁盘上重新获得。
根据需要将缓存刷到磁盘。将缓存条目刷到磁盘的操作可以通过cache.flush()方法来执行,这大大方便了Ehcache的使用。

7、监听器
缓存管理器监听器。允许注册实现了CacheManagerEventListener接口的监听器:
notifyCacheAdded()
notifyCacheRemoved()
缓存事件监听器。允许注册实现了CacheEventListener接口的监听器,它提供了许多对缓存事件发生后的处理机制:
notifyElementRemoved/Put/Updated/Expired

8、开启JMX
Ehcache的JMX功能是默认开启的,你可以监控和管理如下的MBean:
CacheManager、Cache、CacheConfiguration、CacheStatistics

9、分布式缓存
从Ehcache 1.2开始,支持高性能的分布式缓存,兼具灵活性和扩展性。
分布式缓存的选项包括:
通过Terracotta的缓存集群:设定和使用Terracotta模式的Ehcache缓存。缓存发现是自动完成的,并且有很多选项可以用来调试缓存行为和性能。
使用RMI、JGroups或者JMS来冗余缓存数据:节点可以通过多播或发现者手动配置。状态更新可以通过RMI连接来异步或者同步完成。
Custom:一个综合的插件机制,支持发现和复制的能力。
可用的缓存复制选项。支持的通过RMI、JGroups或JMS进行的异步或同步的缓存复制。
可靠的分发:使用TCP的内建分发机制。
节点发现:节点可以手动配置或者使用多播自动发现,并且可以自动添加和移除节点。对于多播阻塞的情况下,手动配置可以很好地控制。
分布式缓存可以任意时间加入或者离开集群。缓存可以配置在初始化的时候执行引导程序员。
BootstrapCacheLoaderFactory抽象工厂,实现了BootstrapCacheLoader接口(RMI实现)。
缓存服务端。Ehcache提供了一个Cache Server,一个war包,为绝大多数web容器或者是独立的服务器提供支持。
缓存服务端有两组API:面向资源的RESTful,还有就是SOAP。客户端没有实现语言的限制。
RESTful缓存服务器:Ehcached的实现严格遵循RESTful面向资源的架构风格。
SOAP缓存服务端:Ehcache RESTFul Web Services API暴露了单例的CacheManager,他能在ehcache.xml或者IoC容器里面配置。
标准服务端包含了内嵌的Glassfish web容器。它被打成了war包,可以任意部署到支持Servlet 2.5的web容器内。Glassfish V2/3、Tomcat 6和Jetty 6都已经经过了测试。

10、搜索
标准分布式搜索使用了流式查询接口的方式,请参阅文档。

11、Java EE和应用缓存
为普通缓存场景和模式提供高质量的实现。
阻塞缓存:它的机制避免了复制进程并发操作的问题。
SelfPopulatingCache在缓存一些开销昂贵操作时显得特别有用,它是一种针对读优化的缓存。它不需要调用者知道缓存元素怎样被返回,也支持在不阻塞读的情况下刷新缓存条目。
CachingFilter:一个抽象、可扩展的cache filter。
SimplePageCachingFilter:用于缓存基于request URI和Query String的页面。它可以根据HTTP request header的值来选择采用或者不采用gzip压缩方式将页面发到浏览器端。你可以用它来缓存整个Servlet页面,无论你采用的是JSP、 velocity,或者其他的页面渲染技术。
SimplePageFragmentCachingFilter:缓存页面片段,基于request URI和Query String。在JSP中使用jsp:include标签包含。
已经使用Orion和Tomcat测试过,兼容Servlet 2.3、Servlet 2.4规范。
Cacheable命令:这是一种老的命令行模式,支持异步行为、容错。
兼容Hibernate,兼容Google App Engine。
基于JTA的事务支持,支持事务资源管理,二阶段提交和回滚,以及本地事务。

12、开源协议
Apache 2.0 license

 

二、Ehcache的加载模块列表,他们都是独立的库,每个都为Ehcache添加新的功能,可以在此下载

 

  • ehcache-core:API,标准缓存引擎,RMI复制和Hibernate支持
  • ehcache:分布式Ehcache,包括Ehcache的核心和Terracotta的库
  • ehcache-monitor:企业级监控和管理
  • ehcache-web:为Java Servlet Container提供缓存、gzip压缩支持的filters
  • ehcache-jcache:JSR107 JCACHE的实现
  • ehcache-jgroupsreplication:使用JGroup的复制
  • ehcache-jmsreplication:使用JMS的复制
  • ehcache-openjpa:OpenJPA插件
  • ehcache-server:war内部署或者单独部署的RESTful cache server
  • ehcache-unlockedreadsview:允许Terracotta cache的无锁读
  • ehcache-debugger:记录RMI分布式调用事件
  • Ehcache for Ruby:Jruby and Rails支持

Ehcache的结构设计概览:

三、核心定义

 

cache manager:缓存管理器,以前是只允许单例的,不过现在也可以多实例了

cache:缓存管理器内可以放置若干cache,存放数据的实质,所有cache都实现了Ehcache接口

element:单条缓存数据的组成单位

system of record(SOR):可以取到真实数据的组件,可以是真正的业务逻辑、外部接口调用、存放真实数据的数据库等等,缓存就是从SOR中读取或者写入到SOR中去的。

 

代码示例:

Java代码  收藏代码
  1. CacheManager manager = CacheManager.newInstance("src/config/ehcache.xml");  
  2. manager.addCache("testCache");  
  3. Cache test = singletonManager.getCache("testCache");  
  4. test.put(new Element("key1", "value1"));  
  5. manager.shutdown();  

当然,也支持这种类似DSL的配置方式,配置都是可以在运行时动态修改的:

Java代码  收藏代码
  1. Cache testCache = new Cache(  
  2.   new CacheConfiguration("testCache", maxElements)  
  3.     .memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.LFU)  
  4.     .overflowToDisk(true)  
  5.     .eternal(false)  
  6.     .timeToLiveSeconds(60)  
  7.     .timeToIdleSeconds(30)  
  8.     .diskPersistent(false)  
  9.     .diskExpiryThreadIntervalSeconds(0));  

事务的例子:

Java代码  收藏代码
  1. Ehcache cache = cacheManager.getEhcache("xaCache");  
  2. transactionManager.begin();  
  3. try {  
  4.     Element e = cache.get(key);  
  5.     Object result = complexService.doStuff(element.getValue());  
  6.     cache.put(new Element(key, result));  
  7.     complexService.doMoreStuff(result);  
  8.     transactionManager.commit();  
  9. catch (Exception e) {  
  10.     transactionManager.rollback();  
  11. }  

 

四、一致性模型

 

说到一致性,数据库的一致性是怎样的?不妨先来回顾一下数据库的几个隔离级别:

未提交读(Read Uncommitted):在读数据时不会检查或使用任何锁。因此,在这种隔离级别中可能读取到没有提交的数据。会出现脏读、不可重复读、幻象读。
已提交读(Read Committed):只读取提交的数据并等待其他事务释放排他锁。读数据的共享锁在读操作完成后立即释放。已提交读是数据库的默认隔离级别。会出现不可重复读、幻象读。
可重复读(Repeatable Read):像已提交读级别那样读数据,但会保持共享锁直到事务结束。会出现幻象读。
可序列化(Serializable):工作方式类似于可重复读。但它不仅会锁定受影响的数据,还会锁定这个范围,这就阻止了新数据插入查询所涉及的范围。

 

基于以上,再来对比思考下面的一致性模型:

 

1、强一致性模型:系统中的某个数据被成功更新(事务成功返回)后,后续任何对该数据的读取操作都得到更新后的值。这是传统关系数据库提供的一致性模型,也是关系数据库深受人们喜爱的原因之一。强一致性模型下的性能消耗通常是最大的。

 

2、弱一致性模型:系统中的某个数据被更新后,后续对该数据的读取操作得到的不一定是更新后的值,这种情况下通常有个“不一致性时间窗口”存在:即数据更新完成后在经过这个时间窗口,后续读取操作就能够得到更新后的值。

 

3、最终一致性模型:属于弱一致性的一种,即某个数据被更新后,如果该数据后续没有被再次更新,那么最终所有的读取操作都会返回更新后的值。

 

最终一致性模型包含如下几个必要属性,都比较好理解:

 

  • 读写一致:某线程A,更新某条数据以后,后续的访问全部都能取得更新后的数据。
  • 会话内一致:它本质上和上面那一条是一致的,某用户更改了数据,只要会话还存在,后续他取得的所有数据都必须是更改后的数据。
  • 单调读一致:如果一个进程可以看到当前的值,那么后续的访问不能返回之前的值。
  • 单调写一致:对同一进程内的写行为必须是保序的,否则,写完毕的结果就是不可预期的了。

4、Bulk Load:这种模型是基于批量加载数据到缓存里面的场景而优化的,没有引入锁和常规的淘汰算法这些降低性能的东西,它和最终一致性模型很像,但是有批量、高速写和弱一致性保证的机制。

 

这样几个API也会影响到一致性的结果:

 

1、显式锁(Explicit Locking:如果我们本身就配置为强一致性,那么自然所有的缓存操作都具备事务性质。而如果我们配置成最终一致性时,再在外部使用显式锁API,也可以达到事务的效果。当然这样的锁可以控制得更细粒度,但是依然可能存在竞争和线程阻塞。

 

2、无锁可读取视图(UnlockedReadsView):一个允许脏读的decorator,它只能用在强一致性的配置下,它通过申请一个特殊的写锁来比完全的强一致性配置提升性能。

举例如下,xml配置为强一致性模型:

Xml代码  收藏代码
  1. <cache name="myCache"  
  2.      maxElementsInMemory="500"  
  3.      eternal="false"  
  4.      overflowToDisk="false"  
  5.    <terracotta clustered="true" consistency="strong" />  
  6. </cache>  

但是使用UnlockedReadsView:

Java代码  收藏代码
  1. Cache cache = cacheManager.getEhcache("myCache");  
  2. UnlockedReadsView unlockedReadsView = new UnlockedReadsView(cache, "myUnlockedCache");  

 

3、原子方法(Atomic methods):方法执行是原子化 的,即CAS操作(Compare and Swap)。CAS最终也实现了强一致性的效果,但不同的是,它是采用乐观锁而不是悲观锁来实现的。在乐观锁机制下,更新的操作可能不成功,因为在这过程 中可能会有其他线程对同一条数据进行变更,那么在失败后需要重新执行更新操作。现代的CPU都支持CAS原语了。

Java代码  收藏代码
  1. cache.putIfAbsent(Element element);  
  2. cache.replace(Element oldOne, Element newOne);  
  3. cache.remove(Element);  

 

五、缓存拓扑类型

 

1、独立缓存(Standalone Ehcache):这样的缓存应用节点都是独立的,互相不通信。

 

2、分布式缓存(Distributed Ehcache):数据存储在Terracotta的服务器阵列(Terracotta Server Array,TSA)中,但是最近使用的数据,可以存储在各个应用节点中。

 

逻辑视角:


L1缓存就在各个应用节点上,而L2缓存则放在Cache Server阵列中。

 

组网视角:

 

模型存储视角:


L1级缓存是没有持久化存储的。另外,从缓存数据量上看,server端远大于应用节点。

 

3、复制式缓存(Replicated Ehcache):缓存数据时同时存放在多个应用节点的,数据复制和失效的事件以同步或者异步的形式在各个集群节点间传播。上述事件到来时,会阻塞写线程的操作。在这种模式下,只有弱一致性模型。

 

它有如下几种事件传播机制:RMI、JGroups、JMS和Cache Server。

 

RMI模式下,所有节点全部对等:

 

JGroup模式:可以配置单播或者多播,协议栈和配置都非常灵活。

 

Xml代码  收藏代码
  1. <cacheManagerPeerProviderFactory  
  2. class="net.sf.ehcache.distribution.jgroups.JGroupsCacheManagerPeerProviderFactory"  
  3. properties="connect=UDP(mcast_addr=231.12.21.132;mcast_port=45566;):PING:  
  4. MERGE2:FD_SOCK:VERIFY_SUSPECT:pbcast.NAKACK:UNICAST:pbcast.STABLE:FRAG:pbcast.GMS"  
  5. propertySeparator="::"  
  6. />  

 

 

JMS模式:这种模式的核心就是一个消息队列,每个应用节点都订阅预先定义好的主题,同时,节点有元素更新时,也会发布更新元素到主题中去。JMS规范实现者上,Open MQ和Active MQ这两个,Ehcache的兼容性都已经测试过。

Cache Server模式:这种模式下存在主从节点,通信可以通过RESTful的API或者SOAP。

无论上面哪个模式,更新事件又可以分为updateViaCopy或updateViaInvalidate,后者只是发送一个过期消息,效率要高得多。

复制式缓存容易出现数据不一致的问题,如果这成为一个问题,可以考虑使用数据同步分发的机制。

 

即便不采用分布式缓存和复制式缓存,依然会出现一些不好的行为,比如:

 

缓存漂移(Cache Drift):每个应用节点只管理自己的缓存,在更新某个节点的时候,不会影响到其他的节点,这样数据之间可能就不同步了。这在web会话数据缓存中情况尤甚。

 

数据库瓶颈(Database Bottlenecks ):对于单实例的应用来说,缓存可以保护数据库的读风暴;但是,在集群的环境下,每一个应用节点都要定期保持数据最新,节点越多,要维持这样的情况对数据库的开销也越大。

 

六、存储方式

 

1、堆内存储:速度快,但是容量有限。

 

2、堆外(OffHeapStore)存储:被称为 BigMemory,只在企业版本的Ehcache中提供,原理是利用nio的DirectByteBuffers实现,比存储到磁盘上快,而且完全不受 GC的影响,可以保证响应时间的稳定性;但是direct buffer的在分配上的开销要比heap buffer大,而且要求必须以字节数组方式存储,因此对象必须在存储过程中进行序列化,读取则进行反序列化操作,它的速度大约比堆内存储慢一个数量级。

(注:direct buffer不受GC影响,但是direct buffer归属的的JAVA对象是在堆上且能够被GC回收的,一旦它被回收,JVM将释放direct buffer的堆外空间。)

 

3、磁盘存储

 

七、缓存使用模式

 

cache-aside:直接操作。先询问cache某条缓存数据是否存在,存在的话直接从cache中返回数据,绕过SOR;如果不存在,从SOR中取得数据,然后再放入cache中。

 

Java代码  收藏代码
  1. public V readSomeData(K key)   
  2. {  
  3.    Element element;  
  4.    if ((element = cache.get(key)) != null) {  
  5.        return element.getValue();  
  6.    }  
  7.    if (value = readDataFromDataStore(key)) != null) {  
  8.        cache.put(new Element(key, value));  
  9.    }   
  10.    return value;  
  11. }  

 

cache-as-sor:结合了read-through、write-through或write-behind操作,通过给SOR增加了一层代理,对外部应用访问来说,它不用区别数据是从缓存中还是从SOR中取得的。

read-through

write-through

write-behind(write-back):既将写的过程变为异步的,又进一步延迟写入数据的过程。

 

 

Copy Cache的两个模式:CopyOnRead和CopyOnWrite。

CopyOnRead指的是在读缓存数据的请求到达时,如果发现数据已经过期,需要重新从源处获取,发起的copy element的操作(pull);

CopyOnWrite则是发生在真实数据写入缓存时,发起的更新其他节点的copy element的操作(push)。

 

前者适合在不允许多个线程访问同一个element的时候使用,后者则允许你自由控制缓存更新通知的时机。

更多push和pull的变化和不同,也可参见这里

 

八、多种配置方式

 

包括配置文件、声明式配置、编程式配置,甚至通过指定构造器的参数来完成配置,配置设计的原则包括:

所有配置要放到一起

缓存的配置可以很容易在开发阶段、运行时修改

错误的配置能够在程序启动时发现,在运行时修改出错则需要抛出运行时异常

提供默认配置,几乎所有的配置都是可选的,都有默认值

 

九、自动资源控制(Automatic Resource Control,ARC):

 

它是提供了一种智能途径来控制缓存,调优性能。特性包括:

内存内缓存对象大小的控制,避免OOM出现

池化(cache manager级别)的缓存大小获取,避免单独计算缓存大小的消耗

灵活的独立基于层的大小计算能力,下图中可以看到,不同层的大小都是可以单独控制的

可以统计字节大小、缓存条目数和百分比

优化高命中数据的获取,以提升性能,参见下面对缓存数据在不同层之间的流转的介绍

缓存数据的流转包括了这样几种行为:

Flush:缓存条目向低层次移动。

Fault:从低层拷贝一个对象到高层。在获取缓存的过程中,某一层发现自己的该缓存条目已经失效,就触发了Fault行为。

Eviction:把缓存条目除去。

Expiration:失效状态。

Pinning:强制缓存条目保持在某一层。

下面的图反映了数据在各个层之间的流转,也反映了数据的生命周期:

 

十、监控功能

 

监控的拓扑:


每个应用节点部署一个监控探针,通过TCP协议与监控服务器联系,最终将数据提供给富文本客户端或者监控操作服务器。

 

十一、广域网复制

缓存数据复制方面,Ehcache允许两个地理位置各异的节点在广域网下维持数据一致性,同时它提供了这样几种方案(注:下面的示例都只绘制了两个节点的情形,实际可以推广到N个节点):

 

第一种方案:Terracotta Active/Mirror Replication


这种方案下,服务端包含一个活跃节点,一个备份节点;各个应用节点全部靠该活跃节点提供读写服务。这种方式最简单,管理容易;但是,需要寄希望于理想的网络状况,服务器之间和客户端到服务器之间都存在走WAN的情况,这样的方案其实最不稳定。

 

第二种方案:Transactional Cache Manager Replication


这 种方案下,数据读取不需要经过WAN,写入数据时写入两份,分别由两个cache manager处理,一份在本地Server,一份到其他Server去。这种方案下读的吞吐量较高而且延迟较低;但是需要引入一个XA事务管理器,两个 cache manager写两份数据导致写开销较大,而且过WAN的写延迟依然可能导致系统响应的瓶颈。

 

第三种方案:Messaging based (AMQ) replication


这 种方案下,引入了批量处理和队列,用以减缓WAN的瓶颈出现,同时,把处理读请求和复制逻辑从Server Array物理上就剥离开,避免了WAN情况恶化对节点读取业务的影响。这种方案要较高的吞吐量和较低的延迟,读/复制的分离保证了可以提供完备的消息分 发保证、冲突处理等特性;但是它较为复杂,而且还需要一个消息总线。

 

有一些Ehcache特性应用较少或者比较边缘化,没有提到,例如对于JMX的支持;还有一些则是有类似的特性和介绍了,例如对于WEB的支持,请参见我这篇关于OSCache的解读,其中的“web支持”一节有详细的原理分析。

 

最后,关于Ehcache的性能比对,下面这张图来自Ehcache的创始人Greg Luck的blog

 

put/get上Ehcache要500-1000倍快过Memcached。原因何在?他自己分析道:“In-process caching and asynchronous replication are a clear performance winner”。有关它详细的内容还是请参阅他的blog吧。


转自:

http://raychase.iteye.com/blog/1545906

posted @ 2012-07-26 13:07 小胡子 阅读(8355) | 评论 (0)编辑 收藏

 Windows 7,在使用 JGroups 的时候提示错误:

java.lang.RuntimeException: the type of the stack (IPv6) and the user supplied addresses (IPv4) don't match: /xxx.xxx.xxx.xxx.

在操作系统中禁用 IPv6 是没用的,这个需要在 JVM 中禁用 IPv6 特性,加入以下参数即可:

-Djava.net.preferIPv4Stack=true

posted @ 2012-07-26 12:57 小胡子 阅读(949) | 评论 (0)编辑 收藏

     摘要: Spring框架提供了构造Web应用程序的全能MVC模块。Spring MVC分离了控制器、模型对象、分派器以及处理程序对象的角色,这种分离让它们更容易进行制定。是一个标准的MVC框架。   那你猜一猜哪一部分应该是哪一部分? SpringMVC框架图 SpringMVC接口解释   DispatcherServlet接口:   Spring提供的前端...  阅读全文

posted @ 2012-07-24 13:52 小胡子 阅读(17252) | 评论 (5)编辑 收藏

看到别人的iphone壁纸那么好看,有的可以动,有的可以与应用程序结合使用,你想到没有这种效果是怎么实现的,今天推荐给大家40个创造性的壁纸,你可以下载这些壁纸使用到你的应用程序当中,无论你是一个壁纸爱好者,还是设计者,下面的这些壁纸能够为你的开发节省一些宝贵的时间,这些壁纸下载到电脑也可以使用,不受大小所限制,作为设计者必须使用其他方式,使壁纸的特性能够与智能手机使用相关联。

Icons Skins 2 -Icons Skins 2是包括42种最适合您的应用程序图标的最佳屏幕背景,他的分辨率大小是960×480

图标外观2

Shelf Backgrounds and Wallpapers  - 你可以自定义自己的主屏幕,450 +的发光效果,包括24 +圣诞发光效果。

货架背景和壁纸

蓝图 -它看起来像一个iPhone的界面设计蓝图。

蓝图

Fresh Touch  -这是一套高清晰度的壁纸,。

新鲜的触摸

Hanging -在这个壁纸上面可以放你的应用程序。

挂

Snazzy -时髦的壁纸让你的iPhone主屏幕丰富多彩。

时髦

Doodle Jump -像回到学校的感觉?

涂鸦跳转

Pirate Parrot  -海盗,一只鹦鹉,这些作为你的壁纸

海盗鹦鹉

Pencils  

铅笔

Fire And Metal -看起来这个壁纸像在放火,呵呵

消防和金属

Screws & Bricks  -像是回到车库。

螺丝砖

Screws & Wood  -螺钉和木?是的,我们也有。

螺丝及木

Liege -注人?这种壁纸原来将即时贴到您的应用程序图标

列日

媚俗 -女装图片完美的iPhone壁纸。

媚俗

DOTA 2 

DOTA 2

面料 -面料,简单的织物壁纸作为你的iPhone主屏幕。

布

TRON  

创新福星

创新-轻便自行车 -看这个壁纸像什么

创新福星 - 轻便自行车

霓虹灯箭头 -这几乎就像是一个没有电池排水位的动态壁纸。

霓虹灯箭头

纳米管 -这种壁纸与微观管放在您的主屏幕,以容纳您的应用程序图标。

纳米管

Grid 

格

塞尔达 -塞尔达在您的手机。记住他不是游戏,主屏幕壁纸!

塞尔达

PAC-MAN -这是Pac-Man的您的主屏幕上跑来跑去!

PAC-MAN

金刚 -记住金刚?是的,现在它可以为您的iPhone的壁纸。

金刚

挖挖 -让另一个最喜欢的游戏添加到主屏幕,!

掏挖

超级玛丽 

超级马里奥兄弟

圣诞节 -当你看到这个背景时会想到当当响的声音吗

圣诞节

红沙发 -照明和红色的搭配

红沙发

Cooliog - Cooliog增加您的应用程序图标区的照明。

Cooliog

Pirate 

海盗

Wood Shelves

木制棚架

spargetts货架

spargetts货架

飞溅 

溅

工具箱HD -爱红?工具箱的HD有鲜红的货架上

“工具箱”HD

货架 -红色为您太热?然后再为这个寒冷的白色应用现成的壁纸。

架

4Shelves -这是一个的有趣trophycase应用架子

4Shelves墙

 -行是微妙的,但它们不包含应用程序,你很难发现它们的存在。

行

书虫 -喜欢你的iPhone 4革背景?试试这个壁纸,

书呆子

iPhone 4的纹理壁纸 - iPhone 4的纹理壁纸

iPhone 4的纹理壁纸

Grungy Walls -让你的iPhone有重金属的感觉。

肮脏的墙壁

3
0
(请您对文章做出评价)
« 博主前一篇:把RSS订阅到邮箱去

原文:
http://www.cnblogs.com/web8cn/archive/2012/07/19/2598745.html

posted @ 2012-07-19 13:27 小胡子 阅读(217) | 评论 (0)编辑 收藏

引子

  “请写一个Singleton。”面试官微笑着和我说。

  “这可真简单。”我心里想着,并在白板上写下了下面的Singleton实现:

 
class Singleton  { 
  
public: static Singleton& Instance() {  
  static Singleton singleton;  
  
return singleton;   
}  
  private: Singleton() { }; 
};

  “那请你讲解一下该实现的各组成。”面试官的脸上仍然带着微笑。

  “首先要说的就是Singleton的构造函数。由于Singleton限制其类型实例有且只能有一个,因此我们应通过将构造函数设置为非公有 来保证其不会被用户代码随意创建。而在类型实例访问函数中,我们通过局部静态变量达到实例仅有一个的要求。另外,通过该静态变量,我们可以将该实例的创建 延迟到实例访问函数被调用时才执行,以提高程序的启动速度。”

 

保护

  “说得不错,而且更可贵的是你能注意到对构造函数进行保护。毕竟中间件代码需要非常严谨才能防止用户代码的误用。那么,除了构造函数以外,我们还需要对哪些组成进行保护?”

  “还需要保护的有拷贝构造函数,析构函数以及赋值运算符。或许,我们还需要考虑取址运算符。这是因为编译器会在需要的时候为这些成员创建一个默认的实现。”

  “那你能详细说一下编译器会在什么情况下创建默认实现,以及创建这些默认实现的原因吗?”面试官继续问道。

  “在这些成员没有被声明的情况下,编译器将使用一系列默认行为:对实例的构造就是分配一部分内存,而不对该部分内存做任何事情;对实例的拷贝也 仅仅是将原实例中的内存按位拷贝到新实例中;而赋值运算符也是对类型实例所拥有的各信息进行拷贝。而在某些情况下,这些默认行为不再满足条件,那么编译器 将尝试根据已有信息创建这些成员的默认实现。这些影响因素可以分为几种:类型所提供的相应成员,类型中的虚函数以及类型的虚基类。”

  “就以构造函数为例,如果当前类型的成员或基类提供了由用户定义的构造函数,那么仅进行内存拷贝可能已经不是正确的行为。这是因为该成员的构造 函数可能包含了成员初始化,成员函数调用等众多执行逻辑。此时编译器就需要为这个类型生成一个默认构造函数,以执行对成员或基类构造函数的调用。另外,如 果一个类型声明了一个虚函数,那么编译器仍需要生成一个构造函数,以初始化指向该虚函数表的指针。如果一个类型的各个派生类中拥有一个虚基类,那么编译器 同样需要生成构造函数,以初始化该虚基类的位置。这些情况同样需要在拷贝构造函数中考虑:如果一个类型的成员变量拥有一个拷贝构造函数,或者其基类拥有一 个拷贝构造函数,位拷贝就不再满足要求了,因为拷贝构造函数内可能执行了某些并不是位拷贝的逻辑。同时如果一个类型声明了虚函数,拷贝构造函数需要根据目 标类型初始化虚函数表指针。如基类实例经过拷贝后,其虚函数表指针不应指向派生类的虚函数表。同理,如果一个类型的各个派生类中拥有一个虚派生,那么编译 器也应为其生成拷贝构造函数,以正确设置各个虚基类的偏移。”

  “当然,析构函数的情况则略为简单一些:只需要调用其成员的析构函数以及基类的析构函数即可,而不需要再考虑对虚基类偏移的设置及虚函数表指针的设置。”

  “在这些默认实现中,类型实例的各个原生类型成员并没有得到初始化的机会。但是这一般被认为是软件开发人员的责任,而不是编译器的责任。”说完这些,我长出一口气,心里也暗自庆幸曾经研究过该部分内容。

  “你刚才提到需要考虑保护取址运算符,是吗?我想知道。”

  “好的。首先要声明的是,几乎所有的人都会认为对取址运算符的重载是邪恶的。甚至说,boost为了防止该行为所产生的错误更是提供了 addressof()函数。而另一方面,我们需要讨论用户为什么要用取址运算符。Singleton所返回的常常是一个引用,对引用进行取址将得到相应 类型的指针。而从语法上来说,引用和指针的最大区别在于是否可以被delete关键字删除以及是否可以为NULL。但是Singleton返回一个引用也 就表示其生存期由非用户代码所管理。因此使用取址运算符获得指针后又用delete关键字删除Singleton所返回的实例明显是一个用户错误。综上所 述,通过将取址运算符设置为私有没有多少意义。”

 

重用

  “好的,现在我们换个话题。如果我现在有几个类型都需要实现为Singleton,那我应怎样使用你所编写的这段代码呢?”

  刚刚还在洋洋自得的我恍然大悟:这个Singleton实现是无法重用的。没办法,只好一边想一边说:“一般来说,较为流行的重用方法一共有三 种:组合、派生以及模板。首先可以想到的是,对Singleton的重用仅仅是对Instance()函数的重用,因此通过从Singleton派生以继 承该函数的实现是一个很好的选择。而Instance()函数如果能根据实际类型更改返回类型则更好了。因此奇异递归模板(CRTP,The Curiously Recurring Template Pattern)模式则是一个非常好的选择。”于是我在白板上飞快地写下了下面的代码:

 1 template <typename T>  2 class Singleton  3 {  4 public:  5     static T& Instance()  6     {  7         static T s_Instance;  8         return s_Instance;  9     } 10  11 protected: 12     Singleton(void) {} 13     ~Singleton(void) {} 14  15 private: 16     Singleton(const Singleton& rhs) {} 17     Singleton& operator = (const Singleton& rhs) {} 18 };

  同时我也在白板上写下了对该Singleton实现进行重用的方法:

1 class SingletonInstance : public Singleton<SingletonInstance>…

  “在需要重用该Singleton实现时,我们仅仅需要从Singleton派生并将Singleton的泛型参数设置为该类型即可。”

 

生存期管理

  “我看你在实现中使用了静态变量,那你是否能介绍一下上面Singleton实现中有关生存期的一些特征吗?毕竟生存期管理也是编程中的一个重要话题。”面试官提出了下一个问题。

  “嗯,让我想一想。我认为对Singleton的生存期特性的讨论需要分为两个方面:Singleton内使用的静态变量的生存期以及 Singleton外在用户代码中所表现的生存期。Singleton内使用的静态变量是一个局部静态变量,因此只有在Singleton的 Instance()函数被调用时其才会被创建,从而拥有了延迟初始化(Lazy)的效果,提高了程序的启动性能。同时该实例将生存至程序执行完毕。而就 Singleton的用户代码而言,其生存期贯穿于整个程序生命周期,从程序启动开始直到程序执行完毕。当然,Singleton在生存期上的一个缺陷就 是创建和析构时的不确定性。由于Singleton实例会在Instance()函数被访问时被创建,因此在某处新添加的一处对Singleton的访问 将可能导致Singleton的生存期发生变化。如果其依赖于其它组成,如另一个Singleton,那么对它们的生存期进行管理将成为一个灾难。甚至可 以说,还不如不用Singleton,而使用明确的实例生存期管理。”

  “很好,你能提到程序初始化及关闭时单件的构造及析构顺序的不确定可能导致致命的错误这一情况。可以说,这是通过局部静态变量实现 Singleton的一个重要缺点。而对于你所提到的多个Singleton之间相互关联所导致的生存期管理问题,你是否有解决该问题的方法呢?”

  我突然间意识到自己给自己出了一个难题:“有,我们可以将Singleton的实现更改为使用全局静态变量,并将这些全局静态变量在文件中按照特定顺序排序即可。”

  “但是这样的话,静态变量将使用eager initialization的方式完成初始化,可能会对性能影响较大。其实,我想听你说的是,对于具有关联的两个Singleton,对它们进行使用的 代码常常局限在同一区域内。该问题的一个解决方法常常是将对它们进行使用的管理逻辑实现为Singleton,而在内部逻辑中对它们进行明确的生存期管 理。但不用担心,因为这个答案也过于经验之谈。那么下一个问题,你既然提到了全局静态变量能解决这个问题,那是否可以讲解一下全局静态变量的生命周期是怎 样的呢?”

  “编译器会在程序的main()函数执行之前插入一段代码,用来初始化全局变量。当然,静态变量也包含在内。该过程被称为静态初始化。”

  “嗯,很好。使用全局静态变量实现Singleton的确会对性能造成一定影响。但是你是否注意到它也有一定的优点呢?”

  见我许久没有回答,面试官主动帮我解了围:“是线程安全性。由于在静态初始化时用户代码还没有来得及执行,因此其常常处于单线程环境下,从而保 证了Singleton真的只有一个实例。当然,这并不是一个好的解决方法。所以,我们来谈谈Singleton的多线程实现吧。”

 

多线程

  “首先请你写一个线程安全的Singleton实现。”

  我拿起笔,在白板上写下早已烂熟于心的多线程安全实现:

 1 template <typename T>  2 class Singleton  3 {  4 public:  5     static T& Instance()  6     {  7         if (m_pInstance == NULL)  8         {  9             Lock lock; 10             if (m_pInstance == NULL) 11             { 12                 m_pInstance = new T(); 13                 atexit(Destroy); 14             } 15             return *m_pInstance; 16         } 17         return *m_pInstance; 18     } 19  20 protected: 21     Singleton(void) {} 22     ~Singleton(void) {} 23  24 private: 25     Singleton(const Singleton& rhs) {} 26     Singleton& operator = (const Singleton& rhs) {} 27  28     void Destroy() 29     { 30         if (m_pInstance != NULL) 31             delete m_pInstance; 32         m_pInstance = NULL; 33     } 34  35     static T* volatile m_pInstance; 36 }; 37  38 template <typename T> 39 T* Singleton<T>::m_pInstance = NULL;

  “写得很精彩。那你是否能逐行讲解一下你写的这个Singleton实现呢?”

  “好的。首先,我使用了一个指针记录创建的Singleton实例,而不再是局部静态变量。这是因为局部静态变量可能在多线程环境下出现问题。”

  “我想插一句话,为什么局部静态变量会在多线程环境下出现问题?”

  “这是由局部静态变量的实际实现所决定的。为了能满足局部静态变量只被初始化一次的需求,很多编译器会通过一个全局的标志位记录该静态变量是否已经被初始化的信息。那么,对静态变量进行初始化的伪码就变成下面这个样子:”。

1 bool flag = false; 2 if (!flag) 3 { 4     flag = true; 5     staticVar = initStatic(); 6 }

  “那么在第一个线程执行完对flag的检查并进入if分支后,第二个线程将可能被启动,从而也进入if分支。这样,两个线程都将执行对静态变量 的初始化。因此在这里,我使用了指针,并在对指针进行赋值之前使用锁保证在同一时间内只能有一个线程对指针进行初始化。同时基于性能的考虑,我们需要在每 次访问实例之前检查指针是否已经经过初始化,以避免每次对Singleton的访问都需要请求对锁的控制权。”

  “同时,”我咽了口口水继续说,“因为new运算符的调用分为分配内存、调用构造函数以及为指针赋值三步,就像下面的构造函数调用:”

1 SingletonInstance pInstance = new SingletonInstance();

  “这行代码会转化为以下形式:”

1 SingletonInstance pHeap = __new(sizeof(SingletonInstance)); 2 pHeap->SingletonInstance::SingletonInstance(); 3 SingletonInstance pInstance = pHeap;

  “这样转换是因为在C++标准中规定,如果内存分配失败,或者构造函数没有成功执行, new运算符所返回的将是空。一般情况下,编译器不会轻易调整这三步的执行顺序,但是在满足特定条件时,如构造函数不会抛出异常等,编译器可能出于优化的 目的将第一步和第三步合并为同一步:”

1 SingletonInstance pInstance = __new(sizeof(SingletonInstance)); 2 pInstance->SingletonInstance::SingletonInstance();

  “这样就可能导致其中一个线程在完成了内存分配后就被切换到另一线程,而另一线程对Singleton的再次访问将由于pInstance已经 赋值而越过if分支,从而返回一个不完整的对象。因此,我在这个实现中为静态成员指针添加了volatile关键字。该关键字的实际意义是由其修饰的变量 可能会被意想不到地改变,因此每次对其所修饰的变量进行操作都需要从内存中取得它的实际值。它可以用来阻止编译器对指令顺序的调整。只是由于该关键字所提 供的禁止重排代码是假定在单线程环境下的,因此并不能禁止多线程环境下的指令重排。”

  “最后来说说我对atexit()关键字的使用。在通过new关键字创建类型实例的时候,我们同时通过atexit()函数注册了释放该实例的 函数,从而保证了这些实例能够在程序退出前正确地析构。该函数的特性也能保证后被创建的实例首先被析构。其实,对静态类型实例进行析构的过程与前面所提到 的在main()函数执行之前插入静态初始化逻辑相对应。”

 

引用还是指针

  “既然你在实现中使用了指针,为什么仍然在Instance()函数中返回引用呢?”面试官又抛出了新的问题。

  “这是因为Singleton返回的实例的生存期是由Singleton本身所决定的,而不是用户代码。我们知道,指针和引用在语法上的最大区 别就是指针可以为NULL,并可以通过delete运算符删除指针所指的实例,而引用则不可以。由该语法区别引申出的语义区别之一就是这些实例的生存期意 义:通过引用所返回的实例,生存期由非用户代码管理,而通过指针返回的实例,其可能在某个时间点没有被创建,或是可以被删除的。但是这两条 Singleton都不满足,因此在这里,我使用指针,而不是引用。”

  “指针和引用除了你提到的这些之外,还有其它的区别吗?”

  “有的。指针和引用的区别主要存在于几个方面。从低层次向高层次上来说,分为编译器实现上的,语法上的以及语义上的区别。就编译器的实现来说, 声明一个引用并没有为引用分配内存,而仅仅是为该变量赋予了一个别名。而声明一个指针则分配了内存。这种实现上的差异就导致了语法上的众多区别:对引用进 行更改将导致其原本指向的实例被赋值,而对指针进行更改将导致其指向另一个实例;引用将永远指向一个类型实例,从而导致其不能为NULL,并由于该限制而 导致了众多语法上的区别,如dynamic_cast对引用和指针在无法成功进行转化时的行为不一致。而就语义而言,前面所提到的生存期语义是一个区别, 同时一个返回引用的函数常常保证其返回结果有效。一般来说,语义区别的根源常常是语法上的区别,因此上面的语义区别仅仅是列举了一些例子,而真正语义上的 差别常常需要考虑它们的语境。”

  “你在前面说到了你的多线程内部实现使用了指针,而返回类型是引用。在编写过程中,你是否考虑了实例构造不成功的情况,如new运算符运行失败?”

  “是的。在和其它人进行讨论的过程中,大家对于这种问题有各自的理解。首先,对一个实例的构造将可能在两处抛出异常:new运算符的执行以及构 造函数抛出的异常。对于new运算符,我想说的是几点。对于某些操作系统,例如Windows,其常常使用虚拟地址,因此其运行常常不受物理内存实际大小 的限制。而对于构造函数中抛出的异常,我们有两种策略可以选择:在构造函数内对异常进行处理,以及在构造函数之外对异常进行处理。在构造函数内对异常进行 处理可以保证类型实例处于一个有效的状态,但一般不是我们想要的实例状态。这样一个实例会导致后面对它的使用更为繁琐,例如需要更多的处理逻辑或再次导致 程序执行异常。反过来,在构造函数之外对异常进行处理常常是更好的选择,因为软件开发人员可以根据产生异常时所构造的实例的状态将一定范围内的各个变量更 改为合法的状态。举例来说,我们在一个函数中尝试创建一对相互关联的类型实例,那么在一个实例的构造函数抛出了异常时,我们不应该在构造函数里对该实例的 状态进行维护,因为前一个实例的构造是按照后一个实例会正常创建来进行的。相对来说,放弃后一个实例,并将前一个实例删除是一个比较好的选择。”

  我在白板上比划了一下,继续说到:“我们知道,异常有两个非常明显的缺陷:效率,以及对代码的污染。在太小的粒度中使用异常,就会导致异常使用次数的增加,对于效率以及代码的整洁型都是伤害。同样地,对拷贝构造函数等组成常常需要使用类似的原则。”

  “反过来说,Singleton的使用也可以保持着这种原则。Singleton仅仅是一个包装好的全局实例,对其的创建如果一旦不成功,在较高层次上保持正常状态同样是一个较好的选择。”

 

Anti-Patten

  “既然你提到了Singleton仅仅是一个包装好的全局变量,那你能说说它和全局变量的相同与不同么?”

  “单件可以说是全局变量的替代品。其拥有全局变量的众多特点:全局可见且贯穿应用程序的整个生命周期。除此之外,单件模式还拥有一些全局变量所 不具有的性质:同一类型的对象实例只能有一个,同时适当的实现还拥有延迟初始化(Lazy)的功能,可以避免耗时的全局变量初始化所导致的启动速度不佳等 问题。要说明的是,Singleton的最主要目的并不是作为一个全局变量使用,而是保证类型实例有且仅有一个。它所具有的全局访问特性仅仅是它的一个副 作用。但正是这个副作用使它更类似于包装好的全局变量,从而允许各部分代码对其直接进行操作。软件开发人员需要通过仔细地阅读各部分对其进行操作的代码才 能了解其真正的使用方式,而不能通过接口得到组件依赖性等信息。如果Singleton记录了程序的运行状态,那么该状态将是一个全局状态。各个组件对其 进行操作的调用时序将变得十分重要,从而使各个组件之间存在着一种隐式的依赖。”

  “从语法上来讲,首先Singleton模式实际上将类型功能与类型实例个数限制的代码混合在了一起,违反了SRP。其次Singleton模式在Instance()函数中将创建一个确定的类型,从而禁止了通过多态提供另一种实现的可能。”

  “但是从系统的角度来讲,对Singleton的使用则是无法避免的:假设一个系统拥有成百上千个服务,那么对它们的传递将会成为系统的一个灾 难。从微软所提供的众多类库上来看,其常常提供一种方式获得服务的函数,如GetService()等。另外一个可以减轻Singleton模式所带来不 良影响的方法则是为Singleton模式提供无状态或状态关联很小的实现。”

  “也就是说,Singleton本身并不是一个非常差的模式,对其使用的关键在于何时使用它并正确的使用它。”

  面试官抬起手腕看了看时间:“好了,时间已经到了。你的C++功底已经很好了。我相信,我们会在不久的将来成为同事。”

 

笔者注:这本是Writing Patterns Line by Line的一篇文章,但最后想想,写模式的人太多了,我还是省省吧。。。

下一篇回归WPF,环境刚好。可能中间穿插些别的内容,比如HTML5,JS,安全等等。

头一次写小品文,不知道效果是不是好。因为这种文章的特点是知识点分散,而且隐藏在文章的每一句话中。。。好处就是写起来轻松,呵呵。。。


 


 

posted @ 2012-07-19 13:09 小胡子 阅读(190) | 评论 (0)编辑 收藏

错误
Server Tomcat v6.0 Server at localhost was unable to start within 45 seconds. If the server requires more time, try increasing the timeout in the server editor.
修改 workspace\.metadata\.plugins\org.eclipse.wst.server.core\servers.xml文件。
<servers>
<server hostname="localhost" id="JBoss v5.0 at localhost" name="JBoss v5.0 at
localhost" runtime-id="JBoss v5.0" server-type="org.eclipse.jst.server.generic.jboss5"
server-type-id="org.eclipse.jst.server.generic.jboss5" start-timeout="1000" stop-
timeout="15" timestamp="0">
<map jndiPort="1099" key="generic_server_instance_properties" port="8090"
serverAddress="127.0.0.1" serverConfig="default"/>
</server>
</servers>
把 start-timeout="45" 改为 start-timeout="1000" 或者更长
重启eclipse就可以了。
这个原因就是:启动tomcat需要的时间比45秒大了,Eclipse会判断tomcat在默认的时间是否启动了,如果在默认45秒没有启动就会报错了。

posted @ 2012-07-18 15:36 小胡子 阅读(4478) | 评论 (1)编辑 收藏

在看java performance的时候看到一些同步的名词,偏向锁、轻量级锁之类的,于是想先了解一下虚拟机中的锁机制,于是找到了这篇文章。发现是《深入理解Java虚拟机:JVM高级特性与最佳实践》一书的章节,讲得干脆好懂,差点就有要去买一本的冲动-----还是明天吧。以下是文章转载:

------------------------------------------------------------------------------------------------------------------------------------------------------------------

高效并发是JDK1.6的一个重要主题,HotSpot虚拟机开发团队在这个版本上花费了大量的精力去实现各种锁优化技术,如适应性自旋 (AdaptiveSpinning)、锁削除(Lock Elimination)、锁膨胀(Lock Coarsening)、轻量级锁(LightweightLocking)、偏向锁(BiasedLocking)等,这些技术都是为了在线程之间更高 效地共享数据,以及解决竞争问题,从而提高程序的执行效率。


13.3.1 自旋锁与自适应自旋

  前面我们讨论互斥同步的时候,提到了互斥同步对性能最大的影响是阻塞的实现,挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给系统的并发 性能带来了很大的压力。同时,虚拟机的开发团队也注意到在许多应用上,共享数据的锁定状态只会持续很短的一段时间,为了这段时间去挂起和恢复线程并不值 得。如果物理机器有一个以上的处理器,能让两个或以上的线程同时并行执行,我们就可以让后面请求锁的那个线程“稍等一会”,但不放弃处理器的执行时间,看 看持有锁的线程是否很快就会释放锁。为了让线程等待,我们只须让线程执行一个忙循环(自旋),这项技术就是所谓的自旋锁。
  自旋锁在JDK 1.4.2中就已经引入,只不过默认是关闭的,可以使用-XX:+UseSpinning参数来开启,在JDK1.6中就已经改为默认开启了。自旋等待不 能代替阻塞,且先不说对处理器数量的要求,自旋等待本身虽然避免了线程切换的开销,但它是要占用处理器时间的,所以如果锁被占用的时间很短,自旋等待的效 果就会非常好,反之如果锁被占用的时间很长,那么自旋的线程只会白白消耗处理器资源,而不会做任何有用的工作,反而会带来性能的浪费。因此自旋等待的时间 必须要有一定的限度,如果自旋超过了限定的次数仍然没有成功获得锁,就应当使用传统的方式去挂起线程了。自旋次数的默认值是10次,用户可以使用参数 -XX:PreBlockSpin来更改。
  在JDK1.6中引入了自适应的自旋锁。自适应意味着自旋的时间不再固定了,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同 一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也很有可能再次成功,进而它将允许自旋等待持续相对更 长的时间,比如100个循环。另一方面,如果对于某个锁,自旋很少成功获得过,那在以后要获取这个锁时将可能省略掉自旋过程,以避免浪费处理器资源。有了 自适应自旋,随着程序运行和性能监控信息的不断完善,虚拟机对程序锁的状况预测就会越来越准确,虚拟机就会变得越来越“聪明”了。

13.3.2 锁削除

  锁削除是指虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行削除。锁削除的主要判定依据来源于逃逸分析 的数据支持(第11章已经讲解过逃逸分析技术),如果判断到一段代码中,在堆上的所有数据都不会逃逸出去被其他线程访问到,那就可以把它们当作栈上数据对 待,认为它们是线程私有的,同步加锁自然就无须进行。
  也许读者会有疑问,变量是否逃逸,对于虚拟机来说需要使用数据流分析来确定,但是程序员自己应该是很清楚的,怎么会在明知道不存在数据争用的情况下要 求同步呢?答案是有许多同步措施并不是程序员自己加入的,同步的代码在Java程序中的普遍程度也许超过了大部分读者的想象。我们来看看下面代码清单 13-6中的例子,这段非常简单的代码仅仅是输出三个字符串相加的结果,无论是源码字面上还是程序语义上都没有同步。

  代码清单 13-6 一段看起来没有同步的代码
Java代码  收藏代码
  1. public String concatString(String s1, String s2, String s3) {  
  2.     return s1 + s2 + s3;  
  3. }  
  1. public String concatString(String s1, String s2, String s3) {  
  2.     return s1 + s2 + s3;  
  3. }  
   我们也知道,由于String是一个不可变的类,对字符串的连接操作总是通过生成新的String对象来进行的,因此Javac编译器会对String 连接做自动优化。在JDK 1.5之前,会转化为StringBuffer对象的连续append()操作,在JDK1.5及以后的版本中,会转化为StringBuilder对象 的连续append()操作。即代码清单13-6中的代码可能会变成代码清单13-7的样子 。

  代码清单 13-7 Javac转化后的字符串连接操作
Java代码  收藏代码
  1. public String concatString(String s1, String s2, String s3) {  
  2.     StringBuffer sb = new StringBuffer();  
  3.     sb.append(s1);  
  4.     sb.append(s2);  
  5.     sb.append(s3);  
  6.     return sb.toString();  
  7. }  
  1. public String concatString(String s1, String s2, String s3) {  
  2.     StringBuffer sb = new StringBuffer();  
  3.     sb.append(s1);  
  4.     sb.append(s2);  
  5.     sb.append(s3);  
  6.     return sb.toString();  
  7. }  
(注1:实事求是地说,既然谈到锁削除与逃逸分析,那虚拟机就不可能是JDK 1.5之前的版本,所以实际上会转化为非线程安全的StringBuilder来完成字符串拼接,并不会加锁。但是这也不影响笔者用这个例子证明Java对象中同步的普遍性。)

  现在大家还认为这段代码没有涉及同步吗?每个StringBuffer.append()方法中都有一个同步块,锁就是sb对象。虚拟机观察变量 sb,很快就会发现它的动态作用域被限制在concatString()方法内部。也就是sb的所有引用永远不会“逃逸”到concatString() 方法之外,其他线程无法访问到它,所以这里虽然有锁,但是可以被安全地削除掉,在即时编译之后,这段代码就会忽略掉所有的同步而直接执行了。

13.3.3 锁膨胀

  原则上,我们在编写代码的时候,总是推荐将同步块的作用范围限制得尽量小——只在共享数据的实际作用域中才进行同步,这样是为了使得需要同步的操作数量尽可能变小,如果存在锁竞争,那等待锁的线程也能尽快地拿到锁。
  大部分情况下,上面的原则都是正确的,但是如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作是出现在循环体中的,那即使没有线程竞争,频繁地进行互斥同步操作也会导致不必要的性能损耗。
  上面代码清单13-7中连续的append()方法就属于这类情况。如果虚拟机探测到有这样一串零碎的操作都对同一个对象加锁,将会把加锁同步的范围 扩展(膨胀)到整个操作序列的外部,以代码清单13-7为例,就是扩展到第一个append()操作之前直至最后一个append()操作之后,这样只需 要加锁一次就可以了。

13.3.4 轻量级锁

  轻量级锁是JDK1.6之中加入的新型锁机制,它名字中的“轻量级”是相对于使用操作系统互斥量来实现的传统锁而言的,因此传统的锁机制就被称为“重 量级”锁。首先需要强调一点的是,轻量级锁并不是用来代替重量级锁的,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的 性能消耗。
  要理解轻量级锁,以及后面会讲到的偏向锁的原理和运作过程,必须从HotSpot虚拟机的对象(对象头部分)的内存布局开始介绍。HotSpot虚拟 机的对象头(ObjectHeader)分为两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄 (Generational GCAge)等,这部分数据的长度在32位和64位的虚拟机中分别为32个和64个Bits,官方称它为“MarkWord”,它是实现轻量级锁和偏向锁 的关键。另外一部分用于存储指向方法区对象类型数据的指针,如果是数组对象的话,还会有一个额外的部分用于存储数组长度。
  对象头信息是与对象自身定义的数据无关的额外存储成本,考虑到虚拟机的空间效率,MarkWord被设计成一个非固定的数据结构以便在极小的空间内存 储尽量多的信息,它会根据对象的状态复用自己的存储空间。例如在32位的HotSpot虚拟机中对象未被锁定的状态下,MarkWord的32个Bits 空间中的25Bits用于存储对象哈希码(HashCode),4Bits用于存储对象分代年龄,2Bits用于存储锁标志位,1Bit固定为0,在其他 状态(轻量级锁定、重量级锁定、GC标记、可偏向)下对象的存储内容如表13-1所示。

  表13-1 HotSpot虚拟机对象头Mark Word
存储内容 标志位 状态
对象哈希码、对象分代年龄 01 未锁定
指向锁记录的指针 00 轻量级锁定
指向重量级锁的指针 10 膨胀(重量级锁定)
空,不需要记录信息 11 GC标记
偏向线程ID、偏向时间戳、对象分代年龄 01 可偏向

  简单地介绍完了对象的内存布局,我们把话题返回到轻量级锁的执行过程上。在代码进入同步块的时候,如果此同步对象没有被锁定(锁标志位为“01”状 态),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的MarkWord的拷贝(官方把这份拷贝加了一个Displaced前缀,即Displaced MarkWord),这时候线程堆栈与对象头的状态如图13-3所示。


  图13-3 轻量级锁CAS操作之前堆栈与对象的状态


  然后,虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向LockRecord的指针。如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位(MarkWord的最后两个Bits)将转变为“00”,即表示此对象处于轻量级锁定状态,这时候线程堆栈与对象头的状态如图 13-4所示。


  图13-4 轻量级锁CAS操作之后堆栈与对象的状态

注2:图13-3和图13-4来源于HotSpot虚拟机的一位Senior Staff Engineer——Paul Hohensee所写的PPT《The Hotspot Java Virtual Machine》

  如果这个更新操作失败了,虚拟机首先会检查对象的MarkWord是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直 接进入同步块继续执行,否则说明这个锁对象已经被其他线程抢占了。如果有两条以上的线程争用同一个锁,那轻量级锁就不再有效,要膨胀为重量级锁,锁标志的 状态值变为“10”,MarkWord中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。
  上面描述的是轻量级锁的加锁过程,它的解锁过程也是通过CAS操作来进行的,如果对象的MarkWord仍然指向着线程的锁记录,那就用CAS操作把 对象当前的Mark Word和线程中复制的Displaced MarkWord替换回来,如果替换成功,整个同步过程就完成了。如果替换失败,说明有其他线程尝试过获取该锁,那就要在释放锁的同时,唤醒被挂起的线 程。
  轻量级锁能提升程序同步性能的依据是“对于绝大部分的锁,在整个同步周期内都是不存在竞争的”,这是一个经验数据。如果没有竞争,轻量级锁使用CAS 操作避免了使用互斥量的开销,但如果存在锁竞争,除了互斥量的开销外,还额外发生了CAS操作,因此在有竞争的情况下,轻量级锁会比传统的重量级锁更慢。

13.3.5 偏向锁

  偏向锁也是JDK1.6中引入的一项锁优化,它的目的是消除数据在无竞争情况下的同步原语,进一步提高程序的运行性能。如果说轻量级锁是在无竞争的情况下使用CAS操作去消除同步使用的互斥量,那偏向锁就是在无竞争的情况下把整个同步都消除掉,连CAS操作都不做了。
  偏向锁的“偏”,就是偏心的“偏”、偏袒的“偏”。它的意思是这个锁会偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要再进行同步。
  如果读者读懂了前面轻量级锁中关于对象头MarkWord与线程之间的操作过程,那偏向锁的原理理解起来就会很简单。假设当前虚拟机启用了偏向锁(启 用参数-XX:+UseBiasedLocking,这是JDK1.6的默认值),那么,当锁对象第一次被线程获取的时候,虚拟机将会把对象头中的标志位 设为“01”,即偏向模式。同时使用CAS操作把获取到这个锁的线程的ID记录在对象的MarkWord之中,如果CAS操作成功,持有偏向锁的线程以后 每次进入这个锁相关的同步块时,虚拟机都可以不再进行任何同步操作(例如Locking、Unlocking及对Mark Word的Update等)。
  当有另外一个线程去尝试获取这个锁时,偏向模式就宣告结束。根据锁对象目前是否处于被锁定的状态,撤销偏向(RevokeBias)后恢复到未锁定 (标志位为“01”)或轻量级锁定(标志位为“00”)的状态,后续的同步操作就如上面介绍的轻量级锁那样执行。偏向锁、轻量级锁的状态转化及对象 Mark Word的关系如图13-5所示。


  图13-5 偏向锁、轻量级锁的状态转化及对象Mark Word的关系


  偏向锁可以提高带有同步但无竞争的程序性能。它同样是一个带有效益权衡(TradeOff)性质的优化,也就是说它并不一定总是对程序运行有 利,如果程序中大多数的锁都总是被多个不同的线程访问,那偏向模式就是多余的。在具体问题具体分析的前提下,有时候使用参数 -XX:-UseBiasedLocking来禁止偏向锁优化反而可以提升性能。


作者:icyfenix@gmail.com
来源:《深入理解Java虚拟机:JVM高级特性与最佳实践》

posted @ 2012-07-17 21:09 小胡子 阅读(1061) | 评论 (0)编辑 收藏

再谈javascript图片预加载技术

比onload更快获取图片尺寸

文章更新:2011-05-31
lightbox类效果为了让图片居中显示而使用预加载,需要等待完全加载完毕才能显示,体验不佳(如filick相册的全屏效果)。javascript无法获取img文件头数据,真的是这样吗?本文通过一个巧妙的方法让javascript获取它。

这是大部分人使用预加载获取图片大小的例子:

01var imgLoad = function (url, callback) {
02    var img = new Image();
03 
04    img.src = url;
05    if (img.complete) {
06        callback(img.width, img.height);
07    } else {
08        img.onload = function () {
09            callback(img.width, img.height);
10            img.onload = null;
11        };
12    };
13 
14};

可以看到上面必须等待图片加载完毕才能获取尺寸,其速度不敢恭维,我们需要改进。

web应用程序区别于桌面应用程序,响应速度才是最好的用户体验。如果想要速度与优雅兼得,那就必须提前获得图片尺寸,如何在图片没有加载完毕就能获取图片尺寸?

十多年的上网经验告诉我:浏览器在加载图片的时候你会看到图片会先占用一块地然后才慢慢加载完毕,并且不需要预设width与height属性,因 为浏览器能够获取图片的头部数据。基于此,只需要使用javascript定时侦测图片的尺寸状态便可得知图片尺寸就绪的状态。

当然实际中会有一些兼容陷阱,如width与height检测各个浏览器的不一致,还有webkit new Image()建立的图片会受以处在加载进程中同url图片影响,经过反复测试后的最佳处理方式:

01// 更新:
02// 05.27: 1、保证回调执行顺序:error > ready > load;2、回调函数this指向img本身
03// 04-02: 1、增加图片完全加载后的回调 2、提高性能
04 
05/**
06 * 图片头数据加载就绪事件 - 更快获取图片尺寸
07 * @version 2011.05.27
08 * @author  TangBin
10 * @param   {String}    图片路径
11 * @param   {Function}  尺寸就绪
12 * @param   {Function}  加载完毕 (可选)
13 * @param   {Function}  加载错误 (可选)
14 * @example imgReady('http://www.google.com.hk/intl/zh-CN/images/logo_cn.png', function () {
15        alert('size ready: width=' + this.width + '; height=' + this.height);
16    });
17 */
18var imgReady = (function () {
19    var list = [], intervalId = null,
20 
21    // 用来执行队列
22    tick = function () {
23        var i = 0;
24        for (; i < list.length; i++) {
25            list[i].end ? list.splice(i--, 1) : list[i]();
26        };
27        !list.length && stop();
28    },
29 
30    // 停止所有定时器队列
31    stop = function () {
32        clearInterval(intervalId);
33        intervalId = null;
34    };
35 
36    return function (url, ready, load, error) {
37        var onready, width, height, newWidth, newHeight,
38            img = new Image();
39         
40        img.src = url;
41 
42        // 如果图片被缓存,则直接返回缓存数据
43        if (img.complete) {
44            ready.call(img);
45            load && load.call(img);
46            return;
47        };
48         
49        width = img.width;
50        height = img.height;
51         
52        // 加载错误后的事件
53        img.onerror = function () {
54            error && error.call(img);
55            onready.end = true;
56            img = img.onload = img.onerror = null;
57        };
58         
59        // 图片尺寸就绪
60        onready = function () {
61            newWidth = img.width;
62            newHeight = img.height;
63            if (newWidth !== width || newHeight !== height ||
64                // 如果图片已经在其他地方加载可使用面积检测
65                newWidth * newHeight > 1024
66            ) {
67                ready.call(img);
68                onready.end = true;
69            };
70        };
71        onready();
72         
73        // 完全加载完毕的事件
74        img.onload = function () {
75            // onload在定时器时间差范围内可能比onready快
76            // 这里进行检查并保证onready优先执行
77            !onready.end && onready();
78         
79            load && load.call(img);
80             
81            // IE gif动画会循环执行onload,置空onload即可
82            img = img.onload = img.onerror = null;
83        };
84 
85        // 加入队列中定期执行
86        if (!onready.end) {
87            list.push(onready);
88            // 无论何时只允许出现一个定时器,减少浏览器性能损耗
89            if (intervalId === null) intervalId = setInterval(tick, 40);
90        };
91    };
92})();

调用例子:

2    alert('size ready: width=' + this.width + '; height=' + this.height);
3});

是不是很简单?这样的方式获取摄影级别照片尺寸的速度往往是onload方式的几十多倍,而对于web普通(800×600内)浏览级别的图片能达到秒杀效果。看了这个再回忆一下你见过的web相册,是否绝大部分都可以重构一下呢?好了,请观赏令人愉悦的 DEMO :

http://www.planeart.cn/demo/imgReady/

(通过测试的浏览器:Chrome、Firefox、Safari、Opera、IE6、IE7、IE8)

planeArt.cn原创文章,原文地址:http://www.planeart.cn/?p=1121

其他文章:

1、再谈IE6之Fixed定位
2、简易的全屏透明遮罩(lightBox)解决方案

这篇文章发表于10/03/2011 (星期四)在下午 2:33,所属分类为javascript。 您可以通过RSS 2.0跟踪这篇文章的评论。 您可以发表回复,或从您的网站发布引用通告

posted @ 2012-07-15 20:28 小胡子 阅读(135) | 评论 (0)编辑 收藏

http://www.cnblogs.com/lhb25/archive/2011/03/09/1964344.html

您可能还喜欢



  对于Web设计和开发人员来说,CSS是非常重要的一部分,随着越来越多的浏览器对CSS3的支持及不断完善,设计师和开发者们有了更多的选 择。如今,用纯CSS就可以实现各种各样很酷的效果,甚至是动画。今天这篇文章向大家推荐24款非常优秀的CSS3工具,为了获得更佳的效果,请在 Chrome 4+, Safari 4+, Firefox 3.6+, IE9+, Opera 10.5+版本浏览器中浏览如下在线工具。

1.CSS3 Pie

使用CSS3 Pie可以让IE6至IE8版本实现大多数的CSS3修饰特性,如圆角、阴影、渐变等等。

2. CSS3 Click Chart

非常好的CSS3效果演示,提供了示例代码。

3.CSS3 Please!

非常帅的一款CSS3工具,可修改代码,即时预览。

4.CSS3 Button Maker

一个非常不错的CSS3按钮制作工具。

5.CSS3 Generator

非常不错的CSS3代码生成器,带预览效果。

6.CSS3 Menu

非常不错的CSS3菜单制作工具。

7.CSS3 Gradients

一款非常棒的CSS3渐变效果演示工具。

8.CSS3 Cheat Sheet

一份不错的CSS3属性速查手册(PDF格式)。

9.CSS3 Selector Test

非常不错的CSS3选择器测试工具

10.CSS3 Transforms

一款强大的CSS3旋转动画效果演示工具,即时生成代码。

11.CSS3 Preview

CSS3特性介绍及效果预览。

12.CSS3 Generator

一款非常不错的CSS3代码生成工具。

13.CSS3 Color Names

CSS3颜色命名对照表。

14.Toggle CSS3 Bookmarklet

CSS3书签工具。

15.CSS3 Border Radius

一款在线CSS3圆角工具,四个角输入值就能生成对应的效果和代码。

16.CSS3 Desk

很炫的CSS3桌面。

17.Web Browser CSS Support

非常详尽的浏览器对CSS支持情况,包括CSS2.1和CSS3。

18.Key CSS

让元素以键盘风格显示的样式表。

19.CSS3 Playground

一款在线CSS3圆角和阴影效果演示及代码生成工具。

20.CSS3 Wrapping Drop Shadows

CSS3包装阴影效果。

21.CSS3 Carve Me

模仿内阴影效果,可输入内容查看效果,中文也可以噢。

22.Mother Effing Text Shadows

这工具名字太奇怪了,一款文本阴影效果工具,可即时生成代码。

23.CSS3 Learning Tool

在线CSS3学习工具,可即时预览效果。

24.CSS3 Maker

最后压轴的这款工具非常强大,可在线演示渐变、阴影、旋转、动画等非常多的效果,并生成对应效果的代码,赶紧体验一下吧!

  

您可能还喜欢



(编译来源:梦想天空  原文来自:Ultimate Collection of CSS3 Tools For Your Next Web Development

 

posted @ 2012-07-15 20:15 小胡子 阅读(170) | 评论 (0)编辑 收藏

这篇文章主要收录了十二月份发布在梦想天空的优秀文章,特别推荐给Web开发人员和设计师阅读。梦天空博客关注前端开发技术,展示最新 HTML5 和 CSS3 技术应用,分享实用的 jQuery 插件,推荐优秀的网页设计案例,共享精美的设计素材和优秀的 Web 开发工具。希望这些文章能帮助到您。

HTML5 & CSS3 应用

jQuery 插件和教程

网页设计素材

英文字体资源

摄影作品欣赏

其它推荐文章

同系列文章

 

本文链接:Web前端开发人员和设计师必读文章推荐【系列七】

文章来源:梦想天空 ◆ 关注前端开发技术 ◆ 分享网页设计资源

posted @ 2012-07-15 20:05 小胡子 阅读(136) | 评论 (0)编辑 收藏

仅列出标题
共15页: First 上一页 7 8 9 10 11 12 13 14 15 下一页