存档

2009年5月 的存档

深入研究ReentrantLock(重入锁)之引出话题篇

2009年5月30日

一直以来都想好好研究下ReentrantLock,她的独到魅力令我屡试不爽,无奈网上实在是没有太多的资料可以参考,于是自己开始深入研究它的内部实现机制,经过数天的研究,终于有点心得体会升华了,记录之……

synchronized原语和ReentrantLock在一般情况下没有什么区别,但是在非常复杂的同步应用中,请考虑使用ReentrantLock,特别是遇到下面2种需求的时候。
1.某个线程在等待一个锁的控制权的这段时间需要中断
2.需要分开处理一些wait-notify,ReentrantLock里面的Condition应用,能够控制notify哪个线程
3.具有公平锁功能,每个到来的线程都将排队等候
下面细细道来……

先说第一种情况,ReentrantLock的lock机制有2种,忽略中断锁和响应中断锁,这给我们带来了很大的灵活性。比如:如果A、B2个线程去竞争锁,A线程得到了锁,B线程等待,但是A线程这个时候实在有太多事情要处理,就是一直不返回,B线程可能就会等不及了,想中断自己,不再等待这个锁了,转而处理其他事情。这个时候ReentrantLock就提供了2种机制,第一,B线程中断自己(或者别的线程中断它),但是ReentrantLock不去响应,继续让B线程等待,你再怎么中断,我全当耳边风(synchronized原语就是如此);第二,B线程中断自己(或者别的线程中断它),ReentrantLock处理了这个中断,并且不再等待这个锁的到来,完全放弃。(如果你没有了解java的中断机制,请参考下相关资料,再回头看这篇文章,80%的人根本没有真正理解什么是java的中断,呵呵)

这里来做个试验,首先搞一个Buffer类,它有读操作和写操作,为了不读到脏数据,写和读都需要加锁,我们先用synchronized原语来加锁,如下:

  1. public class Buffer {
  2.  
  3.     private Object lock;
  4.  
  5.     public Buffer() {
  6.         lock = this;
  7.     }
  8.  
  9.     public void write() {
  10.         synchronized (lock) {
  11.             long startTime = System.currentTimeMillis();
  12.             System.out.println("开始往这个buff写入数据…");
  13.             for (;;)// 模拟要处理很长时间
  14.             {
  15.                 if (System.currentTimeMillis()
  16.                         – startTime > Integer.MAX_VALUE)
  17.                     break;
  18.             }
  19.             System.out.println("终于写完了");
  20.         }
  21.     }
  22.  
  23.     public void read() {
  24.         synchronized (lock) {
  25.             System.out.println("从这个buff读数据");
  26.         }
  27.     }
  28. }

接着,我们来定义2个线程,一个线程去写,一个线程去读。

  1. public class Writer extends Thread {
  2.  
  3.     private Buffer buff;
  4.  
  5.     public Writer(Buffer buff) {
  6.         this.buff = buff;
  7.     }
  8.  
  9.     @Override
  10.     public void run() {
  11.         buff.write();
  12.     }
  13.  
  14. }
  15.  
  16. public class Reader extends Thread {
  17.  
  18.     private Buffer buff;
  19.  
  20.     public Reader(Buffer buff) {
  21.         this.buff = buff;
  22.     }
  23.  
  24.     @Override
  25.     public void run() {
  26.  
  27.         buff.read();//这里估计会一直阻塞
  28.  
  29.         System.out.println("读结束");
  30.  
  31.     }
  32.  
  33. }

好了,写一个Main来试验下,我们有意先去“写”,然后让“读”等待,“写”的时间是无穷的,就看“读”能不能放弃了。

  1. public class Test {
  2.     public static void main(String[] args) {
  3.         Buffer buff = new Buffer();
  4.  
  5.         final Writer writer = new Writer(buff);
  6.         final Reader reader = new Reader(buff);
  7.  
  8.         writer.start();
  9.         reader.start();
  10.  
  11.         new Thread(new Runnable() {
  12.  
  13.             @Override
  14.             public void run() {
  15.                 long start = System.currentTimeMillis();
  16.                 for (;;) {
  17.                     //等5秒钟去中断读
  18.                     if (System.currentTimeMillis()
  19.                             – start > 5000) {
  20.                         System.out.println("不等了,尝试中断");
  21.                         reader.interrupt();
  22.                         break;
  23.                     }
  24.  
  25.                 }
  26.  
  27.             }
  28.         }).start();
  29.  
  30.     }
  31. }

我们期待“读”这个线程能退出等待锁,可是事与愿违,一旦读这个线程发现自己得不到锁,就一直开始等待了,就算它等死,也得不到锁,因为写线程要21亿秒才能完成 T_T ,即使我们中断它,它都不来响应下,看来真的要等死了。这个时候,ReentrantLock给了一种机制让我们来响应中断,让“读”能伸能屈,勇敢放弃对这个锁的等待。我们来改写Buffer这个类,就叫BufferInterruptibly吧,可中断缓存。

  1. import java.util.concurrent.locks.ReentrantLock;
  2.  
  3. public class BufferInterruptibly {
  4.  
  5.     private ReentrantLock lock = new ReentrantLock();
  6.  
  7.     public void write() {
  8.         lock.lock();
  9.         try {
  10.             long startTime = System.currentTimeMillis();
  11.             System.out.println("开始往这个buff写入数据…");
  12.             for (;;)// 模拟要处理很长时间
  13.             {
  14.                 if (System.currentTimeMillis()
  15.                         – startTime > Integer.MAX_VALUE)
  16.                     break;
  17.             }
  18.             System.out.println("终于写完了");
  19.         } finally {
  20.             lock.unlock();
  21.         }
  22.     }
  23.  
  24.     public void read() throws InterruptedException {
  25.         lock.lockInterruptibly();// 注意这里,可以响应中断
  26.         try {
  27.             System.out.println("从这个buff读数据");
  28.         } finally {
  29.             lock.unlock();
  30.         }
  31.     }
  32.  
  33. }

当然,要对reader和writer做响应的修改

  1. public class Reader extends Thread {
  2.  
  3.     private BufferInterruptibly buff;
  4.  
  5.     public Reader(BufferInterruptibly buff) {
  6.         this.buff = buff;
  7.     }
  8.  
  9.     @Override
  10.     public void run() {
  11.  
  12.         try {
  13.             buff.read();//可以收到中断的异常,从而有效退出
  14.         } catch (InterruptedException e) {
  15.             System.out.println("我不读了");
  16.         }
  17.        
  18.         System.out.println("读结束");
  19.  
  20.     }
  21.  
  22. }
  23.  
  24. /**
  25. * Writer倒不用怎么改动
  26. */
  27. public class Writer extends Thread {
  28.  
  29.     private BufferInterruptibly buff;
  30.  
  31.     public Writer(BufferInterruptibly buff) {
  32.         this.buff = buff;
  33.     }
  34.  
  35.     @Override
  36.     public void run() {
  37.         buff.write();
  38.     }
  39.  
  40. }
  41.  
  42. public class Test {
  43.     public static void main(String[] args) {
  44.         BufferInterruptibly buff = new BufferInterruptibly();
  45.  
  46.         final Writer writer = new Writer(buff);
  47.         final Reader reader = new Reader(buff);
  48.  
  49.         writer.start();
  50.         reader.start();
  51.  
  52.         new Thread(new Runnable() {
  53.  
  54.             @Override
  55.             public void run() {
  56.                 long start = System.currentTimeMillis();
  57.                 for (;;) {
  58.                     if (System.currentTimeMillis()
  59.                             – start > 5000) {
  60.                         System.out.println("不等了,尝试中断");
  61.                         reader.interrupt();
  62.                         break;
  63.                     }
  64.  
  65.                 }
  66.  
  67.             }
  68.         }).start();
  69.  
  70.     }
  71. }

这次“读”线程接收到了lock.lockInterruptibly()中断,并且有效处理了这个“异常”。好奇的读者,肯定要探个究竟,为什么ReentrantLock能做到这点,接下来,我们去迷宫探险吧……

java , , ,

对象序列化为何要定义serialVersionUID的来龙去脉

2009年5月5日

在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存。比如最常见的是Web服务器中的Session对象,当有10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些seesion先序列化到内存,等要用了,再还原到对象中,说白了,就是能将一个2进制文件变成内存中的对象。在JAVA中,要实现这种机制,只要实现Serializable接口就可以了,先看下面这个简单例子,serialVersionUID稍后引出。我们先定义一个简单的Person类,然后创建这个对象,最后序列化它到一个文件。

  1. import java.io.Serializable;
  2.  
  3. public class Person implements Serializable {
  4.    
  5.     private String name;
  6.    
  7.     public String getName() {
  8.         return name;
  9.     }
  10.     public void setName(String name) {
  11.         this.name = name;
  12.     }
  13. }
  1. import java.io.FileInputStream;
  2. import java.io.FileOutputStream;
  3. import java.io.ObjectInputStream;
  4. import java.io.ObjectOutputStream;
  5.  
  6. public class WhySerialversionUID {
  7.  
  8. public static void main(String[] args) throws Exception {
  9.  
  10. //这里是把对象序列化到文件       
  11. Person crab = new Person();
  12. crab.setName("Mr.Crab");
  13.  
  14. ObjectOutputStream oo = new ObjectOutputStream
  15.     (new FileOutputStream("crab_file"));
  16. oo.writeObject(crab);
  17. oo.close();
  18.  
  19. //这里是把对象序列化到文件,我们先注释掉,一会儿用
  20. //ObjectInputStream oi = new ObjectInputStream
  21. //    (new FileInputStream("crab_file"));
  22. //Person crab_back = (Person) oi.readObject();
  23. //System.out.println("Hi, My name is " + crab_back.getName());
  24. //oi.close();
  25.  
  26.     }
  27. }

运行完后,我们发现有了一个crab_file文件,这个文件就保存这crab对象在内存中的形态。同样,我们把这部分代码注释掉,运行下面那段还原代码,发现,crab_file文件可以被转化为一个对象。

一切都那么顺利,但是如果在序列化之后,Person这个类发生了改变呢?比如,多了一个成员变量。我们做如下试验,还是先将对象序列化到一个文件中,之后在Person这个类中添加一个成员变量,如下:

  1. import java.io.Serializable;
  2.  
  3. public class Person implements Serializable {
  4.    
  5.     private String name;
  6.     //添加这么一个成员变量
  7.     private String address;
  8.    
  9.     public String getName() {
  10.         return name;
  11.     }
  12.     public void setName(String name) {
  13.         this.name = name;
  14.     }
  15. }

之后,我们再去运行一下还原,就发现运行出错了,会报如下错误:
Exception in thread “main” java.io.InvalidClassException: Person; local class incompatible: stream classdesc serialVersionUID = 8383901821872620925, local class serialVersionUID = -763618247875550322
意思就是说,文件流中的class和classpath中的class,也就是修改过后的class,不兼容了,处于安全机制考虑,程序抛出了错误,并且拒绝载入。那么如果我们真的有需求要在序列化后添加一个字段或者方法呢?应该怎么办?那就是自己去指定serialVersionUID。之前,在我们的例子中,我们是没有指定serialVersionUID的,那么java编译器会自动给这个class进行一个摘要算法,类似于指纹算法,只要这个文件多一个空格,得到的UID就会截然不同的,可以保证在这么多类中,这个编号是唯一的。所以,我们添加了一个字段后,由于没有显指定serialVersionUID,编译器又为我们生成了一个UID,当然和前面保存在文件中的那个不会一样了,于是就出现了2个号码不一致的错误。因此,只要我们自己指定了serialVersionUID,就可以在序列化后,去添加一个字段,或者方法,而不会影响到后期的还原,还原后的对象照样可以使用,而且还多了方法可以用,呵呵。但是serialVersionUID我们怎么去生成呢?你可以写1,也可以写2,都无所谓,但是最好还是按照摘要算法,生成一个惟一的指纹数字,eclipse可以自动生成的,jdk也自带了这个工具。一般写法类似于
private static final long serialVersionUID = -763618247875550322L;

java

iphone 2.X固件令人惊叹

2009年5月1日

iphone已经用了一年半了,自从1.13版本以来就再也没有刷机过了,感觉硬件水平不变的情况下,软件平台本身是不会有质的飞跃的,但是前几天刷机到2.21之后,我发现自己真的落伍了,没想到这个一代的老iphone还是能发挥的如此出色,现在的3方软件也是五花八门,层出不穷,比起以前每用一个软件就失望一次比起来,这次真是给我带来太多的惊喜,下面是截图。

首先是主页面
img_0001img_0030

地图导航,能找到我在哪个位置,要去哪里,怎么走…居然把GPS模仿的这么好
img_0006img_0004

另外,UCWEB,飞信,QQ,同花顺,彩信等常用软件都用的非常爽
img_0027img_0008img_0010img_0012img_0023img_0034

最大的进步莫过于游戏了,想1年半之前,iphone刚推出那会儿,啥游戏都没有,装个GBA、PS2模拟器就乐呵乐呵了,看看现在这么多的游戏,真是再次令人惊叹。感谢网龙公司为大家贡献的这些,呵呵。

大杂烩

myfaces的初始化是如何进行的

2009年5月1日

在看myfaces源码的时候,一直不明白javax.faces.webapp.FacesServlet中有一段

  1. public void init(ServletConfig servletConfig)
  2.             throws ServletException
  3.     {
  4.         if(log.isTraceEnabled()) log.trace("init begin");
  5.         _servletConfig = servletConfig;
  6.         _facesContextFactory = (FacesContextFactory)FactoryFinder.getFactory(FactoryFinder.FACES_CONTEXT_FACTORY);
  7.         //TODO: null-check for Weblogic, that tries to initialize Servlet before ContextListener
  8.  
  9.         //Javadoc says: Lifecycle instance is shared across multiple simultaneous requests, it must be implemented in a thread-safe manner.
  10.         //So we can acquire it here once:
  11.         LifecycleFactory lifecycleFactory = (LifecycleFactory)FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY);
  12.         _lifecycle = lifecycleFactory.getLifecycle(getLifecycleId());
  13.         if(log.isTraceEnabled()) log.trace("init end");
  14.     }

其中的FactoryFinder.getFactory是这样一段代码

  1. public static Object getFactory(String factoryName)
  2.             throws FacesException
  3.     {
  4.         if(factoryName == null)
  5.             throw new NullPointerException("factoryName may not be null");
  6.  
  7.         ClassLoader classLoader = getClassLoader();
  8.  
  9.         //This code must be synchronized because this could cause a problem when
  10.         //using update feature each time of myfaces (org.apache.myfaces.CONFIG_REFRESH_PERIOD)
  11.         //In this moment, a concurrency problem could happen
  12.         Map factoryClassNames = null;
  13.         Map<String, Object> factoryMap = null;
  14.        
  15.         synchronized(_registeredFactoryNames)
  16.         {
  17.             factoryClassNames = _registeredFactoryNames.get(classLoader);
  18.  
  19.             if (factoryClassNames == null)
  20.             {
  21.                 throw new IllegalStateException(message);
  22.             }

注意其中的 _registeredFactoryNames.get(classLoader),说明在javax.faces.webapp.FacesServlet之前_registeredFactoryNames已经构造好了,可是我查web.xml的时候,根本找不到其它类了,到底谁去初始化了_registeredFactoryNames呢?

其实是 WEB-INF/lib/myfaces-impl-1.2.6.jar 这个引入的包起了作用,这里面有2个.tld文件,tomcat等j2ee容器会扫描所有的classpath,遇到.tld文件,也会去里面执行的。在myfaces_core.tld中,除了定义了一些f:开头标签以外,就有一段监听器代码

  1. <listener>
  2.   <listener-class>org.apache.myfaces.webapp.StartupServletContextListener</listener-class>
  3. </listener>

这才是整个myfaces加载的爆炸点。以前struts的爆炸点都是在web.xml定义的servlet中的,这次放在了jar包里面,正是有点不习惯,要是仅仅想用里面的一些功能,还非得加载这个玩意了,呵呵。

java