`
wtyling
  • 浏览: 9638 次
  • 性别: Icon_minigender_2
  • 来自: 湖南永州
文章分类
社区版块
存档分类
最新评论
阅读更多
  mock有两种,一种是静态的,一种是动态的。静态的就是在写测试代码之前根据需要打桩的类生成另外一个类,这个类就是mock object。动态的就是mock object是在测试代码运行的时候才生成的。所以很明显,动态打桩比静态打桩要方便地多。本章就是介绍动态打桩的工具。

        早期的动态mock工具只能够mock接口,而不能够mock类;现在的mock工具无论是mock接口还是类都能够轻松完成了。

        easymock就是其中的佼佼者,easymock现在已经是2.2版本了,本文使用的是easymock1.2_Java1.5版本。使用 easymock能够轻松的mock任何接口,但如果想mock类,那还需要另外一个lib,就是easymockclassextension,使用了这个工具,你就能轻松地mock你要的任何类了。本文使用的是easymockclassextension1.2,使用这个库必须要cglib这个 jar包,而cglib又需要asm的jar包,所以要搭建好环境还得花些时间,不过当你把环境搞好之后,你便会发现物超所值,从此以后打桩无难事了。 cglib和asm的版本一定要适配,要不然不能正常工作,幸好cglib的网站已经提供了cglib和asm的绑定包,本文所用的是cglib- nodep-2.2_beta1版本。

        本章先对如何对接口进行打桩示例,下一章再对如何对接口进行打桩示例。

先看欲打桩接口代码:
/** *//************************************************************
* Project Name: lhjTest
* File Name   : CoolInterface.java
* File Desc   : CoolInterface.java
* Author      : Administrator
* Create      : 2007-3-25
* Modify:
***********************************************************/
package org.lhj.cool.junit;

/** *//**
* @author Administrator
*/
public interface CoolInterface
...{
    String reverseString(String inputStr) throws NullPointerException;
}

该接口很简单,只要一个reverString的方法。

下面看看欲测试类(使用了CoolInterface)的代码:
/** *//************************************************************
* Project Name: lhjTest
* File Name   : UseCoolInterface.java
* File Desc   : UseCoolInterface.java
* Author      : Administrator
* Create      : 2007-3-25
* Modify:
***********************************************************/
package org.lhj.cool.mock;

import org.lhj.cool.junit.CoolInterface;

/** *//**
* @author Administrator
*/
public class UseCoolInterface
...{
    private CoolInterface cool;
   
    public void setCoolInterface(CoolInterface cool)
    ...{
        this.cool = cool;
    }
   
    public String reverseString(String inputStr) throws NullPointerException
    ...{
        return cool.reverseString(inputStr);
    }

}

这个类也很简单,相当于一个adapter。

下面看看如何使用easymock进行接口的打桩:
/** *//************************************************************
* Project Name: lhjTest
* File Name   : UseCoolInterfaceJTest.java
* File Desc   : UseCoolInterfaceJTest.java
* Author      : Administrator
* Create      : 2007-3-25
* Modify:
***********************************************************/
package org.lhj.cool.mock;

import junit.framework.TestCase;

import org.easymock.MockControl;
import org.lhj.cool.junit.CoolInterface;
import org.lhj.cool.mock.UseCoolInterface;

/** *//**
* @author Administrator
*/
public class UseCoolInterfaceJTest extends TestCase
...{
    private UseCoolInterface testInterface;
   
    private MockControl ctrl;
   
    private CoolInterface cool;

    /**//* (non-Javadoc)
     * @see junit.framework.TestCase#setUp()
     */
    protected void setUp() throws Exception
    ...{
        super.setUp();
       
        ctrl = MockControl.createControl(CoolInterface.class);
        cool = (CoolInterface) ctrl.getMock();
       
        testInterface = new UseCoolInterface();
        testInterface.setCoolInterface(cool);
    }

    /**//* (non-Javadoc)
     * @see junit.framework.TestCase#tearDown()
     */
    protected void tearDown() throws Exception
    ...{
        super.tearDown();
    }

    /** *//**
     * Test method for {@link org.lhj.cool.mock.UseCoolInterface#reverseString(java.lang.String)}.
     */
    public void testReverseString()
    ...{       
        ctrl.expectAndThrow(cool.reverseString(null), new NullPointerException());       
        ctrl.expectAndReturn(cool.reverseString(""), null);
        ctrl.expectAndReturn(cool.reverseString("AAaa"), "aaAA");
        ctrl.expectAndReturn(cool.reverseString("aaAA"), "AAaa");
        ctrl.replay();
       
        try
        ...{
            testInterface.reverseString(null);
            fail();
        }
        catch (NullPointerException e)
        ...{
            //donothing
        }
        assertNull(testInterface.reverseString(""));
        assertEquals("aaAA", testInterface.reverseString("AAaa"));
        assertEquals("AAaa", testInterface.reverseString("aaAA"));
       
        ctrl.verify();
    }
}

从上面可以看出使用easymock的步骤:

1、在setUp时创建MockControl,它是控制mock object行为的类,参数传入欲打桩的接口的class。

2、从mockControl中getmock,返回mock object转化为本类型对象。

3、创建欲测试类并将mock object传进去。

以上几步代码为:
        ctrl = MockControl.createControl(CoolInterface.class);
        cool = (CoolInterface) ctrl.getMock();
       
        testInterface = new UseCoolInterface();
        testInterface.setCoolInterface(cool);

4、使用mockControl对mock object进行设置,这一步又称为训练,代码为
        ctrl.expectAndThrow(cool.reverseString(null), new NullPointerException());       
        ctrl.expectAndReturn(cool.reverseString(""), null);
        ctrl.expectAndReturn(cool.reverseString("AAaa"), "aaAA");
        ctrl.expectAndReturn(cool.reverseString("aaAA"), "AAaa");

第一行意思是告诉mockControl,mock object的reverseString方法会被调用,传入的参数是null,并会抛出异常;第二行意思是告诉mockControl,mock object的reverseString方法会被调用,传入的参数是"",并返回null;其他行类似。

5、训练完之后可以接受检查了,代码为:
        ctrl.replay();

6、现在可以进行测试了,以下是测试代码:
        try
        ...{
            testInterface.reverseString(null);
            fail();
        }
        catch (NullPointerException e)
        ...{
            //donothing
        }
        assertNull(testInterface.reverseString(""));
        assertEquals("aaAA", testInterface.reverseString("AAaa"));
        assertEquals("AAaa", testInterface.reverseString("aaAA"));

可以看出,这跟一般的JUnit语句是一样的。

7、检验测试结果
        ctrl.verify();

mockControl会验证mockObject是否按照训练的情况参与了测试,即训练时的方法是否被调用了,传入的参数对不对。

         以上示例只是easymock的一部分功能,除此之外,easymock还能测试mockObject的方法是否按照顺序调用了,调用的次数对不对,传入的参数是否在给定的范围之内,另外还可以改变参数对象的内容。

        除了easymock可以mock接口外,像mock objects,Jmock,mock Creator都可以实现此类功能。其中mock Creator还可以mock class,但是可惜mock creator是静态打桩的,实现起来有额外的工作。我在公司里用的是jdk1.4,无法用easymockclassextension1.2,所以使用了easymock加mock creator,幸好mock creator有eclipse插件,生成mock class也比较方便。所以如果你使用的是JDK1。4,用easymock加mock creator绝对是最佳组合;如果你使用的是JDK1.5,那么使用easymock加easymockclassextension会使你工作更轻松。



很多时候,我们要写一些单元测试来测试我们程序是否能正确触发异常。
  比如下面的例子中,我们就写了一个test case来测试一个Email验证类EmailAddrValidator,这个类有一个doValidate(email)方法可以验证email是否合法,如果不合法则会抛出ValidationException异常。因此我们写了两个方法来进行单元测试,前一个方法testDoValidate用来测试正常值,后一个方法testDoValidateException用来测试对错误的email格式是否能正确触发异常。
  这个例子的关键是方法testDoValidateException(String email) 。

import junit.framework.TestCase;

public class TestEmailAddrValidator extends TestCase {
    EmailAddrValidator validator = new EmailAddrValidator();

    public void testDoValidate() throws ValidationException {
        validator.doValidate("glchengang@163.com", null);
        validator.doValidate("glchen.gang@163.com", null);
        validator.doValidate("glchen_gang@163.com", null);
        validator.doValidate("glchen.gang@163_tom.com", null);
    }

    public void testDoValidateException() {
        testDoValidateException("@b.c");
        testDoValidateException("a@.c");
        testDoValidateException("a@b.");
        testDoValidateException("@.c");
        testDoValidateException("@...");
        testDoValidateException(" ");
        testDoValidateException(null);
    }

    private void testDoValidateException(String email) {
        try {
            validator.doValidate(email, null);
            fail("末抛出异常");
        } catch (ValidationException e) {
            assertTrue(true);
        }
    }
}



-----------------------------------------
注:在这里Locale 参数并没有用到。
import java.util.Locale;

import com.hygensoft.common.configure.ConfigureObject;

public class EmailAddrValidator{

    protected static final String ERROR_CODE_INVALID_EMAIL_ADDR = "INVALID_EMAIL_ADDR";
    protected static final String ERROR_CODE_INVALID_INPUT = "INVALID_INPUT_OBJECT";

    public Object doValidate(Object input, Locale locale) throws ValidationException {
        if (!(input instanceof String)) {
            throw new ValidationException(ERROR_CODE_INVALID_INPUT, input);
        }
        String inputStr = (String) input;
        int idx = inputStr.indexOf('@');
        if (idx == -1 || idx == 0) {
            throw new ValidationException(ERROR_CODE_INVALID_INPUT, input);
        }
        int idx2 = inputStr.indexOf('.', idx);
        if (idx2 == -1 || idx2 == idx + 1) {
            throw new ValidationException(ERROR_CODE_INVALID_INPUT, input);
        }
        if (inputStr.endsWith(".")) {
            throw new ValidationException(ERROR_CODE_INVALID_INPUT, input);
        }
        return input;
    }

    /* (non-Javadoc)
     * @see com.hygensoft.common.configure.Configurable#initialize(com.hygensoft.common.configure.ConfigureObject)
     */
    public void initialize(ConfigureObject conf) {}

}


比如下面的例子中,我们就写了一个test case来测试一个Email验证类EmailAddrValidator,这个类有一个doValidate(email)方法可以验证email是否合法,如果不合法则会抛出ValidationException异常。因此我们写了两个方法来进行单元测试,前一个方法testDoValidate用来测试正常值,后一个方法testDoValidateException用来测试对错误的email格式是否能正确触发异常。

  这个例子的关键是方法testDoValidateException(String email) 。

import junit.framework.TestCase;
public class TestEmailAddrValidator extends TestCase {
EmailAddrValidator validator = new EmailAddrValidator();
public void testDoValidate() throws ValidationException {
validator.doValidate("glchengang@163.com", null);
validator.doValidate("glchen.gang@163.com", null);
validator.doValidate("glchen_gang@163.com", null);
validator.doValidate("glchen.gang@163_tom.com", null);
}
public void testDoValidateException() {
testDoValidateException("@b.c");
testDoValidateException("a@.c");
testDoValidateException("a@b.");
testDoValidateException("@.c");
testDoValidateException("@...");
testDoValidateException(" ");
testDoValidateException(null);
}
private void testDoValidateException(String email) {
try {
validator.doValidate(email, null);
fail("末抛出异常");
} catch (ValidationException e) {
assertTrue(true);
}
}
}

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

  注:在这里Locale 参数并没有用到。

import java.util.Locale;
import com.hygensoft.common.configure.ConfigureObject;
public class EmailAddrValidator{
protected static final String ERROR_CODE_INVALID_EMAIL_ADDR = "INVALID_EMAIL_ADDR";
protected static final String ERROR_CODE_INVALID_INPUT = "INVALID_INPUT_OBJECT";
public Object doValidate(Object input, Locale locale) throws ValidationException {
if (!(input instanceof String)) {
throw new ValidationException(ERROR_CODE_INVALID_INPUT, input);
}
String inputStr = (String) input;
int idx = inputStr.indexOf('@');
if (idx == -1 || idx == 0) {
throw new ValidationException(ERROR_CODE_INVALID_INPUT, input);
}
int idx2 = inputStr.indexOf('.', idx);
if (idx2 == -1 || idx2 == idx + 1) {
throw new ValidationException(ERROR_CODE_INVALID_INPUT, input);
}
if (inputStr.endsWith(".")) {
throw new ValidationException(ERROR_CODE_INVALID_INPUT, input);
}
return input;
}
/* (non-Javadoc)
* @see com.hygensoft.common.configure.Configurable#initialize(com.hygensoft.common.configure.ConfigureObject)
*/
public void initialize(ConfigureObject conf) {}
}


[关键字]JUnit、测试异常

很多时候,我们要写一些单元测试来测试我们程序是否能正确触发异常。
  比如下面的例子中,我们就写了一个Test Case来测试一个EmAIl验证类EmailAddrValidator,这个类有一个doValidate(email)方法可以验证email是否合法,如果不合法则会抛出ValidationException异常。因此我们写了两个方法来进行单元测试,前一个方法tEStDoValidate用来测试正常值,后一个方法teSTDoValidateException用来测试对错误的email格式是否能正确触发异常。
  这个例子的关键是方法testDoValidateException(String email) 。

import junit.Framework.TestCASe;

public Class TestEmailAddrValidator extends TestCase {
    EmailAddrValidator validator = new EmailAddrValidator();

    public void testDoValidate() throws ValidationException {
        validator.doValidate("glchenGAng@163.com", null);
        validator.doValidate("glchen.gang@163.com", null);
        validator.doValidate("glchen_gang@163.com", null);
        validator.doValidate("glchen.gang@163_tom.com", null);
    }

    public void testDoValidateException() {
        testDoValidateException("@b.c");
        testDoValidateException("a@.c");
        testDoValidateException("a@b.");
        testDoValidateException("@.c");
        testDoValidateException("@...");
        testDoValidateException(" ");
        testDoValidateException(null);
    }

    private void testDoValidateException(String email) {
        try {
            validator.doValidate(email, null);
            fail("末抛出异常");
        } catch (ValidationException e) {
            assertTrue(true);
        }
    }
}

-----------------------------------------
注:在这里Locale 参数并没有用到。
import java.util.Locale;

import com.hygensoft.common.configure.Configureobject;

public class EmailAddrValidator{

    protECted static final String ERROR_CODE_INVALID_EMAIL_ADDR = "INVALID_EMAIL_ADDR";
    protected static final String ERROR_CODE_INVALID_INPUT = "INVALID_INPUT_OBJECT";

    public Object doValidate(Object input, Locale locale) throws ValidationException {
        if (!(input instanceof String)) {
            throw new ValidationException(ERROR_CODE_INVALID_INPUT, input);
        }
        String inputStr = (String) input;
        int idx = inputStr.indexOf('@');
        if (idx == -1 || idx == 0) {
            throw new ValidationException(ERROR_CODE_INVALID_INPUT, input);
        }
        int idx2 = inputStr.indexOf('.', idx);
        if (idx2 == -1 || idx2 == idx + 1) {
            throw new ValidationException(ERROR_CODE_INVALID_INPUT, input);
        }
        if (inputStr.endsWith(".")) {
            throw new ValidationException(ERROR_CODE_INVALID_INPUT, input);
        }
        return input;
    }

    /* (non-JavADOc)
     * @see com.hygensoft.common.configure.Configurable#initialize(com.hygensoft.common.configure.ConfigureObject)
     */
    public void initialize(ConfigureObject conf) {}

}


junit assert() 使用实例

package com.liyingcheng.netTest;


import com.liyingcheng.net.Sort;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import junit.xiaoxuhui.Sum;


public class SortTest extends TestCase
{
// Sort popObj = new Sort();

public SortTest(String name)
{
  super(name);
}


protected void setUp() throws Exception
{
  super.setUp();
}


protected void tearDown() throws Exception
{
  super.tearDown();
}


static public void assertEquals(int[] expected, int[] actual)
{
  for(int i = 0; i < expected.length; i++)
  {
   assertEquals(null, expected[i], actual[i]);
  }
}


public void testPopSort()
{
  int[] expected = new int[] {1, 2, 3, 4};
  assertEquals(expected, Sort.popSort(new int[] {1, 2, 4, 3}));
  assertEquals(expected, Sort.popSort(new int[] {1, 2, 3, 4}));
  assertEquals(expected, Sort.popSort(new int[] {1, 3, 4, 2}));
  assertEquals(expected, Sort.popSort(new int[] {1, 3, 2, 4}));
  assertEquals(expected, Sort.popSort(new int[] {2, 1, 4, 3}));
  assertEquals(expected, Sort.popSort(new int[] {2, 4, 1, 3}));
  assertEquals(expected, Sort.popSort(new int[] {3, 2, 4, 1}));
  assertEquals(new int[] {1, 2}, Sort.popSort(new int[] {2, 1}));
  // assertEquals(new int[]{1,3,2,4},popObj.popSort(new int[]{1,2,4,3}));
  // assertEquals(new int[]{1,2,3,4},popObj.popSort(new int[]{1,2,4,3}));
}


public void testCreateArray()
{
  assertEquals(4, Sort.createArray(4).length);
}


public void testGetSum()
{
  assertEquals(5050, Sum.getSum(1, 100));
}


public void testFalse()
{
  assertFalse(false);
  assertTrue(true);
}


public void testIsNull()
{
  String str1 = "";
  int[] arr1 = {};
  String str2 = null;
  int[] arr2 = null;
  assertNotNull(str1);
  assertNotNull(arr1);
  assertNull(str2);
  assertNull(arr2);
  // assertNull(str);
}


public void testNull()
{
}


public void testNotSame()
{
  String str1 = "123";
  String str2 = "123";
  String str3 = new String(str1);
  String str4 = new String("123");
  int one = 1;
  int first = 1;
  assertSame(one, first);
  assertSame(str1, str2);
  assertNotSame(str3, str4);
  //fail("hahahahahahahah");
  /*
   * assertNotSame(one,first); assertNotSame(str1,str2);
   */
}


public static Test suite()
{
  TestSuite suite = new TestSuite("Test sort!");
  suite.addTestSuite(SortTest.class);
  return suite;
}

}


package junit.sineat.templet; import java.util.Hashtable; import junit.framework.Assert; import junit.framework.TestCase; import junit.framework.TestSuite; public class JunitB extends TestCase...{ /** *//**定义你需要测试的类及用到的变量*****************************/ public Hashtable hasha=null;// public Hashtable hashb=null; /** *//*******************************************************/ public JunitB(String name)...{ super(name);//创建子类 } /** *//**用setUp进行初始化操作*/ protected void setUp() throws Exception ...{ super.setUp(); hasha =new Hashtable();//这里 } /** *//**用tearDown来销毁所占用的资源*/ protected void tearDown() throws Exception ...{ super.tearDown(); //System.gc(); } /** *//**写一个测试方法断言期望的结果**/ public void testBodyStatus() ...{ //hasha =new Hashtable();//有此句后也可去掉setUp() tearDown() assertNotNull(hasha); //hasha.put("0","let's try again");//test1.error版 assertTrue(hasha.isEmpty());//期望为空 } /** *//**再写一个测试方法断言期望的结果**/ public void testBodySame() ...{ //hashb=(Hashtable)hasha.clone(); //test2.error版 hashb=hasha; //test2.OK 版 Assert.assertSame(hasha,hashb); } /** *//**suite()方法,使用反射动态的创建一个包含所有的testXxxx方法的测试套件**/ public static TestSuite suite() ...{ return new TestSuite(JunitB.class); } /** *//****写一个main()运行测试*****************/ public static void main(String args[]) ...{ junit.textui.TestRunner.run(suite());//以文本运行器的方式方便的 //junit.swingui.TestRunner.run(JunitB.class); } }


package jp.planstaff.dao;

import java.util.Date;

import jp.planstaff.model.User;
import junit.framework.TestCase;
public class UserDAOTest extends TestCase {

public void testGetInstance() {
   UserDAO dao = UserDAO.getInstance();
   assertNotNull(dao);
}
public void testSave() throws Exception {
   User user = new User("hira", "kata", 1, new Date(), "aaa@aaa.com",
     "1234-123", "1234-123", "aaaa", "bbbb", "asfaewr", "awerasr",
     "prprrprr", new Date());
   UserDAO.getInstance().save(user);
   assertNotNull(user.getId());
}
public void testUpdate() throws Exception {
   User user = new User("hira", "kata", 1, new Date(), "aaa@aaa.com",
     "1234-123", "1234-123", "aaaa", "bbbb", "asfaewr", "awerasr",
     "prprrprr", new Date());
   UserDAO.getInstance().save(user);
   User user1 = UserDAO.getInstance().findById(user.getId());
   user1.setHira("hirahira");
   user1.setKata("katakata");
   UserDAO.getInstance().update(user1);
   assertEquals("hirahira", user1.getHira());
   assertEquals("katakata", user1.getKata());
   assertEquals(user.getId(), user1.getId());
}
public void testFindAll() throws Exception {
   int size1 = UserDAO.getInstance().findAll().size();
   User user = new User("hira", "kata", 1, new Date(), "aaa@aaa.com",
     "1234-123", "1234-123", "aaaa", "bbbb", "asfaewr", "awerasr",
     "prprrprr", new Date());
   UserDAO.getInstance().save(user);
   int size2 = UserDAO.getInstance().findAll().size();
   assertNotSame(new Integer(size1), new Integer(size2));
}
public void testDelete() throws Exception {
   User user = new User("hira", "kata", 1, new Date(), "aaa@aaa.com",
     "1234-123", "1234-123", "aaaa", "bbbb", "asfaewr", "awerasr",
     "prprrprr", new Date());
   UserDAO.getInstance().save(user);
   User user1 = new User();
   user1.setId(user.getId());
   UserDAO.getInstance().delete(user1);
}
}

例2是一个用JUnit测试例1的单例模式的案例:
例2.一个单例模式的案例

import org.apache.log4j.Logger;
import junit.framework.Assert;
import junit.framework.TestCase;

public class SingletonTest extends TestCase {
   private ClassicSingleton sone = null, stwo = null;
   private static Logger logger = Logger.getRootLogger();

   public SingletonTest(String name) {
      super(name);
   }
   public void setUp() {
      logger.info("getting singleton...");
      sone = ClassicSingleton.getInstance();
      logger.info("...got singleton: " + sone);

      logger.info("getting singleton...");
      stwo = ClassicSingleton.getInstance();
      logger.info("...got singleton: " + stwo);
   }
   public void testUnique() {
      logger.info("checking singletons for equality");
      Assert.assertEquals(true, sone == stwo);
   }
}


例2两次调用ClassicSingleton.getInstance(),并且把返回的引用存储在成员变量中。方法testUnique()会检查这些引用看它们是否相同。例3是这个测试案例的输出:
例3.是这个测试案例的输出

Buildfile: build.xml

init:
     [echo] Build 20030414 (14-04-2003 03:08)

compile:

run-test-text:
     [java] .INFO main: getting singleton...
     [java] INFO main: created singleton: Singleton@e86f41
     [java] INFO main: ...got singleton: Singleton@e86f41
     [java] INFO main: getting singleton...
     [java] INFO main: ...got singleton: Singleton@e86f41
     [java] INFO main: checking singletons for equality

     [java] Time: 0.032

     [java] OK (1 test)


正如前面的清单所示,例2的简单测试顺利通过----通过ClassicSingleton.getInstance()获得的两个单例类的引用确实相同;然而,你要知道这些引用是在单线程中得到的。下面的部分着重于用多线程测试单例类。

多线程因素的考虑

在例1中的ClassicSingleton.getInstance()方法由于下面的代码而不是线程安全的:

1: if(instance == null) {
2:    instance = new Singleton();
3: }


如果一个线程在第二行的赋值语句发生之前切换,那么成员变量instance仍然是null,然后另一个线程可能接下来进入到if块中。在这种情况下,两个不同的单例类实例就被创建。不幸的是这种假定很少发生,这样这种假定也很难在测试期间出现(译注:在这可能是作者对很少出现这种情况而导致无法测试从而使人们放松警惕而感到叹惜)。为了演示这个线程轮换,我得重新实现例1中的那个类。例4就是修订后的单例类:
例4.人为安排的方式

import org.apache.log4j.Logger;

public class Singleton {
  private static Singleton singleton = null;
  private static Logger logger = Logger.getRootLogger();
  private static boolean firstThread = true;

  protected Singleton() {
    // Exists only to defeat instantiation.
  }
  public static Singleton getInstance() {
     if(singleton == null) {
        simulateRandomActivity();
        singleton = new Singleton();
     }
     logger.info("created singleton: " + singleton);
     return singleton;
  }
  private static void simulateRandomActivity() {
     try {
        if(firstThread) {
           firstThread = false;
           logger.info("sleeping...");

           // This nap should give the second thread enough time
           // to get by the first thread.
             Thread.currentThread().sleep(50);
       }
     }
     catch(InterruptedException ex) {
        logger.warn("Sleep interrupted");
     }
  }
}


除了在这个清单中的单例类强制使用了一个多线程错误处理,例4类似于例1中的单例类。在getInstance()方法第一次被调用时,调用这个方法的线程会休眠50毫秒以便另外的线程也有时间调用getInstance()并创建一个新的单例类实例。当休眠的线程觉醒时,它也会创建一个新的单例类实例,这样我们就有两个单例类实例。尽管例4是人为如此的,但它却模拟了第一个线程调用了getInstance()并在没有完成时被切换的真实情形。
例5测试了例4的单例类:
例5.失败的测试

import org.apache.log4j.Logger;
import junit.framework.Assert;
import junit.framework.TestCase;

public class SingletonTest extends TestCase {
   private static Logger logger = Logger.getRootLogger();
   private static Singleton singleton = null;

   public SingletonTest(String name) {
      super(name);
   }
   public void setUp() {
      singleton = null;
   }
   public void testUnique() throws InterruptedException {
      // Both threads call Singleton.getInstance().
      Thread threadOne = new Thread(new SingletonTestRunnable()),
             threadTwo = new Thread(new SingletonTestRunnable());

      threadOne.start();
      threadTwo.start();

      threadOne.join();
      threadTwo.join();
   }
   private static class SingletonTestRunnable implements Runnable {
      public void run() {
         // Get a reference to the singleton.
         Singleton s = Singleton.getInstance();

         // Protect singleton member variable from
         // multithreaded access.
         synchronized(SingletonTest.class) {
            if(singleton == null) // If local reference is null...
               singleton = s;     // ...set it to the singleton
         }
         // Local reference must be equal to the one and
         // only instance of Singleton; otherwise, we have two
                  // Singleton instances.
         Assert.assertEquals(true, s == singleton);
      }
   }
}


例 5的测试案例创建两个线程,然后各自启动,等待完成。这个案例保持了一个对单例类的静态引用,每个线程都会调用 Singleton.getInstance()。如果这个静态成员变量没有被设置,那么第一个线程就会将它设为通过调用getInstance()而得到的引用,然后这个静态变量会与一个局部变量比较是否相等。
在这个测试案例运行时会发生一系列的事情:第一个线程调用 getInstance(),进入if块,然后休眠;接着,第二个线程也调用getInstance()并且创建了一个单例类的实例。第二个线程会设置这个静态成员变量为它所创建的引用。第二个线程检查这个静态成员变量与一个局部备份的相等性。然后测试通过。当第一个线程觉醒时,它也会创建一个单例类的实例,并且它不会设置那个静态成员变量(因为第二个线程已经设置过了),所以那个静态变量与那个局部变量脱离同步,相等性测试即告失败。例6列出了例5的输出:
例6.例5的输出

Buildfile: build.xml
init:
     [echo] Build 20030414 (14-04-2003 03:06)
compile:
run-test-text:
INFO Thread-1: sleeping...
INFO Thread-2: created singleton: Singleton@7e5cbd
INFO Thread-1: created singleton: Singleton@704ebb
junit.framework.AssertionFailedError: expected: but was:
   at junit.framework.Assert.fail(Assert.java:47)
   at junit.framework.Assert.failNotEquals(Assert.java:282)
   at junit.framework.Assert.assertEquals(Assert.java:64)
   at junit.framework.Assert.assertEquals(Assert.java:149)
   at junit.framework.Assert.assertEquals(Assert.java:155)
   at SingletonTest$SingletonTestRunnable.run(Unknown Source)
   at java.lang.Thread.run(Thread.java:554)
     [java] .
     [java] Time: 0.577

     [java] OK (1 test)


到现在为止我们已经知道例4不是线程安全的,那就让我们看看如何修正它。

同步

要使例4的单例类为线程安全的很容易----只要像下面一个同步化getInstance()方法:

public synchronized static Singleton getInstance() {
   if(singleton == null) {
      simulateRandomActivity();
      singleton = new Singleton();
   }
   logger.info("created singleton: " + singleton);
   return singleton;
}


在同步化getInstance()方法后,我们就可以得到例5的测试案例返回的下面的结果:


Buildfile: build.xml

init:
     [echo] Build 20030414 (14-04-2003 03:15)

compile:
    [javac] Compiling 2 source files

run-test-text:
INFO Thread-1: sleeping...
INFO Thread-1: created singleton: Singleton@ef577d
INFO Thread-2: created singleton: Singleton@ef577d
     [java] .
     [java] Time: 0.513

     [java] OK (1 test)


这此,这个测试案例工作正常,并且多线程的烦恼也被解决;然而,机敏的读者可能会认识到getInstance()方法只需要在第一次被调用时同步。因为同步的性能开销很昂贵(同步方法比非同步方法能降低到100次左右),或许我们可以引入一种性能改进方法,它只同步单例类的getInstance()方法中的赋值语句。
一种性能改进的方法

寻找一种性能改进方法时,你可能会选择像下面这样重写getInstance()方法:


public static Singleton getInstance() {
   if(singleton == null) {
      synchronized(Singleton.class) {
         singleton = new Singleton();
      }
   }
   return singleton;
}


这个代码片段只同步了关键的代码,而不是同步整个方法。然而这段代码却不是线程安全的。考虑一下下面的假定:线程1进入同步块,并且在它给 singleton成员变量赋值之前线程1被切换。接着另一个线程进入if块。第二个线程将等待直到第一个线程完成,并且仍然会得到两个不同的单例类实例。有修复这个问题的方法吗?请读下去。
双重加锁检查

初看上去,双重加锁检查似乎是一种使懒汉式实例化为线程安全的技术。下面的代码片段展示了这种技术:

public static Singleton getInstance() {
  if(singleton == null) {
     synchronized(Singleton.class) {
       if(singleton == null) {
         singleton = new Singleton();
       }
    }
  }
  return singleton;
}


如果两个线程同时访问getInstance()方法会发生什么?想像一下线程1进行同步块马上又被切换。接着,第二个线程进入if 块。当线程1退出同步块时,线程2会重新检查看是否singleton实例仍然为null。因为线程1设置了singleton成员变量,所以线程2的第二次检查会失败,第二个单例类实例也就不会被创建。似乎就是如此。
不幸的是,双重加锁检查不会保证正常工作,因为编译器会在Singleton的构造方法被调用之前随意给singleton赋一个值。如果在singleton引用被赋值之后而被初始化之前线程1被切换,线程2就会被返回一个对未初始化的单例类实例的引用。
一个改进的线程安全的单例模式实现

例7列出了一个简单、快速而又是线程安全的单例模式实现:
例7.一个简单的单例类

public class Singleton {
   public final static Singleton INSTANCE = new Singleton();
   private Singleton() {
         // Exists only to defeat instantiation.
      }
}


这段代码是线程安全的是因为静态成员变量一定会在类被第一次访问时被创建。你得到了一个自动使用了懒汉式实例化的线程安全的实现;你应该这样使用它:

      Singleton singleton = Singleton.INSTANCE;
      singleton.dothis();
      singleton.dothat();
      ...


当然万事并不完美,前面的Singleton只是一个折衷的方案;如果你使用那个实现,你就无法改变它以便后来你可能想要允许多个单例类的实例。用一种更折哀的单例模式实现(通过一个getInstance()方法获得实例)你可以改变这个方法以便返回一个唯一的实例或者是数百个实例中的一个.你不能用一个公开且是静态的(public static)成员变量这样做.

你可以安全的使用例7的单例模式实现或者是例1的带一个同步的getInstance()方法的实现.然而,我们必须要研究另一个问题:你必须在编译期指定这个单例类,这样就不是很灵活.一个单例类的注册表会让我们在运行期指定一个单例类.
分享到:
评论
发表评论

文章已被作者锁定,不允许评论。

相关推荐

    apache-mina-2.0.4.rar_apache mina_mina

    Apache Mina是一个高性能、异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。这个"apache-mina-2.0.4.rar"压缩包包含的是Apache Mina 2.0.4版本的源代码,是深入理解和定制Mina的...

    mina的高级使用,mina文件图片传送,mina发送文件,mina报文处理,mina发送xml和json

    Apache Mina是一个开源的网络通信应用框架,主要应用于Java平台,它为高性能、高可用性的网络应用程序提供了基础架构。在本文中,我们将深入探讨Mina的高级使用,特别是在文件图片传送、文件发送、XML和JSON报文处理...

    Mina+Socket通信

    Mina和Socket是两种常见的网络通信框架和技术,它们在Java编程环境中被广泛使用。本篇文章将深入探讨如何使用Mina与Socket实现通信,并提供客户端和服务端的实现代码概述。 Mina(全称“MINA: Minimalistic ...

    mina连接 mina心跳连接 mina断线重连

    Apache Mina是一个开源的网络通信框架,常用于构建高性能、高效率的服务端应用程序,尤其在Java平台上。在本文中,我们将深入探讨Mina的核心概念,包括连接管理、心跳机制以及断线重连策略。 首先,让我们理解"Mina...

    mina自定义编解码器详解

    **mina自定义编解码器详解** mina是一个Java开发的网络通信框架,广泛应用于TCP和UDP协议的服务器和客户端开发。在mina框架中,编解码器(Codec)扮演着至关重要的角色,它负责将应用层的数据转换为网络传输的字节...

    Java springboot 整合mina 框架,nio通讯基础教程,mina框架基础教程.zip

    Java SpringBoot 整合Mina框架,涉及到的核心技术主要包括Java NIO(非阻塞I/O)、Mina框架以及SpringBoot的集成应用。本教程旨在帮助开发者深入理解和掌握这些技术,并提供了一个可直接使用的基础平台框架。 Java ...

    给予mina 协议进行大数据传输

    标题中的“给予mina协议进行大数据传输”指的是一种基于Java的网络通信框架——Apache MINA(Model-View-Controller for Network Applications)。MINA是Apache软件基金会的一个项目,它提供了一个高度可扩展和高...

    Mina开发实例(服务端、客户端)DEMO

    Apache Mina是一个高度可扩展的网络通信框架,它允许开发者创建高性能、高效率的服务端和客户端应用程序。在Java世界中,Mina以其简洁的API和灵活性而受到青睐,尤其适用于处理大量的并发连接,如TCP/IP和UDP协议。...

    springboot 深度整合mina开箱即用

    在本文中,我们将深入探讨如何将Spring Boot与Mina进行深度整合,以便为新手开发者提供一个开箱即用的解决方案。Spring Boot以其简洁的配置和快速的开发体验,已经成为Java领域中的主流微服务框架,而Mina则是一个...

    MINA_API+MINA_DOC+mina

    MINA (Java IO Network Application Framework) 是一个由Apache软件基金会开发的开源网络通信框架,主要应用于构建高性能、高可用性的网络服务器。这个压缩包包含了MINA API文档、自学手册以及开发指南,对于学习和...

    mina心跳包机制

    mina心跳包机制是Apache Mina框架中的一个关键特性,它用于维持网络连接的活跃状态,确保数据能够在客户端和服务端之间顺畅地传输。Mina是一个高度可扩展的Java网络应用框架,广泛应用于各种分布式系统和网络服务,...

    java-mina通信框架详解.docx

    Apache Mina是一个强大的网络通信框架,专为基于TCP/IP和UDP/IP协议栈的应用设计。它提供了JAVA对象的序列化和虚拟机内部通信的功能,使得开发者能够迅速构建高性能、高可扩展性的网络应用。Mina的核心特性是其事件...

    spring boot 整合mina 串口

    **Spring Boot 整合Mina实现串口通信详解** 在Java开发中,有时我们需要与硬件设备进行串口通信,例如读取传感器数据或控制工业设备。Spring Boot作为一款轻量级的框架,使得快速构建应用变得简单。而Mina则是一款...

    mina2.0 含11个jar包

    mina-core-2.0.0-M6.jar mina-example-2.0.0-M6.jar mina-filter-codec-netty-2.0.0-M6.jar mina-filter-compression-2.0.0-M6.jar mina-integration-beans-2.0.0-M6.jar mina-integration-jmx-2.0.0-M6.jar mina-...

    mina客户端简单代码示例

    Apache Mina是一个开源的网络通信框架,主要用于简化Java应用程序与远程服务器之间的通信。它提供了高度可扩展和高性能的网络协议处理能力,支持多种传输层协议,如TCP/IP、UDP/IP和SSL/TLS等。在本示例中,我们关注...

    mina2.0全部jar包

    《mina2.0全部jar包详解》 Apache MINA(Multipurpose Infrastructure for Network Applications)是一个高性能、异步事件驱动的网络应用程序框架,主要用于简化开发Java网络应用,特别是那些基于TCP和UDP协议的...

    MINA断线重连死锁解决

    在IT行业中,网络通信是不可或缺的一部分,而Apache MINA(Model-Independent Network Application Framework)是一个高性能、异步的网络应用程序框架,广泛应用于TCP/IP和UDP协议的开发。当我们遇到"MINA断线重连...

    MINA2.0用户手册中文随笔翻译

    MINA2.0 用户手册中文随笔翻译 MINA 是一个基于 NIO(Non-Blocking I/O)的网络框架,提供了统一的接口来处理 TCP、UDP 和其他机制的通信。MINA 的主要特点是能够处理大量的 socket 连接,并提供了一个高层接口来...

    Android-MinaSocket一款基于Mina的Socket长连接库

    **Android-MinaSocket:基于Mina的高效Socket长连接库** 在移动应用开发中,尤其是Android平台,实时性与稳定性是许多应用场景的核心需求,比如在线游戏、即时通讯、物联网设备等。在这种背景下,使用Socket进行长...

    Mina2.0.7原代码,去掉slf4j代码

    Apache Mina是一个高度可扩展的网络通信框架,用于构建高性能、高效率的网络应用程序,如服务器和客户端。在"MINA 2.0.7原代码,去掉slf4j代码"这个项目中,我们可以看到Mina的核心组件mina_core_2.0.7已经被剥离了...

Global site tag (gtag.js) - Google Analytics