posts - 12, comments - 8, trackbacks - 0, articles - 5
  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

2020年4月18日

博客做了迁移,新的地址:观云的BLOG https://yeas.fun/
新博课主要用于记录一些系列的技术文章,今年的一个目标就是深入研究JVM底层,我会把一些学习心得记录下来,也欢迎大家一起讨论~

posted @ 2020-04-18 10:12 杨罗罗 阅读(181) | 评论 (0)编辑 收藏

2016年1月13日

现在网络那么发达,我们上网的每个人势必会在各个网站上登陆,那势必会有一堆密码需要管理,那怎么能记住那么多网站的密码呢?我之前的做法是设置几个常用的密码,好多不重要的网站用一个,重要的网站用一个,然后...CSDN密码泄露之后,只得吭哧吭哧的改一堆密码。那种痛苦真的是呵呵呵

那有没有什么方式可以方便的管理密码呢?

那就是LastPass的工作,它是一款跨平台密码管理软件。你在每个网站上都可以随机生成一个密码,然后软件会自动记住,你只需要记住这个软件的主密码就可以了。超方便!

为什么要用LastPass?

我用LastPass,是因为它可以安装浏览器插件,之后你在网站的注册,它会自动提醒你要不要加入它的密码库,你在网站的登陆它会自动帮你填写账号密码,甚至于一些常用的表单(比如说身份证、公司地址、银行卡等)你可以提前设置好,它也会自动帮你填写,我再也不用记那么多密码了,一切都是自动化。
如果你经常有国外的帐号,比如说google等大公司,LastPass甚至提供了一键改密码的功能,好方便!

支持智能手机么?

当然!现在智能机那么流行,不能在智能机上用,简直不能忍。当你安装了IOS软件之后,在打开网站登录界面,点击下方的向上小箭头,选择LassPass软件,点击Touch ID,好了,密码自动完成,世界顿时清净了,想想以前在手机上输入超长的密码,跪了!

真的安全吗?

有的人担心密码泄露问题,其实对于LastPass没啥必要,因为LastPass存储的都是加密文件,只要你的主密码不泄露,别人即使拿到你的网上的密码,也是加密的,没法用。

它收费吗?

好东西都要收费,价格也还可以,几十块一年,其实收费版和免费版对于普通用户,最重要的区别就是:免费版帐号密码不能云同步

免费获得一个月的高级账户权限

https://lastpass.com/f?18430702  通过这个地址注册,则会免费获得一个月高级账户权限

最后也是最重要的:

点击这个链接,输入刚刚你注册的邮箱,则会送半年的高级账户,记住一个密码,就记住了所有密码,就是那么简单!

posted @ 2016-01-13 14:19 杨罗罗 阅读(325) | 评论 (0)编辑 收藏

2011年4月6日

一. 应用场景

在大型分布式应用中,我们经常碰到在多数据库之间的数据同步问题,比如说一款游戏,在玩家注册后,可以马上登陆进入服务器,数据在一个IDC更新,其它IDC立即可见。为了简化思路,我们这里称玩家注册的数据库(数据来源库)为中心库,同步目的地的数据库为分站库。

在分布式领域有个CAP理论,是说Consistency(一致性), Availability(可用性), Partition tolerance(分区和容错) 三部分在系统实现只可同时满足二点,法三者兼顾。

能做的

· 数据快速搬运到指定的IDC节点

· 数据传递过程中失败时,重新传递

· 监控数据传递流程

· 故障转移

· 数据版本控制

· 分配全局唯一的ID

不能做的

· 不参与业务行为,业务操作只能通过注册的方式集成

· 不保存业务数据,不提供传递的业务的查询

二.系统要求

1.数据快速同步:除去网络原因,正常情况下从来源库同步到接收库的时间不超过300m2.高并发:单个应用每秒同步2000条记录
3.可伸缩性,在资源达到上限时能通过增加应用分散处理后期增长的压力
4.数据完整性要求,在数据同步过程中保证数据不丢失和数据安全
5.故障转移和数据恢复功能

三.设计思路

系统优化,最常用的就是进行业务切割,将总任务切割为许多子任务,分区块分析系统中可能存在的性能瓶颈并有针对性地进行优化,在本系统中,主要业务包含以下内容:
1.Syncer:外部接口,接收同步数据请求,初始化同步系统的一些必要数据
2.Delivery:将同步数据按照业务或优先级进行分发,并记录分发结果
3.Batch:分站库收到同步数据后,根据不同的业务类型调用相应的业务逻辑处理数据
基于以上三块业务功能,我们可以把整个数据同步流程切割为3个应用,具体如下图显示。在Syncer端应用中,我们需要将原始同步数据和分发的分站进行存储,以备失败恢复,此时如果采用数据库进行存储,势必会受限于数据库性能影响,因此我们采用了高效的key-value风格存储的redis服务来记录数据,同时在不同应用之间采用队列(Httpsqs服务)的方式来进行通讯,同时也保证的数据通讯的顺序性,为之后的顺序同步做好基础。
Httpsqs提供了http风格的数据操作模式,业务编码非常简单,同时也提供了web形式的队列处理情况查询,这是选择它做队列服务很大一部分原因:方便业务操作和性能监控。

四.数据流转 

绿色-正常流程、红色-异常流程

队列处理

根据业务划分队列名称,每个队列再划分为三个关联队列:正常队列(Normal)、重试队列(Retry)、死亡队列(Death),处理流程为:

【进程A】把数据先放入正常队列,如果放置失败写恢复日志

【进程B】监听正常队列,获取队列数据并进行业务处理,处理失败放入重试队列

【进程C】监听重试队列,过几秒获取队列数据并重新进行业务处理,处理失败放入死亡队列

【进程D】监听死亡队列,获取队列数据并重新进行业务处理,处理失败重新放入死亡队列尾部,等待下一次轮回

业务处理失败如果无法再次放入队列,记录恢复日志

数据同步流程

1发送数据,支持Http POST:curl -d "经过URL编码的文本消息",如"http://host:port/sync_all/register"
或者Http GET:curl "http://host:port/sync_all/register?data=经过URL编码的文本消息"

sync-syncer接收到同步数据请求,创建sid并分解出需要同步的节点个数,把原始数据和子任务写入redis中,sid写入httpsqs中

sync-delivery监听中心httpsqs队列,根据sid从redis获取到原始数据和需要同步的节点地址,往其他节点发送数据,流程如按"队列处理流程"进行

sync-batch监听分节点的httpsqs队列,调用已经注册的处理器处理队列数据,流程如按"队列处理流程"进行

三. 恢复和监控

恢复数据源

· httpsqs中的死亡队列 - 业务处理暂时处理不了的数据

· recovery日志文件 - 其它异常情况下的数据,例如网络无法连接、内部服务不可用

数据恢复

独立的应用来处理正常流程中没有完成的任务,主要功能有:

· 监听死亡队列,进行业务重做,再次执行失败时将执行次数+1,最大执行次数为5(默认),超出上限则记录到恢复日志中

· 读取恢复日志,重新放入死亡队列

应用监控

· 使用scribe日志框架服务业务日志的采集和监控

· 收集重要的业务操作日志

· 动态的开启/关闭某类业务日志

· 对redis进行监控

· 对httpsps,监控队列个数,每个队列的状态

四. 数据结构

{"sid":111,"type":"reg","v":1,"data":"hello world","ctime":65711321800,"exec":1}

· sid(sync id) - 全局唯一id

· v(version) - 版本号

· data - 业务数据

· ctime(create time) - 创建时间(毫秒)

· exec - 可选,执行次数

类别

key格式

value格式

备注

redis原始数据

sync:<业务类型>:<sid>

{"ctime":65711321800,"v":1,"data":"hello world"}

分站没有此项

redis业务附加任务

sync:<业务类型>:<sid>:sub

set类型,保存需要同步的节点id,例如[1,3,5]

分发确认Set数据结构 

httpsqs队列

sync:<业务类型> 
sync:<业务类型>:retry 
sync:<业务类型>:death

{"sid":111,"type":"pp_register","exec":1} 

中心队列内容,key中<业务类型>是可选项 

httpsqs队列

sync:<业务类型> 
sync:<业务类型>:retry 
sync:<业务类型>:death

{"sid":111,"v":1,"data":"hello world","ctime":65711321800,"exec":1} 

分站队列内容,包含业务数据 

所有的key都小写,以 ':' 作为分隔符

五.编码及测试结果

经过编码和测试,在内网环境下,在无数据库限制的情况下,单应用可以传递1500条/秒,基本满足业务需求。如果需进一步扩展,采用集群式布署可使得吞吐量成倍的增长。

posted @ 2011-04-06 15:50 杨罗罗 阅读(3582) | 评论 (3)编辑 收藏

2010年12月15日

下面这篇文章写的非常好,结合memcached的 特点利用Consistent hasning 算法,可以打造一个非常完备的分布式缓存服务器。

memcached的分布式

正如第1次中介绍的那样, memcached虽然称为“分布式”缓存服务器,但服务器端并没有“分布式”功能。 服务器端仅包括 第2次、 第3次 前坂介绍的内存存储功能,其实现非常简单。 至于memcached的分布式,则是完全由客户端程序库实现的。 这种分布式是memcached的最大特点。

memcached的分布式是什么意思?

这里多次使用了“分布式”这个词,但并未做详细解释。 现在开始简单地介绍一下其原理,各个客户端的实现基本相同。

下面假设memcached服务器有node1~node3三台, 应用程序要保存键名为“tokyo”“kanagawa”“chiba”“saitama”“gunma” 的数据。

memcached-0004-01.png

图1 分布式简介:准备

首先向memcached中添加“tokyo”。将“tokyo”传给客户端程序库后, 客户端实现的算法就会根据“键”来决定保存数据的memcached服务器。 服务器选定后,即命令它保存“tokyo”及其值。

memcached-0004-02.png

图2 分布式简介:添加时

同样,“kanagawa”“chiba”“saitama”“gunma”都是先选择服务器再保存。

接下来获取保存的数据。获取时也要将要获取的键“tokyo”传递给函数库。 函数库通过与数据保存时相同的算法,根据“键”选择服务器。 使用的算法相同,就能选中与保存时相同的服务器,然后发送get命令。 只要数据没有因为某些原因被删除,就能获得保存的值。

memcached-0004-03.png

图3 分布式简介:获取时

这样,将不同的键保存到不同的服务器上,就实现了memcached的分布式。 memcached服务器增多后,键就会分散,即使一台memcached服务器发生故障 无法连接,也不会影响其他的缓存,系统依然能继续运行。

接下来介绍第1次 中提到的Perl客户端函数库Cache::Memcached实现的分布式方法。

Cache::Memcached的分布式方法

Perl的memcached客户端函数库Cache::Memcached是 memcached的作者Brad Fitzpatrick的作品,可以说是原装的函数库了。

该函数库实现了分布式功能,是memcached标准的分布式方法。

根据余数计算分散

Cache::Memcached的分布式方法简单来说,就是“根据服务器台数的余数进行分散”。 求得键的整数哈希值,再除以服务器台数,根据其余数来选择服务器。

下面将Cache::Memcached简化成以下的Perl脚本来进行说明。

use strict;
use warnings;
use String::CRC32;

my @nodes = ('node1','node2','node3');
my @keys = ('tokyo', 'kanagawa', 'chiba', 'saitama', 'gunma');

foreach my $key (@keys) {
   
my $crc = crc32($key); # CRC値
   
my $mod = $crc % ( $#nodes + 1 );
   
my $server = $nodes[ $mod ]; # 根据余数选择服务器
   
printf "%s =&gt; %s\n", $key, $server;
}

Cache::Memcached在求哈希值时使用了CRC。

首先求得字符串的CRC值,根据该值除以服务器节点数目得到的余数决定服务器。 上面的代码执行后输入以下结果:

tokyo       => node2
kanagawa => node3
chiba => node2
saitama => node1
gunma => node1

根据该结果,“tokyo”分散到node2,“kanagawa”分散到node3等。 多说一句,当选择的服务器无法连接时,Cache::Memcached会将连接次数 添加到键之后,再次计算哈希值并尝试连接。这个动作称为rehash。 不希望rehash时可以在生成Cache::Memcached对象时指定“rehash => 0”选项。

根据余数计算分散的缺点

余数计算的方法简单,数据的分散性也相当优秀,但也有其缺点。 那就是当添加或移除服务器时,缓存重组的代价相当巨大。 添加服务器后,余数就会产生巨变,这样就无法获取与保存时相同的服务器, 从而影响缓存的命中率。用Perl写段代码来验证其代价。

use strict;
use warnings;
use String::CRC32;

my @nodes = @ARGV;
my @keys = ('a'..'z');
my %nodes;

foreach my $key ( @keys ) {
   
my $hash = crc32($key);
   
my $mod = $hash % ( $#nodes + 1 );
   
my $server = $nodes[ $mod ];
   
push @{ $nodes{ $server } }, $key;
}

foreach my $node ( sort keys %nodes ) {
   
printf "%s: %s\n", $node, join ",", @{ $nodes{$node} };
}

这段Perl脚本演示了将“a”到“z”的键保存到memcached并访问的情况。 将其保存为mod.pl并执行。

首先,当服务器只有三台时:

$ mod.pl node1 node2 nod3
node1: a,c,d,e,h,j,n,u,w,x
node2: g,i,k,l,p,r,s,y
node3: b,f,m,o,q,t,v,z

结果如上,node1保存a、c、d、e……,node2保存g、i、k……, 每台服务器都保存了8个到10个数据。

接下来增加一台memcached服务器。

$ mod.pl node1 node2 node3 node4
node1: d,f,m,o,t,v
node2: b,i,k,p,r,y
node3: e,g,l,n,u,w
node4: a,c,h,j,q,s,x,z

添加了node4。可见,只有d、i、k、p、r、y命中了。像这样,添加节点后 键分散到的服务器会发生巨大变化。26个键中只有六个在访问原来的服务器, 其他的全都移到了其他服务器。命中率降低到23%。在Web应用程序中使用memcached时, 在添加memcached服务器的瞬间缓存效率会大幅度下降,负载会集中到数据库服务器上, 有可能会发生无法提供正常服务的情况。

mixi的Web应用程序运用中也有这个问题,导致无法添加memcached服务器。 但由于使用了新的分布式方法,现在可以轻而易举地添加memcached服务器了。 这种分布式方法称为 Consistent Hashing。

Consistent Hashing

关于Consistent Hashing的思想,mixi株式会社的开发blog等许多地方都介绍过, 这里只简单地说明一下。

Consistent Hashing的简单说明

Consistent Hashing如下所示:首先求出memcached服务器(节点)的哈希值, 并将其配置到0~232的圆(continuum)上。 然后用同样的方法求出存储数据的键的哈希值,并映射到圆上。 然后从数据映射到的位置开始顺时针查找,将数据保存到找到的第一个服务器上。 如果超过232仍然找不到服务器,就会保存到第一台memcached服务器上。

memcached-0004-04.png

图4 Consistent Hashing:基本原理

从上图的状态中添加一台memcached服务器。余数分布式算法由于保存键的服务器会发生巨大变化 而影响缓存的命中率,但Consistent Hashing中,只有在continuum上增加服务器的地点逆时针方向的 第一台服务器上的键会受到影响。

memcached-0004-05.png

图5 Consistent Hashing:添加服务器

因此,Consistent Hashing最大限度地抑制了键的重新分布。 而且,有的Consistent Hashing的实现方法还采用了虚拟节点的思想。 使用一般的hash函数的话,服务器的映射地点的分布非常不均匀。 因此,使用虚拟节点的思想,为每个物理节点(服务器) 在continuum上分配100~200个点。这样就能抑制分布不均匀, 最大限度地减小服务器增减时的缓存重新分布。

通过下文中介绍的使用Consistent Hashing算法的memcached客户端函数库进行测试的结果是, 由服务器台数(n)和增加的服务器台数(m)计算增加服务器后的命中率计算公式如下:

(1 - n/(n+m)) * 100

支持Consistent Hashing的函数库

本连载中多次介绍的Cache::Memcached虽然不支持Consistent Hashing, 但已有几个客户端函数库支持了这种新的分布式算法。 第一个支持Consistent Hashing和虚拟节点的memcached客户端函数库是 名为libketama的PHP库,由last.fm开发。

至于Perl客户端,连载的第1次 中介绍过的Cache::Memcached::Fast和Cache::Memcached::libmemcached支持 Consistent Hashing。

两者的接口都与Cache::Memcached几乎相同,如果正在使用Cache::Memcached, 那么就可以方便地替换过来。Cache::Memcached::Fast重新实现了libketama, 使用Consistent Hashing创建对象时可以指定ketama_points选项。

my $memcached = Cache::Memcached::Fast->new({
servers => ["192.168.0.1:11211","192.168.0.2:11211"],
ketama_points => 150
});

另外,Cache::Memcached::libmemcached 是一个使用了Brain Aker开发的C函数库libmemcached的Perl模块。 libmemcached本身支持几种分布式算法,也支持Consistent Hashing, 其Perl绑定也支持Consistent Hashing。

总结

本次介绍了memcached的分布式算法,主要有memcached的分布式是由客户端函数库实现, 以及高效率地分散数据的Consistent Hashing算法。下次将介绍mixi在memcached应用方面的一些经验, 和相关的兼容应用程序。

posted @ 2010-12-15 13:35 杨罗罗 阅读(1743) | 评论 (1)编辑 收藏

2010年12月8日

     摘要: java.util.concurrent 包含许多线程安全、测试良好、高性能的并发构建块。不客气地说,创建 java.util.concurrent 的目的就是要实现 Collection 框架对数据结构所执行的并发操作。通过提供一组可靠的、高性能并发构建块,开发人员可以提高并发类的线程安全、可伸缩性、性能、可读性和可靠性。 如果一些类名看起来相似,可能是因为 java.util.concurr...  阅读全文

posted @ 2010-12-08 17:40 杨罗罗 阅读(795) | 评论 (0)编辑 收藏

2010年12月3日

AQS中有一个state字段(int类型,32位)用来描述有多少线程获持有锁。在独占锁的时代这个值通常是0或者1(如果是重入的就是重入的次数),在共享锁的时代就是持有锁的数量。
自旋等待适合于比较短的等待,而挂起线程比较适合那些比较耗时的等待。

锁竞争

影响锁竞争性的条件有两个:锁被请求的频率和每次持有锁的时间。显然当而这二者都很小的时候,锁竞争不会成为主要的瓶颈。但是如果锁使用不当,导致二者都比较大,那么很有可能CPU不能有效的处理任务,任务被大量堆积。

所以减少锁竞争的方式有下面三种:

  1. 减少锁持有的时间
  2. 减少锁请求的频率
  3. 采用共享锁取代独占锁

死锁

1.一种情况是线程A永远不释放锁,结果B一直拿不到锁,所以线程B就“死掉”了
2.第二种情况下,线程A拥有线程B需要的锁Y,同时线程B拥有线程A需要的锁X,那么这时候线程A/B互相依赖对方释放锁,于是二者都“死掉”了。
3.如果一个线程总是不能被调度,那么等待此线程结果的线程可能就死锁了。这种情况叫做线程饥饿死锁。比如说非公平锁中,如果某些线程非常活跃,在高并发情况下这类线程可能总是拿到锁,那么那些活跃度低的线程可能就一直拿不到锁,这样就发生了“饥饿死”。

避免死锁的解决方案是:
1.尽可能的按照锁的使用规范请求锁,另外锁的请求粒度要小(不要在不需要锁的地方占用锁,锁不用了尽快释放);
2.在高级锁里面总是使用tryLock或者定时机制(就是指定获取锁超时的时间,如果时间到了还没有获取到锁那么就放弃)。高级锁(Lock)里面的这两种方式可以有效的避免死锁。

posted @ 2010-12-03 10:11 杨罗罗 阅读(1807) | 评论 (0)编辑 收藏

2010年11月25日

     摘要: 内部类详解  1、定义    一个类的定义放在另一个类的内部,这个类就叫做内部类。  Java代码  public class First {   public class Contents{   &nb...  阅读全文

posted @ 2010-11-25 16:27 杨罗罗 阅读(5009) | 评论 (1)编辑 收藏

2010年11月24日

Spring中提供一些Aware相关接口,像是BeanFactoryAware、 ApplicationContextAware、ResourceLoaderAware、ServletContextAware等等,实现这些 Aware接口的Bean在被初始之后,可以取得一些相对应的资源,例如实现BeanFactoryAware的Bean在初始后,Spring容器将会注入BeanFactory的实例,而实现ApplicationContextAware的Bean,在Bean被初始后,将会被注入 ApplicationContext的实例等等。

 Bean取得BeanFactory、ApplicationContextAware的实例目的是什么,一般的目的就是要取得一些档案资源的存取、相 关讯息资源或是那些被注入的实例所提供的机制,例如ApplicationContextAware提供了publishEvent()方法,可以支持基于Observer模式的事件传播机制。

 ApplicationContextAware接口的定义如下:

ApplicationContextAware.java

public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext context);

}


 我们这边示范如何透过实现ApplicationContextAware注入ApplicationContext来实现事件传播,首先我们的HelloBean如下:

HelloBean.java

package onlyfun.caterpillar;

 

import org.springframework.context.*;

 

public class HelloBean implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    private String helloWord = "Hello!World!";

  

    public void setApplicationContext(ApplicationContext context) {

        this.applicationContext = context;

    }

  

    public void setHelloWord(String helloWord) {

        this.helloWord = helloWord;

    }

  

    public String getHelloWord() {

        applicationContext.publishEvent(

               new PropertyGettedEvent("[" + helloWord + "] is getted"));

        return helloWord;

    }

}


 ApplicationContext会由Spring容器注入,publishEvent()方法需要一个继承ApplicationEvent的对象,我们的PropertyGettedEvent继承了ApplicationEvent,如下:

PropertyGettedEvent.java

package onlyfun.caterpillar;

 

import org.springframework.context.*;

 

public class PropertyGettedEvent extends ApplicationEvent {

    public PropertyGettedEvent(Object source) {

        super(source);

    }

}


 当ApplicationContext执行publishEvent()后,会自动寻找实现ApplicationListener接口的对象并通知其发生对应事件,我们实现了PropertyGettedListener如下:

PrppertyGettedListener.java

package onlyfun.caterpillar;

 

import org.springframework.context.*;

 

public class PropertyGettedListener implements ApplicationListener {

    public void onApplicationEvent(ApplicationEvent event) {

        System.out.println(event.getSource().toString());  

    }

}


 Listener必须被实例化,这我们可以在Bean定义档中加以定义:

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN/EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>

    <bean id="propertyGetterListener" class="onlyfun.caterpillar.PropertyGettedListener"/>

 

    <bean id="helloBean" class="onlyfun.caterpillar.HelloBean">

        <property name="helloWord"><value>Hello!Justin!</value></property>

    </bean>

</beans>


 我们写一个测试程序来测测事件传播的运行:

Test.java

package onlyfun.caterpillar;

 

import org.springframework.context.*;

import org.springframework.context.support.*;

 

public class Test {

    public static void main(String[] args) {

        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");

      

        HelloBean hello = (HelloBean) context.getBean("helloBean");

        System.out.println(hello.getHelloWord());

    }

}


 执行结果会如下所示:

log4j:WARN No appenders could be found for logger

(org.springframework.beans.factory.xml.XmlBeanDefinitionReader).

log4j:WARN Please initialize the log4j system properly.

org.springframework.context.support.ClassPathXmlApplicationContext:

displayName=[org.springframework.context.support.ClassPathXmlApplicationContext;

hashCode=33219526]; startup date=[Fri Oct 29 10:56:35 CST 2004];

root of ApplicationContext hierarchy

[Hello!Justin!] is getted

Hello!Justin!


 以上是以实现事件传播来看看实现Aware接口取得对应对象后,可以进行的动作,同样的,您也可以实现ResourceLoaderAware接口:

ResourceLoaderAware.java

public interface ResourceLoaderAware {

    void setResourceLoader(ResourceLoader loader);

}


 实现ResourceLoader的Bean就可以取得ResourceLoader的实例,如此就可以使用它的getResource()方法,这对于必须存取档案资源的Bean相当有用。

 基本上,Spring虽然提供了这些Aware相关接口,然而Bean上若实现了这些界面,就算是与Spring发生了依赖,从另一个角度来看,虽然您可以直接在Bean上实现这些接口,但您也可以透过setter来完成依赖注入,例如:

HelloBean.java

package onlyfun.caterpillar;

 

import org.springframework.context.*;

 

public class HelloBean {

    private ApplicationContext applicationContext;

    private String helloWord = "Hello!World!";

  

    public void setApplicationContext(ApplicationContext context) {

        this.applicationContext = context;

    }

  

    public void setHelloWord(String helloWord) {

        this.helloWord = helloWord;

    }

  

    public String getHelloWord() {

        applicationContext.publishEvent(new PropertyGettedEvent("[" + helloWord + "] is getted"));

        return helloWord;

    }

}


 注意这次我们并没有实现ApplicationContextAware,我们在程序中可以自行注入ApplicationContext实例:

ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");

      

HelloBean hello = (HelloBean) context.getBean("helloBean");

hello.setApplicationContext(context);

System.out.println(hello.getHelloWord());


 就Bean而言,降低了对Spring的依赖,可以比较容易从现有的框架中脱离。

 

posted @ 2010-11-24 11:14 杨罗罗 阅读(7636) | 评论 (1)编辑 收藏

2010年11月19日

Hibernate的二级缓存策略的一般过程如下:

  1) 条件查询的时候,总是发出一条select * from table_name where …. (选择所有字段)这样的SQL语句查询数据库,一次获得所有的数据对象。

  2) 把获得的所有数据对象根据ID放入到第二级缓存中。

  3) 当Hibernate根据ID访问数据对象的时候,首先从Session一级缓存中查;查不到,如果配置了二级缓存,那么从二级缓存中查;查不到,再查询数据库,把结果按照ID放入到缓存。

  4) 删除、更新、增加数据的时候,同时更新缓存。

  Hibernate的二级缓存策略,是针对于ID查询的缓存策略,对于条件查询则毫无作用。为此,Hibernate提供了针对条件查询的Query缓存。

  Hibernate的Query缓存策略的过程如下:

  1) Hibernate首先根据这些信息组成一个Query Key,Query Key包括条件查询的请求一般信息:SQL, SQL需要的参数,记录范围(起始位置rowStart,最大记录个数maxRows),等。

  2) Hibernate根据这个Query Key到Query缓存中查找对应的结果列表。如果存在,那么返回这个结果列表;如果不存在,查询数据库,获取结果列表,把整个结果列表根据Query Key放入到Query缓存中。

  3) Query Key中的SQL涉及到一些表名,如果这些表的任何数据发生修改、删除、增加等操作,这些相关的Query Key都要从缓存中清空。

posted @ 2010-11-19 11:33 杨罗罗 阅读(752) | 评论 (0)编辑 收藏

2010年11月18日

在JDK 5之前Java语言是靠synchronized关键字保证同步的,这会导致有锁(后面的章节还会谈到锁)。

锁机制存在以下问题:

(1)在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题。

(2)一个线程持有锁会导致其它所有需要此锁的线程挂起。

(3)如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能风险。

volatile是不错的机制,但是volatile不能保证原子性。因此对于同步最终还是要回到锁机制上来。

独占锁是一种悲观锁,synchronized就是一种独占锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。而另一个更加有效的锁就是乐观锁。所谓观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。

CAS 操作

上面的乐观锁用到的机制就是CAS,Compare and Swap。

CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

非阻塞算法 (nonblocking algorithms)

一个线程的失败或者挂起不应该影响其他线程的失败或挂起的算法。

现代的CPU提供了特殊的指令,可以自动更新共享数据,而且能够检测到其他线程的干扰,而 compareAndSet() 就用这些代替了锁定。

拿出AtomicInteger来研究在没有锁的情况下是如何做到数据正确性的。

private volatile int value;

首先毫无以为,在没有锁的机制下可能需要借助volatile原语,保证线程间的数据是可见的(共享的)。这样才获取变量的值的时候才能直接读取。

public final int get() {
        return value;
    }

然后来看看++i是怎么做到的。

public final int incrementAndGet() {
    for (;;) {
        int current = get();
        int next = current + 1;
        if (compareAndSet(current, next))
            return next;
    }
}

在这里采用了CAS操作,每次从内存中读取数据然后将此数据和+1后的结果进行CAS操作,如果成功就返回结果,否则重试直到成功为止。

而compareAndSet利用JNI来完成CPU指令的操作。

public final boolean compareAndSet(int expect, int update) {   
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

整体的过程就是这样子的,利用CPU的CAS指令,同时借助JNI来完成Java的非阻塞算法。其它原子操作都是利用类似的特性完成的。

而整个J.U.C都是建立在CAS之上的,因此对于synchronized阻塞算法,J.U.C在性能上有了很大的提升。

CAS看起来很爽,但是会导致“ABA问题”。

CAS算法实现一个重要前提需要取出内存中某时刻的数据,而在下时刻比较并替换,那么在这个时间差类会导致数据的变化

比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。如果链表的头在变化了两次后恢复了原值,但是不代表链表就没有变化。因此前面提到的原子操作AtomicStampedReference/AtomicMarkableReference就很有用了。这允许一对变化的元素进行原子操作。

posted @ 2010-11-18 15:16 杨罗罗 阅读(3102) | 评论 (1)编辑 收藏