测试之路,与你同行!

发布新日志

  • 数据库锁

    2012-05-16 10:45:45

    转载:http://hi.baidu.com/dapplehou/blog/item/b341a97744fe6616b151b9a3.html

    Table of Contents

    • 1 前言
    • 2 锁的种类
    • 3 何时加锁?
    • 4 锁的粒度
    • 5 锁与事物隔离级别的优先级
    • 6 数据库的其它重要Hint以及它们的区别
    • 7 锁的超时等待
    • 8 附:各种锁的兼容关系表
    • 9 如何提高并发效率
    • 10 后记

    1 前言

    数据库大并发操作要考虑死锁和锁的性能问题。看到网上大多语焉不详(尤其更新锁),所以这里做个简明解释,为下面描述方便,这里用T1代表一个数据库执行请求,T2代表另一个请求,也可以理解为T1为一个线程,T2 为另一个线程。T3,T4以此类推。下面以SQL Server(2005)为例。

    2 锁的种类

    1. 共享锁(Shared lock)。
      例1:
      ----------------------------------------
      T1:    select * from table (请想象它需要执行1个小时之久,后面的sql语句请都这么想象)
      T2:    update table set column1='hello'
      
      过程:
      
      T1运行 (加共享锁)
      T2运行
      If T1 还没执行完
          T2等......
      else
          锁被释放
          T2执行
      endif
      
      T2之所以要等,是因为T2在执行update前,试图对table表加一个排他锁,
      而数据库规定同一资源上不能同时共存共享锁和排他锁。所以T2必须等T1
      执行完,释放了共享锁,才能加上排他锁,然后才能开始执行update语句。
      
      例2:
      ----------------------------------------
      T1:    select * from table
      T2:    select * from table
      
      这里T2不用等待T1执行完,而是可以马上执行。
      
      分析:
      T1运行,则table被加锁,比如叫lockA
      T2运行,再对table加一个共享锁,比如叫lockB。
      
      两个锁是可以同时存在于同一资源上的(比如同一个表上)。这被称为共
      享锁与共享锁兼容。这意味着共享锁不阻止其它session同时读资源,但阻
      止其它session update
      
      例3:
      ----------------------------------------
      T1:    select * from table
      T2:    select * from table
      T3:    update table set column1='hello'
      
      这次,T2不用等T1运行完就能运行,T3却要等T1和T2都运行完才能运行。
      因为T3必须等T1和T2的共享锁全部释放才能进行加排他锁然后执行update
      操作。
      
      例4:(死锁的发生)
      ----------------------------------------
      T1:
      begin tran
      select * from table (holdlock) (holdlock意思是加共享锁,直到事物结束才释放)
      update table set column1='hello'
      
      T2:
      begin tran
      select * from table(holdlock)
      update table set column1='world'
      
      假设T1和T2同时达到select,T1对table加共享锁,T2也对加共享锁,当
      T1的select执行完,准备执行update时,根据锁机制,T1的共享锁需要升
      级到排他锁才能执行接下来的update.在升级排他锁前,必须等table上的
      其它共享锁释放,但因为holdlock这样的共享锁只有等事务结束后才释放,
      所以因为T2的共享锁不释放而导致T1等(等T2释放共享锁,自己好升级成排
      他锁),同理,也因为T1的共享锁不释放而导致T2等。死锁产生了。
      
      例5:
      ----------------------------------------
      T1:
      begin tran
      update table set column1='hello' where id=10
      
      T2:
      begin tran
      update table set column1='world' where id=20
      
      这种语句虽然最为常见,很多人觉得它有机会产生死锁,但实际上要看情
      况,如果id是主键上面有索引,那么T1会一下子找到该条记录(id=10的记
      录),然后对该条记录加排他锁,T2,同样,一下子通过索引定位到记录,
      然后对id=20的记录加排他锁,这样T1和T2各更新各的,互不影响。T2也不
      需要等。
      
      但如果id是普通的一列,没有索引。那么当T1对id=10这一行加排他锁后,
      T2为了找到id=20,需要对全表扫描,那么就会预先对表加上共享锁或更新
      锁或排他锁(依赖于数据库执行策略和方式,比如第一次执行和第二次执行
      数据库执行策略就会不同)。但因为T1已经为一条记录加了排他锁,导致
      T2的全表扫描进行不下去,就导致T2等待。
      
      死锁怎么解决呢?一种办法是,如下:
      例6:
      ----------------------------------------
      T1:
      begin tran
      select * from table(xlock) (xlock意思是直接对表加排他锁)
      update table set column1='hello'
      
      T2:
      begin tran
      select * from table(xlock)
      update table set column1='world'
      
      这样,当T1的select 执行时,直接对表加上了排他锁,T2在执行select时,就需要等T1事物完全执行完才能执行。排除了死锁发生。
      但当第三个user过来想执行一个查询语句时,也因为排他锁的存在而不得不等待,第四个、第五个user也会因此而等待。在大并发
      情况下,让大家等待显得性能就太友好了,所以,这里引入了更新锁。
    2. 更新锁(Update lock)
      为解决死锁,引入更新锁。
      
      例7:
      ----------------------------------------
      T1:
      begin tran
      select * from table(updlock) (加更新锁)
      update table set column1='hello'
      T2:
      begin tran
      select * from table(updlock)
      update table set column1='world'
      
      更新锁的意思是:“我现在只想读,你们别人也可以读,但我将来可能会做更新操作,我已经获取了从共享锁(用来读)到排他锁
      (用来更新)的资格”。一个事物只能有一个更新锁获此资格。
      
      T1执行select,加更新锁。
      T2运行,准备加更新锁,但发现已经有一个更新锁在那儿了,只好等。
      
      当后来有user3、user4...需要查询table表中的数据时,并不会因为T1的select在执行就被阻塞,照样能查询,相比起例6,这提高
      了效率。
      
      例8:
      ----------------------------------------
      T1:    select * from table(updlock)    (加更新锁)
      T2:    select * from table(updlock)    (等待,直到T1释放更新锁,因为同一时间不能在同一资源上有两个更新锁)
      T3:    select * from table (加共享锁,但不用等updlock释放,就可以读)
      
      这个例子是说明:共享锁和更新锁可以同时在同一个资源上。这被称为共享锁和更新锁是兼容的。
      
      例9:
      ----------------------------------------
      T1:
      begin
      select * from table(updlock)      (加更新锁)
      update table set column1='hello'  (重点:这里T1做update时,不需要等T2释放什么,而是直接把更新锁升级为排他锁,然后执行update)
      T2:
      begin
      select * from table               (T1加的更新锁不影响T2读取)
      update table set column1='world'  (T2的update需要等T1的update做完才能执行)
      
      我们以这个例子来加深更新锁的理解,
      
      第一种情况:T1先达,T2紧接到达;在这种情况中,T1先对表加更新锁,T2对表加共享锁,假设T2的select先执行完,准备执行update,
      发现已有更新锁存在,T2等。T1执行这时才执行完select,准备执行update,更新锁升级为排他锁,然后执行update,执行完成,事务
      结束,释放锁,T2才轮到执行update。
      
      第二种情况:T2先达,T1紧接达;在这种情况,T2先对表加共享锁,T1达后,T1对表加更新锁,假设T2 select先结束,准备
      update,发现已有更新锁,则等待,后面步骤就跟第一种情况一样了。
      
      这个例子是说明:排他锁与更新锁是不兼容的,它们不能同时加在同一子资源上。
    3. 排他锁(独占锁,Exclusive Locks)
      这个简单,即其它事务既不能读,又不能改排他锁锁定的资源。
      例10
      T1:    update table set column1='hello' where id<1000
      T2:    update table set column1='world' where id>1000
      
      假设T1先达,T2随后至,这个过程中T1会对id<1000的记录施加排他锁.但不会阻塞T2的update。
      
      例11 (假设id都是自增长且连续的)
      T1:    update table set column1='hello' where id<1000
      T2:    update table set column1='world' where id>900
      
      如同例10,T1先达,T2立刻也到,T1加的排他锁会阻塞T2的update.
    4. 意向锁(Intent Locks)
      意向锁就是说在屋(比如代表一个表)门口设置一个标识,说明屋子里有人(比如代表某些记录)被锁住了。另一个人想知道屋子
      里是否有人被锁,不用进屋子里一个一个的去查,直接看门口标识就行了。
      
      当一个表中的某一行被加上排他锁后,该表就不能再被加表锁。数据库程序如何知道该表不能被加表锁?一种方式是逐条的判断该
      表的每一条记录是否已经有排他锁,另一种方式是直接在表这一层级检查表本身是否有意向锁,不需要逐条判断。显然后者效率高。
      
      例12:
      ----------------------------------------
      T1:    begin tran
             select * from table (xlock) where id=10  --意思是对id=10这一行强加排他锁
      T2:    begin tran
             select * from table (tablock)     --意思是要加表级锁
             
      假设T1先执行,T2后执行,T2执行时,欲加表锁,为判断是否可以加表锁,数据库系统要逐条判断table表每行记录是否已有排他锁,
      如果发现其中一行已经有排他锁了,就不允许再加表锁了。只是这样逐条判断效率太低了。
      
      实际上,数据库系统不是这样工作的。当T1的select执行时,系统对表table的id=10的这一行加了排他锁,还同时悄悄的对整个表
      加了意向排他锁(IX),当T2执行表锁时,只需要看到这个表已经有意向排他锁存在,就直接等待,而不需要逐条检查资源了。
      
      例13:
      ----------------------------------------
      T1:    begin tran
             update table set column1='hello' where id=1
      T2:    begin tran
             update table set column1='world' where id=1
      
      这个例子和上面的例子实际效果相同,T1执行,系统对table同时对行家排他锁、对页加意向排他锁、对表加意向排他锁。
    5. 计划锁(Schema Locks)
      例14:
      ----------------------------------------
      alter table .... (加schema locks,称之为Schema modification (Sch-M) locks
      
      DDL语句都会加Sch-M锁
      该锁不允许任何其它session连接该表。连都连不了这个表了,当然更不用说想对该表执行什么sql语句了。
      
      例15:
      ----------------------------------------
      用jdbc向数据库发送了一条新的sql语句,数据库要先对之进行编译,在编译期间,也会加锁,称之为:Schema stability (Sch-S) locks
      
      select * from tableA
      
      编译这条语句过程中,其它session可以对表tableA做任何操作(update,delete,加排他锁等等),但不能做DDL(比如alter table)操作。
    6. Bulk Update Locks 主要在批量导数据时用(比如用类似于oracle中的imp/exp的bcp命令)。不难理解,程序员往往也不需要关心,不赘述了。

    3 何时加锁?

    如何加锁,何时加锁,加什么锁,你可以通过hint手工强行指定,但大多是数据库系统自动决定的。这就是为什么我们可以不懂锁也可
    以高高兴兴的写SQL。
    
    例15:
    ----------------------------------------
    T1:    begin tran
           update table set column1='hello' where id=1
    T2:    SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED  -- 事物隔离级别为允许脏读
           go
           select * from table where id=1
    这里,T2的select可以查出结果。如果事物隔离级别不设为脏读,则T2会等T1事物执行完才能读出结果。
    
    数据库如何自动加锁的?
    
    1) T1执行,数据库自动加排他锁
    2) T2执行,数据库发现事物隔离级别允许脏读,便不加共享锁。不加共享锁,则不会与已有的排他锁冲突,所以可以脏读。
    
    例16:
    ----------------------------------------
    T1:    begin tran
           update table set column1='hello' where id=1
    T2:    select * from table where id=1 --为指定隔离级别,则使用系统默认隔离级别,它不允许脏读
    
    如果事物级别不设为脏读,则:
    1) T1执行,数据库自动加排他锁
    2) T2执行,数据库发现事物隔离级别不允许脏读,便准备为此次select过程加共享锁,但发现加不上,因为已经有排他锁了,所以就
       等啊等。直到T1执行完,释放了排他锁,T2才加上了共享锁,然后开始读....

    4 锁的粒度

    锁的粒度就是指锁的生效范围,就是说是行锁,还是页锁,还是整表锁. 锁的粒度同样既可以由数据库自动管理,也可以通过手工指定hint来管理。

    例17:
    ----------------------------------------
    T1:    select * from table (paglock)
    T2:    update table set column1='hello' where id>10
    
    T1执行时,会先对第一页加锁,读完第一页后,释放锁,再对第二页加锁,依此类推。假设前10行记录恰好是一页(当然,一般不可能
    一页只有10行记录),那么T1执行到第一页查询时,并不会阻塞T2的更新。
    
    例18:
    ----------------------------------------
    T1:    select * from table (rowlock)
    T2:    update table set column1='hello' where id=10
    
    T1执行时,对每行加共享锁,读取,然后释放,再对下一行加锁;T2执行时,会对id=10的那一行试图加锁,只要该行没有被T1加上行锁,
    T2就可以顺利执行update操作。
    
    例19:
    ----------------------------------------
    T1:    select * from table (tablock)
    T2:    update table set column1='hello' where id = 10
    
    T1执行,对整个表加共享锁. T1必须完全查询完,T2才可以允许加锁,并开始更新。
    
    以上3例是手工指定锁的粒度,也可以通过设定事物隔离级别,让数据库自动设置锁的粒度。不同的事物隔离级别,数据库会有不同的
    加锁策略(比如加什么类型的锁,加什么粒度的锁)。具体请查联机手册。

    5 锁与事物隔离级别的优先级

    手工指定的锁优先,
    例20:
    ----------------------------------------
    T1:    GO
           SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
           GO
           BEGIN TRANSACTION
           SELECT * FROM table (NOLOCK)
           GO
    T2:    update table set column1='hello' where id=10
    
    T1是事物隔离级别为最高级,串行锁,数据库系统本应对后面的select语句自动加表级锁,但因为手工指定了NOLOCK,所以该select
    语句不会加任何锁,所以T2也就不会有任何阻塞。

    6 数据库的其它重要Hint以及它们的区别

    1) holdlock 对表加共享锁,且事物不完成,共享锁不释放。
    2) tablock  对表加共享锁,只要statement不完成,共享锁不释放。
       与holdlock区别,见下例:
       例21
       ----------------------------------------
       T1:
       begin tran
       select * from table (tablock)
       T2:
       begin tran
       update table set column1='hello' where id = 10
    
       T1执行完select,就会释放共享锁,然后T2就可以执行update. 此之谓tablock. 下面我们看holdlock
       例22
       ----------------------------------------
       T1:
       begin tran
       select * from table (holdlock)
       T2:
       begin tran
       update table set column1='hello' where id = 10
       
       T1执行完select,共享锁仍然不会释放,仍然会被hold(持有),T2也因此必须等待而不能update. 当T1最后执行了commit或
       rollback说明这一个事物结束了,T2才取得执行权。
      
    3) TABLOCKX 对表加排他锁
      
       例23:
       ----------------------------------------
       T1:    select * from table(tablockx) (强行加排他锁)
       其它session就无法对这个表进行读和更新了,除非T1执行完了,就会自动释放排他锁。
       例24:
       ----------------------------------------
       T1:    begin tran
              select * from table(tablockx)
       这次,单单select执行完还不行,必须整个事物完成(执行了commit或rollback后)才会释放排他锁。
      
    4) xlock 加排他锁
       那它跟tablockx有何区别呢?
    
       它可以这样用,
       例25:
       ----------------------------------------
       select * from table(xlock paglock) 对page加排他锁
       而TABLELOCX不能这么用。
    
       xlock还可这么用:select * from table(xlock tablock) 效果等同于select * from table(tablockx)

    7 锁的超时等待

    例26

    SET LOCK_TIMEOUT 4000 用来设置锁等待时间,单位是毫秒,4000意味着等待
    4秒可以用select @@LOCK_TIMEOUT查看当前session的锁超时设置。-1 意味着
    永远等待。
    
    T1: begin tran
        udpate table set column1='hello' where id = 10
    T2: set lock_timeout 4000
        select * from table wehre id = 10

    T2执行时,会等待T1释放排他锁,等了4秒钟,如果T1还没有释放排他锁,T2就会抛出异常: Lock request time out period exceeded.

    8 附:各种锁的兼容关系表

    | Requested mode                     | IS  | S   | U   | IX  | SIX | X  |
    | Intent shared (IS)                 | Yes | Yes | Yes | Yes | Yes | No |
    | Shared (S)                         | Yes | Yes | Yes | No  | No  | No |
    | Update (U)                         | Yes | Yes | No  | No  | No  | No |
    | Intent exclusive (IX)              | Yes | No  | No  | Yes | No  | No |
    | Shared with intent exclusive (SIX) | Yes | No  | No  | No  | No  | No |
    | Exclusive (X)                      | No  | No  | No  | No  | No  | No |

    9 如何提高并发效率

    1. 悲观锁:利用数据库本身的锁机制实现。通过上面对数据库锁的了解,可以根据具体业务情况综合使用事务隔离级别与合理的手工指定锁的方式比如降低锁的粒度等减少并发等待。
    2. 乐观锁:利用程序处理并发。原理都比较好理解,基本一看即懂。方式大概有以下3种
      1. 对记录加版本号.
      2. 对记录加时间戳.
      3. 对将要更新的数据进行提前读取、事后对比。

    不论是数据库系统本身的锁机制,还是乐观锁这种业务数据级别上的锁机制,本质上都是对状态位的读、写、判断。

  • java并发

    2012-05-16 10:02:14

    转载:http://dapple.iteye.com/blog/787563

    并发

    Table of Contents

    • 1 什么是并发问题。
    • 2 java中synchronized的用法
    • 3 Java中的锁与排队上厕所。
    • 4 何时释放锁?
    • 5 Lock的使用
    • 6 利用管道进行线程间通信
    • 7 阻塞队列
    • 8 使用Executors、Executor、ExecutorService、ThreadPoolExecutor
    • 9 并发流程控制
    • 10 并发3定律
    • 11 由并发到并行

    1 什么是并发问题。

    多个进程或线程同时(或着说在同一段时间内)访问同一资源会产生并发问题。

    银行两操作员同时操作同一账户就是典型的例子。比如A、B操作员同时读取一余额为1000元的账户,A操作员为该账户增加100元,B操作员同时为该账户减去 50元,A先提交,B后提交。 最后实际账户余额为1000-50=950元,但本该为 1000+100-50=1050。这就是典型的并发问题。如何解决?可以用锁。

    2 java中synchronized的用法

    1. 用法1
      public class Test{
          public synchronized void print(){
              ....;
          } 
      }
      

      某线程执行print()方法,则该对象将加锁。其它线程将无法执行该对象的所有synchronized块。

    2. 用法2
      public class Test{
          public void print(){
              synchronized(this){//锁住本对象
                  ...;
              }
          }
      }
      

      同用法1, 但更能体现synchronized用法的本质。

    3. 用法3
      public class Test{
          private String a = "test";
          public void print(){
              synchronized(a){//锁住a对象
                  ...;
              }
          }
          public synchronized void t(){
              ...; //这个同步代码块不会因为print()而锁定.
          }
      }
      

      执行print(),会给对象a加锁,注意不是给Test的对象加锁,也就是说 Test对象的其它synchronized方法不会因为print()而被锁。同步代码块执行完,则释放对a的锁。

      为了锁住一个对象的代码块而不影响该对象其它 synchronized块的高性能写法:

      public class Test{
          private byte[] lock = new byte[0];
          public void print(){
              synchronized(lock){
                  ...;
              }
          }
          public synchronized void t(){
              ...; 
          }
      }
      
    4. 静态方法的锁
      public class Test{
          public synchronized static void execute(){
              ...;
          }
      }
      

      效果同

      public class Test{
          public static void execute(){
              synchronized(TestThread.class){
                  ...;
              }
          }
      }
      

    3 Java中的锁与排队上厕所。

    锁就是阻止其它进程或线程进行资源访问的一种方式,即锁住的资源不能被其它请求访问。在JAVA中,sychronized关键字用来对一个对象加锁。比如:

    public class MyStack {
        int idx = 0;
        char [] data = new char[6];
    
        public synchronized void push(char c) {
            data[idx] = c;
            idx++;
        }
    
        public synchronized char pop() {
            idx--;
            return data[idx];
        }
    
        public static void main(String args[]){
            MyStack m = new MyStack();
            /**
               下面对象m被加锁。严格的说是对象m的所有synchronized块被加锁。
               如果存在另一个试图访问m的线程T,那么T无法执行m对象的push和
               pop方法。
            */
            m.pop();//对象m被加锁。
        }
    }
    

    Java的加锁解锁跟多个人排队等一个公共厕位完全一样。第一个人进去后顺手把门从里面锁住,其它人只好排队等。第一个人结束后出来时,门才会打开(解锁)。轮到第二个人进去,同样他又会把门从里面锁住,其它人继续排队等待。

    用厕所理论可以很容易明白: 一个人进了一个厕位,这个厕位就会锁住,但不会导致另一个厕位也被锁住,因为一个人不能同时蹲在两个厕位里。对于Java 就是说:Java中的锁是针对同一个对象的,不是针对class的。看下例:

    MyStatck m1 = new MyStack();
    MyStatck m2 = new Mystatck();
    m1.pop();
    m2.pop();  
    

    m1对象的锁是不会影响m2的锁的,因为它们不是同一个厕位。就是说,假设有 3线程t1,t2,t3操作m1,那么这3个线程只可能在m1上排队等,假设另2个线程 t8,t9在操作m2,那么t8,t9只会在m2上等待。而t2和t8则没有关系,即使m2上的锁释放了,t1,t2,t3可能仍要在m1上排队。原因无它,不是同一个厕位耳。

    Java不能同时对一个代码块加两个锁,这和数据库锁机制不同,数据库可以对一条记录同时加好几种不同的锁,请参见:

    http://hi.baidu.com/dapplehou/blog/item/b341a97744fe6616b151b9a3.html

    4 何时释放锁?

    一般是执行完毕同步代码块(锁住的代码块)后就释放锁,也可以用wait()方式半路上释放锁。wait()方式就好比蹲厕所到一半,突然发现下水道堵住了,不得已必须出来站在一边,好让修下水道师傅(准备执行notify的一个线程)进去疏通马桶,疏通完毕,师傅大喊一声: "已经修好了"(notify),刚才出来的同志听到后就重新排队。注意啊,必须等师傅出来啊,师傅不出来,谁也进不去。也就是说notify后,不是其它线程马上可以进入封锁区域活动了,而是必须还要等notify代码所在的封锁区域执行完毕从而释放锁以后,其它线程才可进入。

    这里是wait与notify代码示例:

    public synchronized char pop() {
        char c;
        while (buffer.size() == 0) {
            try {
                this.wait(); //从厕位里出来
            } catch (InterruptedException e) {
                // ignore it...
            }
        }
        c = ((Character)buffer.remove(buffer.size()-1)).
            charValue();
        return c;
    }
    
    public synchronized void push(char c) {
        this.notify(); //通知那些wait()的线程重新排队。注意:仅仅是通知它们重新排队。
        Character charObj = new Character(c);
        buffer.addElement(charObj);
    }//执行完毕,释放锁。那些排队的线程就可以进来了。
    

    再深入一些。

    由于wait()操作而半路出来的同志没收到notify信号前是不会再排队的,他会在旁边看着这些排队的人(其中修水管师傅也在其中)。注意,修水管的师傅不能插队,也得跟那些上厕所的人一样排队,不是说一个人蹲了一半出来后,修水管师傅就可以突然冒出来然后立刻进去抢修了,他要和原来排队的那帮人公平竞争,因为他也是个普通线程。如果修水管师傅排在后面,则前面的人进去后,发现堵了,就wait,然后出来站到一边,再进去一个,再wait,出来,站到一边,只到师傅进去执行notify. 这样,一会儿功夫,排队的旁边就站了一堆人,等着notify.

    终于,师傅进去,然后notify了,接下来呢?

    1. 有一个wait的人(线程)被通知到。
    2. 为什么被通知到的是他而不是另外一个wait的人?取决于JVM.我们无法预先
       判断出哪一个会被通知到。也就是说,优先级高的不一定被优先唤醒,等待
       时间长的也不一定被优先唤醒,一切不可预知!(当然,如果你了解该JVM的
       实现,则可以预知)。
    3. 他(被通知到的线程)要重新排队。
    4. 他会排在队伍的第一个位置吗?回答是:不一定。他会排最后吗?也不一定。
       但如果该线程优先级设的比较高,那么他排在前面的概率就比较大。
    5. 轮到他重新进入厕位时,他会从上次wait()的地方接着执行,不会重新执行。
       恶心点说就是,他会接着拉巴巴,不会重新拉。
    6. 如果师傅notifyAll(). 则那一堆半途而废出来的人全部重新排队。顺序不可知。
    

    Java DOC 上说,The awakened threads will not be able to proceed until the current thread relinquishes the lock on this object(当前线程释放锁前,唤醒的线程不能去执行)。

    这用厕位理论解释就是显而易见的事。

    5 Lock的使用

    用synchronized关键字可以对资源加锁。用Lock关键字也可以。它是JDK1.5中新增内容。用法如下:

    class BoundedBuffer {
        final Lock lock = new ReentrantLock();
        final Condition notFull  = lock.newCondition(); 
        final Condition notEmpty = lock.newCondition(); 
    
        final Object[] items = new Object[100];
        int putptr, takeptr, count;
    
        public void put(Object x) throws InterruptedException {
            lock.lock();
            try {
                while (count == items.length) 
                    notFull.await();
                items[putptr] = x; 
                if (++putptr == items.length) putptr = 0;
                ++count;
                notEmpty.signal();
            } finally {
                lock.unlock();
            }
        }
    
        public Object take() throws InterruptedException {
            lock.lock();
            try {
                while (count == 0) 
                    notEmpty.await();
                Object x = items[takeptr]; 
                if (++takeptr == items.length) takeptr = 0;
                --count;
                notFull.signal();
                return x;
            } finally {
                lock.unlock();
            }
        } 
    }
    

    (注:这是JavaDoc里的例子,是一个阻塞队列的实现例子。所谓阻塞队列,就是一个队列如果满了或者空了,都会导致线程阻塞等待。Java里的 ArrayBlockingQueue提供了现成的阻塞队列,不需要自己专门再写一个了。)

    一个对象的lock.lock()和lock.unlock()之间的代码将会被锁住。这种方式比起synchronize好在什么地方?简而言之,就是对wait的线程进行了分类。用厕位理论来描述,则是那些蹲了一半而从厕位里出来等待的人原因可能不一样,有的是因为马桶堵了,有的是因为马桶没水了。通知(notify)的时候,就可以喊:因为马桶堵了而等待的过来重新排队(比如马桶堵塞问题被解决了),或者喊,因为马桶没水而等待的过来重新排队(比如马桶没水问题被解决了)。这样可以控制得更精细一些。不像synchronize里的wait和notify,不管是马桶堵塞还是马桶没水都只能喊:刚才等待的过来排队!假如排队的人进来一看,发现原来只是马桶堵塞问题解决了,而自己渴望解决的问题(马桶没水)还没解决,只好再回去等待(wait),白进来转一圈,浪费时间与资源。

    Lock方式与synchronized对应关系:

    Lock await signal signalAll
    synchronized wait notify notifyAll

    注意:不要在Lock方式锁住的块里调用wait、notify、notifyAll

    6 利用管道进行线程间通信

    原理简单。两个线程,一个操作PipedInputStream,一个操作 PipedOutputStream。PipedOutputStream写入的数据先缓存在Buffer中,如果 Buffer满,此线程wait。PipedInputStream读出Buffer中的数据,如果Buffer 没数据,此线程wait。

    jdk1.5中的阻塞队列可实现同样功能。

    1. 例1 这个例子实际上只是单线程,还谈不上线程间通信,但不妨一看。

      http://hi.baidu.com/ecspell/blog/item/7b02d3133ab555005aaf53f5.html

      package io;
      import java.io.*;
      public class PipedStreamTest {
          public static void main(String[] args) {
              PipedOutputStream ps=new PipedOutputStream();
              PipedInputStream pis=new PipedInputStream();
              try{
                  ops.connect(pis);//实现管道连接
                  new Producer(ops).run();
                  new Consumer(pis).run();
              }catch(Exception e){
                  e.printStackTrace();
              }
      
          }
      }
      
      //生产者
      class Producer implements Runnable{
          private PipedOutputStream ops;
          public Producer(PipedOutputStream ops)
          {
              this.ops=ops;
          }
      
          public void run()
          {
              try{
                  ops.write("hell,spell".getBytes());
                  ops.close();
              }catch(Exception e)
                  {e.printStackTrace();}
          }
      }
      
      //消费者
      class Consumer implements Runnable{
          private PipedInputStream pis;
          public Consumer(PipedInputStream pis)
          {
              this.pis=pis;
          }
      
          public void run()
          {
              try{
                  byte[] bu=new byte[100];
                  int len=pis.read(bu);
                  System.out.println(new String(bu,0,len));
                  pis.close();
              }catch(Exception e)
                  {e.printStackTrace();}
          }
      } 
      
    2. 例2 对上面的程序做少许改动就成了两个线程。
      package io;
      import java.io.*;
      public class PipedStreamTest {
          public static void main(String[] args) {
              PipedOutputStream ps=new PipedOutputStream();
              PipedInputStream pis=new PipedInputStream();
              try{
                  ops.connect(pis);//实现管道连接
                  Producer p = new Producer(ops);
                  new Thread(p).start();
                  Consumer c = new Consumer(pis);
                  new Thread(c).start();
              }catch(Exception e){
                  e.printStackTrace();
              }
      
          }
      }
      
      //生产者
      class Producer implements Runnable{
          private PipedOutputStream ops;
          public Producer(PipedOutputStream ops)
          {
              this.ops=ops;
          }
      
          public void run()
          {
              try{
                  for(;;){
                      ops.write("hell,spell".getBytes());
                      ops.close();
                  }
              }catch(Exception e)
                  {e.printStackTrace();}
          }
      }
      
      //消费者
      class Consumer implements Runnable{
          private PipedInputStream pis;
          public Consumer(PipedInputStream pis)
          {
              this.pis=pis;
          }
      
          public void run()
          {
              try{
                  for(;;){
                      byte[] bu=new byte[100];
                      int len=pis.read(bu);
                      System.out.println(new String(bu,0,len));
                  }
                  pis.close();
              }catch(Exception e)
                  {e.printStackTrace();}
          }
      }
      
    3. 例3. 这个例子更加贴进应用
      import java.io.*;
             
      public class PipedIO { //程序运行后将sendFile文件的内容拷贝到receiverFile文件中
          public static void main(String args[]){       
              try{//构造读写的管道流对象       
                  PipedInputStream pis=new PipedInputStream();       
                  PipedOutputStream pos=new PipedOutputStream();       
                  //实现关联       
                  pos.connect(pis);       
                  //构造两个线程,并且启动。           
                  new Sender(pos,"c:\\text2.txt").start();           
                  new Receiver(pis,"c:\\text3.txt").start();         
              }catch(IOException e){       
                  System.out.println("Pipe Error"+ e);       
              }       
          }       
      }       
      //线程发送       
      class Sender extends Thread{           
          PipedOutputStream pos;       
          File file;       
          //构造方法       
          Sender(PipedOutputStream pos, String fileName){       
              this.pos=pos;       
              file=new File(fileName);       
          }          
          //线程运行方法       
          public void run(){          
              try{       
                  //读文件内容       
                  FileInputStream fs=new FileInputStream(file);       
                  int data;       
                  while((data=fs.read())!=-1){       
                      //写入管道始端       
                      pos.write(data);       
                  }       
                  pos.close();                        
              }       
              catch(IOException e) {       
                  System.out.println("Sender Error" +e);       
              }       
          }       
      }
             
      //线程读       
      class Receiver extends Thread{       
          PipedInputStream pis;       
          File file;       
          //构造方法       
          Receiver(PipedInputStream pis, String fileName){         
              this.pis=pis;       
              file=new File(fileName);       
          }          
          //线程运行       
          public void run(){          
              try {       
                  //写文件流对象       
                  FileOutputStream fs=new FileOutputStream(file);       
                  int data;       
                  //从管道末端读       
                  while((data=pis.read())!=-1){
             
                      //写入本地文件       
                      fs.write(data);       
                  }       
                  pis.close();            
              }       
              catch(IOException e){       
                  System.out.println("Receiver Error" +e);       
              }       
          }       
      }
      

    7 阻塞队列

    阻塞队列可以代替管道流方式来实现进水管/排水管模式(生产者/消费者).JDK1.5提供了几个现成的阻塞队列. 现在来看ArrayBlockingQueue的代码如下:

    这里是一个阻塞队列

    BlockingQueue<Object> blockingQ = new ArrayBlockingQueue<Object> 10;
    

    一个线程从队列里取

    for(;;){
        Object o = blockingQ.take();//队列为空,则等待(阻塞)
    }
    

    另一个线程往队列存

    for(;;){
        blockingQ.put(new Object());//队列满,则等待(阻塞)
    }
    

    可见,阻塞队列使用起来比管道简单。

    8 使用Executors、Executor、ExecutorService、ThreadPoolExecutor

    可以使用线程管理任务。还可以使用jdk1.5提供的一组类来更方便的管理任务。从这些类里我们可以体会一种面向任务的思维方式。这些类是:

    1. Executor接口。使用方法:
      Executor executor = anExecutor;//生成一个Executor实例。
      executor.execute(new RunnableTask1());
      

      用意:使用者只关注任务执行,不用操心去关注任务的创建、以及执行细节等这些第三方实现者关心的问题。也就是说,把任务的调用执行和任务的实现解耦。

      实际上,JDK1.5中已经有该接口出色的实现。够用了。

    2. Executors是一个如同Collections一样的工厂类或工具类,用来产生各种不同接口的实例。
    3. ExecutorService接口它继承自Executor. Executor只管把任务扔进 executor()里去执行,剩余的事就不管了。而ExecutorService则不同,它会多做点控制工作。比如:
      class NetworkService {
          private final ServerSocket serverSocket;
          private final ExecutorService pool;
      
          public NetworkService(int port, int poolSize) throws IOException {
              serverSocket = new ServerSocket(port);
              pool = Executors.newFixedThreadPool(poolSize);
          }
       
          public void serve() {
              try {
                  for (;;) {
                      pool.execute(new Handler(serverSocket.accept()));
                  }
              } catch (IOException ex) {
                  pool.shutdown(); //不再执行新任务
              }
          }
      }
      
      class Handler implements Runnable {
          private final Socket socket;
          Handler(Socket socket) { this.socket = socket; }
          public void run() {
              // read and service request
          }
      }
      

      ExecutorService(也就是代码里的pool对象)执行shutdown后,它就不能再执行新任务了,但老任务会继续执行完毕,那些等待执行的任务也不再等待了。

    4. 任务提交者与执行者通讯
      public static void main(String args[])throws Exception {
          ExecutorService executor = Executors.newSingleThreadExecutor();
          Callable<String> task = new Callable<String>(){
              public String call()throws Exception{
                  return "test";
              }
          };
          Future<String> f = executor.submit(task); 
          String result = f.get();//等待(阻塞)返回结果
          System.out.println(result);
          executor.shutdown();                
      }
      

      Executors.newSingleThreadExecutor()取得的Executor实例有以下特性:

      1. 任务顺序执行. 比如:
        executor.submit(task1);
        executor.submit(task2);
        

        必须等task1执行完,task2才能执行。

      2. task1和task2会被放入一个队列里,由一个工作线程来处理。即:一共有2个线程(主线程、处理任务的工作线程)。
    5. 其它的类请参考Java Doc

    9 并发流程控制

    本节例子来自温少的Java并发教程,可能会有改动。向温少致敬。

    1. CountDownLatch 门插销计数器
      1. 启动线程,然后等待线程结束。即常用的主线程等所有子线程结束后再执行的问题。
        public static void main(String[] args)throws Exception {
            // TODO Auto-generated method stub
            final int count=10;
            final CountDownLatch completeLatch = new CountDownLatch(count);//定义了门插销的数目是10
                        
            for(int i=0;i<count;i++){
                Thread thread = new Thread("worker thread"+i){
                        public void run(){
                            //do xxxx                                   
                            completeLatch.countDown();//减少一根门插销
                        }
                    };
                thread.start();
            }           
            completeLatch.await();//如果门插销还没减完则等待。
        } 
        

        JDK1.4时,常用办法是给子线程设置状态,主线程循环检测。易用性和效率都不好。

      2. 启动很多线程,等待通知才能开始
        public static void main(String[] args) throws Exception {
            // TODO Auto-generated method stub
            final CountDownLatch startLatch = new CountDownLatch(1);//定义了一根门插销
        
            for (int i = 0; i < 10; i++) {
                Thread thread = new Thread("worker thread" + i) {
                        public void run() {
                            try {
                                startLatch.await();//如果门插销还没减完则等待
                            } catch (InterruptedException e) {
        
                            }
                            // do xxxx
                        }
                    };
                thread.start();
            }
            startLatch.countDown();//减少一根门插销
        }
        
    2. CycliBarrier. 等所有线程都达到一个起跑线后才能开始继续运行。
      public class CycliBarrierTest implements Runnable {
          private CyclicBarrier barrier;
      
          public CycliBarrierTest(CyclicBarrier barrier) {
              this.barrier = barrier;
          }
      
          public void run() {
              //do xxxx;
              try {
                  this.barrier.await();//线程运行至此会检查是否其它线程都到齐了,没到齐就继续等待。到齐了就执行barrier的run函数体里的内容
              } catch (Exception e) {
      
              }
          }
      
          /**
           * @param args
           */
          public static void main(String[] args) {
              //参数2代表两个线程都达到起跑线才开始一起继续往下执行
              CyclicBarrier barrier = new CyclicBarrier(2, new Runnable() {
                      public void run() {
                          //do xxxx;
                      }
                  });
              Thread t1 = new Thread(new CycliBarrierTest(barrier));         
              Thread t2 = new Thread(new CycliBarrierTest(barrier));
              t1.start();
              t2.start();
          }
      
      }
      

      这简化了传统的用计数器+wait/notifyAll来实现该功能的方式。

    10 并发3定律

    1. Amdahl定律. 给定问题规模,可并行化部分占12%,那么即使把并行运用到极致,系统的性能最多也只能提高1/(1-0.12)=1.136倍。即:并行对提高系统性能有上限。
    2. Gustafson定律. Gustafson定律说Amdahl定律没有考虑随着cpu的增多而有更多的计算能力可被使用。其本质在于更改问题规模从而可以把Amdahl定律中那剩下的88%的串行处理并行化,从而可以突破性能门槛。本质上是一种空间换时间。
    3. Sun-Ni定律. 是前两个定律的进一步推广。其主要思想是计算的速度受限于存储而不是CPU的速度. 所以要充分利用存储空间等计算资源,尽量增大问题规模以产生更好/更精确的解.

    11 由并发到并行

    计算机识别物体需要飞速的计算,以至于芯片发热发烫,而人在识别物体时却一目了然,却并不会导致某个脑细胞被烧热烧焦(夸张)而感到不适,是由于大脑是一个分布式并行运行系统,就像google用一些廉价的linux服务器可以进行庞大复杂的计算一样,大脑内部无数的神经元的独自计算,互相分享成果,从而瞬间完成需要单个cpu万亿次运算才能有的效果。试想,如果在并行处理领域有所创建,将对计算机的发展和未来产生不可估量的影响。当然,其中的挑战也可想而知:许多的问题是并不容易轻易就“分割”的了的。

  • tomcat安装配置

    2012-03-16 18:17:06

    http://wenku.baidu.com/view/f4bc2ed276a20029bd642d38.html 
    tomcat和jboss都是web部署工具
    apache是jboss的上层,主要用于负载均衡,和Url重定向。
    eclipse打war包,右键点工程-》export->web->war file
    然后将打好的包放在WEB-INF目录下。(此WEB-INF所在文件夹与tomcat中配置文件路径相同)
  • findbugs工具安装使用

    2012-03-13 13:02:31

    Findbugs是一个在java程序中查找bug的程序,它查找bug模式的实例,也就是可能出错的代码实例,注意Findbugs是检查java字节码,也就是*.class文件。

    其 实准确的说,它是寻找代码缺陷的,很多我们写的不好的地方,可以优化的地方,它都能检查出来。例如:未关闭的数据库连接,缺少必要的null check,多余的 null check,多余的if后置条件,相同的条件分支,重复的代码块,错误的使用了"==",建议使用StringBuffer代替字符串连加等等。而且我们 还可以自己配置检查规则(做哪些检查,不做哪些检查),也可以自己来实现独有的校验规则(用户自定义特定的bug模式需要继承它的接口,编写自己的校验 类,属于高级技巧)。



    参照:http://wenku.baidu.com/view/989c7dc75fbfc77da269b176.html
    http://qa.taobao.com/?p=14798
    fingbugs发现bug种类:

    Findbugs是一个静态分析工具,它检查类或者JAR 文件,将字节码与一组缺陷模式进行对比以发现可能的问题。Findbugs自带检测器,其中有60余种Bad practice,80余种Correctness,1种 Internationalization,12种Malicious code vulnerability,27种Multithreaded correctness,23种Performance,43种Dodgy。

    Bad practice 坏的实践

    一些不好的实践,下面列举几个:

    HE 类定义了equals(),却没有hashCode();或类定义了equals(),却使用

    Object.hashCode();或类定义了hashCode(),却没有equals();或类定义了hashCode(),却使用Object.equals();类继承了equals(),却使用Object.hashCode()。

    SQLStatement 的execute方法调用了非常量的字符串;或Prepared Statement是由一个非常量的字符串产生。

    DE 方法终止或不处理异常,一般情况下,异常应该被处理或报告,或被方法抛出。

    Correctness 一般的正确性问题

    可能导致错误的代码,下面列举几个:

    NP 空指针被引用;在方法的异常路径里,空指针被引用;方法没有检查参数是否null;null值产生并被引用;null值产生并在方法的异常路径被引用;传给方法一个声明为@NonNull的null参数;方法的返回值声明为@NonNull实际是null。

    Nm 类定义了hashcode()方法,但实际上并未覆盖父类Object的hashCode();类定义了tostring()方法,但实际上并未覆盖父类Object的toString();很明显的方法和构造器混淆;方法名容易混淆。

    SQL方法尝试访问一个Prepared Statement的0索引;方法尝试访问一个ResultSet的0索引。

    UwF所有的write都把属性置成null,这样所有的读取都是null,这样这个属性是否有必要存在;或属性从没有被write。

    Internationalization 国际化

    当对字符串使用upper或lowercase方法,如果是国际的字符串,可能会不恰当的转换。

    Malicious code vulnerability 可能受到的恶意攻击

    如果代码公开,可能受到恶意攻击的代码,下面列举几个:

    FI 一个类的finalize()应该是protected,而不是public的。

    MS属性是可变的数组;属性是可变的Hashtable;属性应该是package protected的。

    Multithreaded correctness 多线程的正确性

    多线程编程时,可能导致错误的代码,下面列举几个:

    ESync空的同步块,很难被正确使用。

    MWN错误使用notify(),可能导致IllegalMonitorStateException异常;或错误的

    使用wait()。

    No 使用notify()而不是notifyAll(),只是唤醒一个线程而不是所有等待的线程。

    SC 构造器调用了Thread.start(),当该类被继承可能会导致错误。

    Performance 性能问题

    可能导致性能不佳的代码,下面列举几个:

    DM方法调用了低效的Boolean的构造器,而应该用Boolean.valueOf(…);用类似

    Integer.toString(1) 代替new Integer(1).toString();方法调用了低效的float的构造器,应该用静态的valueOf方法。

    SIC如果一个内部类想在更广泛的地方被引用,它应该声明为static。

    SS 如果一个实例属性不被读取,考虑声明为static。

    UrF如果一个属性从没有被read,考虑从类中去掉。

    UuF如果一个属性从没有被使用,考虑从类中去掉。

    Dodgy 危险的

    具有潜在危险的代码,可能运行期产生错误,下面列举几个:

    CI 类声明为final但声明了protected的属性。

    DLS对一个本地变量赋值,但却没有读取该本地变量;本地变量赋值成null,却没有读取该本地变量。

    ICAST 整型数字相乘结果转化为长整型数字,应该将整型先转化为长整型数字再相乘。

    INT没必要的整型数字比较,如X <= Integer.MAX_VALUE。

    NP 对readline()的直接引用,而没有判断是否null;对方法调用的直接引用,而方法可能返回null。

    REC直接捕获Exception,而实际上可能是RuntimeException。

    ST 从实例方法里直接修改类变量,即static属性。

    总结

    此插件的功能很不错,可以帮助我们提升Java代码的编写能力,写出更加安全可靠的代码。建议使用或加在Ant里进行持续构建。

  • Mysql常用命令

    2012-03-09 15:42:10

    从连接MYSQL、修改密码、增加用户等方面来学习一些MYSQL的常用命令。 
    一、连接MYSQL。 
    格式: mysql -h主机地址 -u用户名 -p用户密码 
    1、例1:连接到本机上的MYSQL。 
    首先在打开DOS窗口,然后进入目录 mysqlbin,再键入命令mysql -uroot -p,回车后提示你输密码,如果刚安装好MYSQL,超级用户root是没有密码的,故直接回车即可进入到MYSQL中了,MYSQL的提示符是:mysql> 
    2、例2:连接到远程主机上的MYSQL。假设远程主机的IP为:110.110.110.110,用户名为root,密码为abcd123。则键入以下命令: 
    mysql -h110.110.110.110 -uroot -pabcd123 
    (注:u与root可以不用加空格,其它也一样) 
    3、退出MYSQL命令: exit (回车) 
    二、修改密码。 
    格式:mysqladmin -u用户名 -p旧密码 password 新密码 
    1、例1:给root加个密码ab12。首先在DOS下进入目录mysqlbin,然后键入以下命令 
    mysqladmin -uroot -password ab12 
    注:因为开始时root没有密码,所以-p旧密码一项就可以省略了。 
    2、例2:再将root的密码改为djg345。 
    mysqladmin -uroot -pab12 password djg345 
    三、增加新用户。(注意:和上面不同,下面的因为是MYSQL环境中的命令,所以后面都带一个分号作为命令结束符) 
    格式:grant select on 数据库.* to 用户名@登录主机 identified by "密码" 
    例1、增加一个用户test1密码为abc,让他可以在任何主机上登录,并对所有数据库有查询、插入、修改、删除的权限。首先用以root用户连入MYSQL,然后键入以下命令: 
    grant select,insert,update,delete on *.* to test1@"%" Identified by "abc"; 
    但例1增加的用户是十分危险的,你想如某个人知道test1的密码,那么他就可以在internet上的任何一台电脑上登录你的mysql数据库并对你的数据可以为所欲为了,解决办法见例2。 
    例2、增加一个用户test2密码为abc,让他只可以在localhost上登录,并可以对数据库mydb进行查询、插入、修改、删除的操作(localhost指本地主机,即MYSQL数据库所在的那台主机),这样用户即使用知道test2的密码,他也无法从internet上直接访问数据库,只能通过MYSQL主机上的web页来访问了。 
    grant select,insert,update,delete on mydb.* to test2@localhost identified by "abc"; 
    如果你不想test2有密码,可以再打一个命令将密码消掉。 
    grant select,insert,update,delete on mydb.* to test2@localhost identified by ""; 


    (下篇) 
    在上篇我们讲了登录、增加用户、密码更改等问题。下篇我们来看看MYSQL中有关数据库方面的操作。注意:你必须首先登录到MYSQL中,以下操作都是在MYSQL的提示符下进行的,而且每个命令以分号结束。 

    一、操作技巧 
    1、如果你打命令时,回车后发现忘记加分号,你无须重打一遍命令,只要打个分号回车就可以了。也就是说你可以把一个完整的命令分成几行来打,完后用分号作结束标志就OK。 
    2、你可以使用光标上下键调出以前的命令。但以前我用过的一个MYSQL旧版本不支持。我现在用的是mysql-3.23.27-beta-win。 

    二、显示命令 
    1、显示数据库列表。 
    show databases; 
    刚开始时才两个数据库:mysql和test。mysql库很重要它里面有MYSQL的系统信息,我们改密码和新增用户,实际上就是用这个库进行操作。 
    2、显示库中的数据表: 
    use mysql; //打开库,学过FOXBASE的一定不会陌生吧 
    show tables; 
    3、显示数据表的结构: 
    describe 表名; 
    4、建库: 
    create database 库名; 
    5、建表: 
    use 库名; 
    create table 表名 (字段设定列表); 
    6、删库和删表: 
    drop database 库名; 
    drop table 表名; 
    7、将表中记录清空: 
    delete from 表名; 
    8、显示表中的记录: 
    select * from 表名; 

    三、一个建库和建表以及插入数据的实例 
    drop database if exists school; //如果存在SCHOOL则删除 
    create database school; //建立库SCHOOL 
    use school; //打开库SCHOOL 
    create table teacher //建立表TEACHER 

    id int(3) auto_increment not null primary key, 
    name char(10) not null, 
    address varchar(50) default '深圳', 
    year date 
    ); //建表结束 
    //以下为插入字段 
    insert into teacher values('','glchengang','深圳一中','1976-10-10'); 
    insert into teacher values('','jack','深圳一中','1975-12-23'); 

    注:在建表中(1)将ID设为长度为3的数字字段:int(3)并让它每个记录自动加一:auto_increment并不能为空:not null而且让他成为主字段primary key(2)将NAME设为长度为10的字符字段(3)将ADDRESS设为长度50的字符字段,而且缺省值为深圳。varchar和char有什么区别呢,只有等以后的文章再说了。 (4)将YEAR设为日期字段。 
    如果你在mysql提示符键入上面的命令也可以,但不方便调试。你可以将以上命令原样写入一个文本文件中假设为school.sql,然后复制到c:\下,并在DOS状态进入目录\mysql\bin,然后键入以下命令: 
    mysql -uroot -p密码 < c:\school.sql 
    如果成功,空出一行无任何显示;如有错误,会有提示。(以上命令已经调试,你只要将//的注释去掉即可使用)。 

    四、将文本数据转到数据库中 
    1、文本数据应符合的格式:字段数据之间用tab键隔开,null值用\n来代替. 
    例: 
    3 rose 深圳二中 1976-10-10 
    4 mike 深圳一中 1975-12-23 
    2、数据传入命令 load data local infile "文件名" into table 表名; 
    注意:你最好将文件复制到\mysql\bin目录下,并且要先用use命令打表所在的库 。 

    五、备份数据库:(命令在DOS的\mysql\bin目录下执行) 
    mysqldump --opt school>school.bbb 
    注释:将数据库school备份到school.bbb文件,school.bbb是一个文本文件,文件名任取,打开看看你会有新发现。 

    后记:其实MYSQL的对数据库的操作与其它的SQL类数据库大同小异,您最好找本将SQL的书看看。我在这里只介绍一些基本的,其实我也就只懂这些了,呵呵。最好的MYSQL教程还是“晏子“译的“MYSQL中文参考手册“不仅免费每个相关网站都有下载,而且它是最权威的。可惜不是象"PHP4中文手册"那样是chm的格式,在查找函数命令的时候不太方便。
  • spring入门程序一

    2012-03-08 19:31:48

    1、下载spring,解压。

    2、eclipse新建普通工程,对工程添加external jar包,在spring解压后的dist/module目录下和lib目录下

    3、在src下新建包,建立HelloBean.java

    package com.springinaction.chapter01.hello;

    public class HelloBean {

     private String hello;

     public String getHello() {
      return hello;
     }

     public void setHello(String hello) {
      this.hello = hello;
     }
     
     
    }

    4、建立beans.xml,注意beans.xml一定要放在工程的根目录下:

    <?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="helloBean" class="com.springinaction.chapter01.hello.HelloBean">
    <property name="hello">
    <value>"hello mm"</value>
    </property>


    </bean>
    </beans>

    4、建立测试类:HelloBeanTest.java

    package com.springinaction.chapter01.hello;


    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.FileSystemXmlApplicationContext;


    public class HelloBeanTest {
    public static void main(String[] args){
     
       ApplicationContext context = new FileSystemXmlApplicationContext("/beans.xml");
       HelloBean hellot = (HelloBean)context.getBean("helloBean");
       System.out.println(hellot.getHello());
      
    }
    }

    运行即可看输出结果:

    hello mm

  • 会话跟踪-Servlet学习笔记六

    2012-02-26 20:57:34

    上篇说到,HTTP协议是无状态协议,因此,要跟踪用户的状态非常困难,常用的对用户信息的跟踪就是购物网站的“购物车”跟踪。通过cookie我们可以实现这个功能,将信息保存到客户端,在需要的地方取出来,但这个功能有一些缺陷,比如用户选择了比较多的产品,就需要产生多个cookie来保存;因为cookie只能保存String类型的数据,而我们可能有时候需要保存对象,这就无法通过cookie来实现了。所以采用一种基于cookie的技术,session(会话)技术。它的基本思想是,给每个客户端分配一个不重复的ID,而将实际需要保存的数据放在服务器内存中,因为每个客户端cookie中只需要保存一个ID,而将实际需要保存的数据放在服务器内存中,因为不需要写到客户端文本,所以在服务器中可以存放任何的对象而不单纯是String数组。
        用户可能关闭浏览器的cookie功能,这样基于cookie的session技术就不能使用了。但是Sun做出了另外一种替代,就是URL重写,简单而言,就是将本来保存在Cookie中的用于标识不同客户端的session ID放在URL后面,如:http://some-site.com/servlet/shop.CartServlet;jessionid=1234567890,这样即使用户关闭了浏览器的cookie功能,session还是能正常使用。一般支持J2EE的WEB服务器会自动根据情况在cookie和URL重写之间进行切换,不需要我们干预。
  • 处理Cookie-Servlet学习笔记五

    2012-02-26 18:34:21

    HTTP连接是一个无状态的连接,这就意味着,当浏览器得到它所请求的资源以后,浏览器和服务器端就失去了联系。浏览器无法知道服务器的状态,而服务器也无法知道浏览器的状态。即使有任何一方被关闭了,另外一方都无从知晓。而当下一次浏览器再次访问服务器的时候,服务器只是将它当做一个新的连接,它也无法知晓这个浏览器是否曾经访问过。通过Cookie,可以让服务器和浏览器建立一种联系。
    Cookie是一小段文本,通过CGI/ASP/JSP/Servlet等程序,可以将Cookie保存在浏览器所在的客户端的内存或磁盘上,而通过这些应用程序,也可以从客户端读出这些Cookie。也就是说,Cookie是一种可以让服务器端的连接在客户端保存和获取信息的机制。
        通过Cookie,在需要登录的网站,用户在第一次输入用户名和密码后,将用户名和密码利用cookie保存在客户端,当用户下一次访问这个网站时,可以直接从客户端独处用户名和密码来,这样就免得用户每次都需要重新登录。另外,用户可以选择自己喜欢的新闻、显示的风格、显示的顺序等,我们可以讲用户的设置保存到客户端的cookie中,这样用户每次访问这个网站的时候,都可以按照他预设的显示。因为需要将信息保存在客户端的机器上,所以安全问题一直是关注焦点。Cookie的安全机制:cookie不会以任何方式在客户端被执行,浏览器一般会限制来自同一网站的cookie数目,单个cookie长度也做了限制,这样客户端就不必担心硬盘被这些cookie所撑爆了。对于一些重要个人资料,在保存这些信息时提示用户不显示在公共计算机上。另外,因为浏览器可以设置成拒绝cookie,所以通常不要将程序设计成对cookie有很大的依赖性。
    在Servlet中处理Cookie
    Cookie是封装在Cookie类的,如果要向客户端发送一个cookie,首先需要创建一个Cookie的实例,可以通过cookie的构造器来创建,它接收两个String类型参数,用于指定cookie的属性名称和属性值。此外,也可以使用setValue()来改变它的属性值。
    在创建好cookie对象后,可以用HttpServletResponse的addCookie()方法来将cookie发送给客户端。在cookie发送到客户端以后,可以使用HttpServletRequest的getCookie()方法从客户端获得这个网站的所有的cookie,它返回包含所有本站Cookie的数组,然后通过遍历这个数组就可以获得对应的Cookie。默认情况下,cookiea在客户端是保存在内存中的,如果浏览器关闭,那么cookie也就失效了。如果想要让cookie长久地保存在磁盘上,需要让cookie的生命周期变得更长,可以通过对象cookie的setMaxAge()来修改它的生命周期,这个方法接收一个以秒为单位的证书,表示这个cookie可以在客户端保存的时间。如果将它设置成0,则表示在客户端删除这个会话。
    getMaxAge()/setMaxAge()读取/设置cookie的过期时间,如果设置了一个负值,表示这个cookie在用户退出浏览器后马上过期,如果设置成0表示删除此cookie。
    getValue()/setValue()读取/设置cookie属性值
    getComment()/setComment读取/设置注释
  • 处理HTTP报头-Servlet学习笔记四

    2012-02-26 17:51:06

    HTTP请求建立在请求(request)和相应(response)的基础之上。在客户端浏览器向服务器发送请求的时候,除了用户输入的表单数据或者查询数据之外,通常浏览器还会自己在GET/POST请求行后面加上一些附加的信息;而服务器向客户端的请求作出响应的时候,也会自动向客户端发送一些附加的信息。通常我们将这些信息称为HTTP报头,将附加在请求信息后面的称为HTTP请求报头,将附加在响应后的信息称为HTTP响应报头。通过Servlet,我们可以获得请求报头信息,或者可以通过Servlet来设置响应报头信息。
    1、在Servlet中取得HTTP请求报头
    HttpServletRequest的getHeader()方法来获得对应的HTTP请求报头,只需要给这个方法传递一个HTTP报头的String参数就可以了。
    HTTP请求报头以及各自含义:
    Accept:浏览器可接受的MIME类型。 Servlet检查Accept来确定使用哪种格式返回给客户端资源。     IE6和7 bug:重新载入页面发送的Accept报头不正确,但在最初的请求中是正确的。  

                Accept-Charset:浏览器可接受的字符集。   

                Accept-Encoding:浏览器能够进行解码的数据编码方式,比如gzip。 Servlet能够向支持gzip的浏览器返回经gzip编码的HTML页面。许多情形下这可以减少5到10倍的下载时间。      在使用任何类型的内容编码之前,一定要检查Accept-Dncoding报头。   

               Accept-Language:浏览器所希望的语言种类,当服务器能够提供一种以上的语言版本时要用到。   

                Authorization:授权信息,通常出现在对服务器发送的WWW-Authenticate头的应答中。

               Connection:表示是否需要持久连接。   如果Servlet看到这里的值为“Keep-Alive”,或者看到请求使用的是HTTP 1.1(HTTP 1.1默认进行持久连接),  它就可以利用持久连接的优点,当页面包含多个元素时(例如Applet,图片),显著地减少下载所需要的时间。    要 实现这一点,Servlet需要在应答中发送一个Content-Length头,  最简单的实现方法是:先把内容写入 ByteArrayOutputStream,然后在正式写出内容之前计算它的大小。   

               Content-Length:表示请求消息正文的长度。    

               Cookie:这是最重要的请求头信息之一。    

               From:请求发送者的email地址,由一些特殊的Web客户程序使用,浏览器不会用到它。 

                Host:初始URL中的主机和端口。    

               If-Modified-Since:只有当所请求的内容在指定的日期之后又经过修改才返回它,否则返回304“Not Modified”应答。    

               Pragma:指定“no-cache”值表示服务器必须返回一个刷新后的文档,即使它是代理服务器而且已经有了页面的本地拷贝。    

               Referer:包含一个URL,用户从该URL代表的页面出发访问当前请求的页面。    

               User-Agent:浏览器类型,如果Servlet返回的内容与浏览器类型有关则该值非常有用。      在不要的时候才使用User-Agent。      在使用的时候要检查是否为null。      区分Netscape和IE 要检查MSIE 而非"Mozilla"。      这个报头可以加造而servlet并不能区分这种情况。 

               UA-Pixels,UA-Color,UA-OS,UA-CPU:由某些版本的IE浏览器所发送的非标准的请求头,表示屏幕大小、颜色深度、操作系统和CPU类型。        有关HTTP头完整、详细的说明,请参见http://www.w3.org/Protocols/的HTTP规范。  

    取得请求头信息的方法有:

    GetHeader()

    getHeaderNames()

    2、在Servlet中设置HTTP响应头

    响应头信息:

    Content-Encoding:页面在传输过程中的编码方式

    Content-Type:设置Servlet输出的MIME类型

    Content-Language:表明页面所使用的语言,如en\en-us等

    Expires:标明页面的过期时间,可以使用这个来在指定的时间内取消页面缓存

    Refresh:表明浏览器自动重新调用最新的页面

    设置HTTP响应头的方法:

    setHeader(String headerName,String headerValue)

    setContentType(String mime)

    setContentLength(int length)

    addCookie(Cookie c)

    sendRedirect(String url)设置Servlet跳转到指定的url

    3、在Servlet中跳转到其他的页面

    假设Servlet处理用户登录的情况,如果为合法用户,跳转到A页面,否则跳转到B页面。在Servlet中,可以使用HttpServletResponse的sendRedirect()方法实现跳转这个功能,这个方法接收一个参数,用于指定跳转的URL。当调用sendRedirect()时,WEB服务器将会向浏览器返回响应,指示浏览器请求新的URL。因为浏览器会重新提出新的请求,所以在跳转之前所存储的所有的请求属性都会被清空。

    ....

    public class Redirect extends HttpServlet{

    public void doGet(HttpSevletRequest request,HttpServletResponse response){

    if(request.getParameter("Dir").equals("login"))

    response.sendRedirect("../login.htm");

    else

    response.sendRedirect("chop03.img");

    }

    ...

    除了使用sendRedirect()方法实现跳转之外,还可以使用RequestDispatcher对象的forward()方法来实现类似的功能,这个方法需要接收两个参数:HttpServletRequest和HttpServletResponse,并且将封装在其中的属性传递到跳转的页面。

  • 利用Servlet读取HTML表单数据-Servlet学习笔记三

    2012-02-25 17:26:14

    一、客户端传递数据的方式
    客户端往web服务器传递数据,通常有两种方式:使用表单或者将数据附在URL后面。这些数据传递给Web服务器中的程序后,通常需要将它们提取出来,然后对这些数据进行相应的处理。
    二、在Servlet中读取客户端发送的数据
    <FORM>标记是接收客户端的输入,讲用户输入的数据提交到<FORM>标记的<Action>熟悉。通常,无论是用GET还是POST方式提交表单,都是将表单中的各个元素值按照“名-值”对的形式传递到服务器端的。因此,我们可以在服务端的程序中,通过分析“名-值”对系列来获得我们需要的数据。一般在Servlet中,我们可以使用Servlet内建机制,达到这个目的。
        在Servlet中,根据客户端的请求方式,会调用doGet()或doPost()方法来处理,并且这两个方法有相同的参数:HttpServletRequest和HttpServletResponse。HttpServletRequest包含很多客户端的请求信息,而表单数据就包含在其中。通过HttpServletRequest对象上的getParameter()方法,并且给这个方法一个字符串参数,用于指定需要获取的参数的名字,就可以得到对应参数名的值。在这里要特别注意,getParameter()方法的参数必须和表单元素的名称一致,并且严格区分大小写。getParameter()方法已经封装好哦了如何分析并获得客户端传递的这些表单数据的过程,我们直接使用这个方法就可以得到和客户端输入一致的数据。
    getPersonData.html

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
    <html>
      <head>
        <title>getPersonData.html</title>
       
        <meta. http-equiv="keywords" content="keyword1,keyword2,keyword3">
        <meta. http-equiv="description" content="this is my page">
        <meta. http-equiv="content-type" content="text/html; charset=UTF-8">
       
        <!--<link rel="stylesheet" type="text/css" href="./styles.css">-->

      </head>
     
      <body>
        <form. name="test" method="post" action="chop01.GetPersonData">
        用户名:<input type="text" name="UserName"/>
        <br/>
       密码:<input type="password" name="Pwd"/>
       <br/>
       <input type="submit" value="提交" >
      
       
        </form>
    </body>
    </html>

    GetPersonData.java
    package chop01;

    import java.io.IOException;
    import java.io.*;

    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;

    public class GetPersonData extends HttpServlet{
         


        public void service(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException{
            String username;
            String password;
            //读取“姓名”
            username = request.getParameter("UserName");
            //读取“密码”
            password = request.getParameter("Pwd");
            PrintWriter ut = response.getWriter();
            out.println("UserNamme:"+username);
            out.println("Password:"+password);
           
        }   
    }





  • Servlet的运行机制和生命周期-Servlet学习笔记二

    2012-02-25 15:10:45

    一、Servlet的运行机制
    当浏览器发送给服务器一个Servlet的请求时,如果这个Servlet是第一次被调用,那么服务器将会自动创建一个Servlet实例,并运行它;而如果这个Servlet已经被实例化,那么服务器只是会新启动一个线程来运行它。所以,多个线程有可能会去访问共享的全局变量,因此,在使用这些全局变量时,一定要特别小心,让这些线程不会访问到不同步的数据。比如,可以使用synchronized关键字来保护共享的对象。
    二、HttpServlet的方法
    在编写Servlet程序时,都是让它继承HttpServlet这个类,然后根据需要去覆盖HttpServlet中的方法。比较常用的方法有:init()、doGet()、doPost()、destroy().
    如果不能确定客户端的请求方式到底是GET还是POST方式,那么可以在Servlet中同时定义这两个方法,如下方式让另一个来调用其中一个:
    public void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException{
            //处理doGet请求
    }
    public void doPost(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException{
            doGet(request,reponse);
    }
    init()方法
    对Servlet做一些初始化工作,这个方法只会被调用一次,不会对每次连接都调用
    service()方法
    如果客户端有一个对Servlet的请求发送过来,那么服务器端会产生一个新的线程,并且让它调用Servlet的service()方法。service()方法根据收到的客户端请求类型,决定调doGet()还是doPost()还是其他的doXXX()方法。
    destroy()方法
    如果要删除某个Servlet实例,那么在删除之前服务器会先调用destroy()方法。可以在这个方法中执行一些清理动作,比如释放数据库连接,关闭打开的文件等。
    三、Servlet的生命周期
    即如上的几个方法的结合。
  • servlet学习——笔记1

    2012-02-25 14:50:23

    一、先介绍下Servlet
    Servlet是运行在Web服务器或者其他应用服务器(Application Server)上的一种特殊的Java程序。首先塔和其他的Java程序一样编写、编译,它的特殊特点在于Servlet必须继承Servlet类或者是它的子类,通常是HttpServlet这个类。
    二、编写入门Servlet程序
    在MyEclipse创建web project,工程名为ChopLearn,会自动生成web.xml文件。
    在src下编写class文件:HelloWorld.java
    package chop01;

    import java.io.IOException;
    import java.io.PrintWriter;
    import java.util.Date;

    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;

    public class HelloWorld extends HttpServlet{

        public void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException{
           
            PrintWriter ut = response.getWriter();
            out.println("how are you");
            out.println(new Date());
               
        }   
    }
    然后在web.xml里配置Servlet映射
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app version="2.5"
        xmlns="http://java.sun.com/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
        http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
      <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
      </welcome-file-list>
      <servlet>
      <servlet-name>firstServlet</servlet-name>
      <servlet-class>chop01.HelloWorld</servlet-class>
      </servlet>
     
      <servlet-mapping>
      <servlet-name>firstServlet</servlet-name>
      <url-pattern>/helloServlet</url-pattern>
      </servlet-mapping>

    </web-app>
    配置完成后,启动tomcat,打开浏览器,输入:
    http://xxx.xxx.2.100:8080/ChopLearn/helloServlet
    tomcat接到/helloServlet请求后会在web.xml中寻找/helloServlet对应的servlet-name,这里为firstServlet,然后根据firstServlet找到对应的servlet类chop01.HelloWorld。
    显示出结果如下:
    how are you Sat Feb 25 14:48:55 CST 2012
    当浏览器每次连接这个Servlet程序时,会将当前服务器上的时间发送给浏览器。



  • 使用javaScript进行表单验证-学习笔记二

    2012-02-25 12:39:59

    一、表单的定义
    FORM表单可以从客户端向服务器端发送数据,在服务器端,可以使用asp、jsp、servlet等程序将传递过来的数据读取出来进行处理。FORM定义基本格式是:
    <FORM. ACTION="" METHOD="" NAME="" ENCTYPE="">
    ...
    </FROM>
    ACTION指定表单提交的目标URL。如果为空,则提交给此表单自身的URL
    METHOD指定提交表单数据的方式。有两种:GET和POST
    如果为空,则使用GET方式提交数据。
    GET和POST的不同:
    1、GET数据是URL的一部分,所以它会将表单数据附在URL后面发送。也就是说,浏览器的地址栏将会显示表单中的数据,并且通常情况下浏览器会将这个附加数据后的URL保存起来,可以通过浏览器历史记录来得到。所以这种方式不适合发送需要保密的数据的表单,如密码等。而POST不是URL的一部分,它不会将表单数据附在URL后面,所以不会有上述问题。
    2、浏览器通常会限制URL长度,所以使用GET方式无法传送大量的数据,而POST方式不会有这种问题。所以,最好使用POST方式来传送表单数据。
    二、使用JavaScript进行表单验证
    使用javaScript可以在客户端对表单进行验证。假设有个登录表单,有用户名和密码。要求用户名和密码必须填写,否则不能登录。如果在服务器端来验证,实现上当然行得通,但是这种方式有一个弊端,如果用户没有输入用户名或密码,那他只有在提交到服务器端进行处理以后才可用得到错误信息,这无形中加重了服务的负担,也浪费了带宽。所以通常情况下,我们会将一部分不涉及到数据库的验证放到客户端来完成。
      <body>
        <form. name="test" method="post" action="login.jsp">
        用户名:<input type="text" name="UserName"/>
        <br/>
       密码:<input type="password" name="Pwd"/>
       <br/>
       <input type="button" value="提交" nClick="JavaScript.:checkForm();">
      
       
        </form>

     

    <Script. language="javaScript">
    function checkForm()
    {
       //定义一个变量,应用表单对象
       var theForm. = document.TEST;
       if(theForm.UserName.value=="")
       {
       alert("请输入用户名!");
       theForm.UserName.focus();
       }
     else if(theForm.Pwd.value=="")
     {
     alert("请输入密码!");
     theForm.Pwd.focus();
     }
     else
     {
     theForm.submit();
     }
     }
    </Script>
    </body>
    </html>
  • html和javascript

    2012-02-25 12:06:25

    一、先说下html,html有一系列标签,它展示的页面是静态页面。
    如:
    <title>
    ...
    </title>
     <body bgcolor="pink">
        <form. >
        <tr>用户名</tr> <input type="text" name="username"  size="5"/>
        <br/>
        <tr>密      码</tr><input type="password" name="passwd" size="5"/>
        <br/>
        <input type="button" name="login" value="登录">
       <a href="MyHtml.html">此处看美女</a>
       
        </form>
        <input type="file" name="浏览" size="5"/> 
      </body>
    展示的样式如下:


    二、JavaScript
    javaScript是一种脚本语言 它可以嵌入到html中。
    java是一种面向对象的语言,而javaScript是基于对象(以及事件)的。JavaScript主要作用是用在浏览器端,用于产生一些动态效果或者用于对Html表单进行客户端的验证等。
    <script>和</script>之间为javaScript脚本。
    除了变量、流程控制、循环、函数定义外,还有内置对象。
    如window对象、document对象,form对象,
    window对象有三个重要的方法,alert用于显示警告信息,confirm用于显示确认信息,prompt用于询问,

    confirm用于显示确认信息
    查看(929) 评论(0) 收藏 分享 管理

  • java操作properties文件

    2012-02-23 16:19:14

    JAVA操作properties文件

    java中的properties文件是一种配置文件,主要用于表达配置信息,文件类型为*.properties,格式为文本文件,文件的内容是格式是"键=值"的格式,在properties

    文件中,可以用"#"来作注释,properties文件在Java编程中用到的地方很多,操作很方便。
    一、properties文件

    test.properties
    ------------------------------------------------------
    #################################
    #   工商报表应用IcisReport的配置文件#
    #   日期:2006年11月21日 #
    #################################
    #
    #   说明:业务系统TopIcis和报表系统IcisReport是分离的
    #   可分开部署到不同的服务器上,也可以部署到同一个服务
    #   器上;IcisReprot作为独立的web应用程序可以使用任何
    #   的Servlet容器或者J2EE服务器部署并单独运行,也可以
    #   通过业务系统的接口调用作为业务系统的一个库来应用.
    #
    #   IcisReport的ip
    IcisReport.server.ip=192.168.3.143
    #   IcisReport的端口
    IcisReport.server.port=8080
    #   IcisReport的上下文路径
    IcisReport.contextPath=/IcisReport

    ------------------------------------------------------ 
    Properties类的重要方法
    Properties 类存在于胞 Java.util 中,该类继承自 Hashtable
    1. getProperty ( String  key) ,   用指定的键在此属性列表中搜索属性。也就是通过参数 key ,得到 key 所对应的 value。
    2. load ( InputStream  inStream) ,从输入流中读取属性列表(键和元素对)。通过对指定的文件(比如说上面的 test.properties 文件)进行装载来获取该文

    件中的所有键 - 值对。以供 getProperty ( String  key) 来搜索。
    3. setProperty ( String  key, String  value) ,调用 Hashtable 的方法 put 。他通过调用基类的put方法来设置 键 - 值对。 
    4. store ( OutputStream  out, String  comments) ,   以适合使用 load 方法加载到 Properties 表中的格式,将此 Properties 表中的属性列表(键和元素

    对)写入输出流。与 load 方法相反,该方法将键 - 值对写入到指定的文件中去。
    5. clear () ,清除所有装载的 键 - 值对。该方法在基类中提供。
    -------------------------------

    二、操作properties文件的java方法 

    读属性文件
    Properties prop = new Properties();
    InputStream in = getClass().getResourceAsStream("/IcisReport.properties");
    prop.load(in);
    Set keyValue = prop.keySet();
    for (Iterator it = keyValue.iterator(); it.hasNext();)
    {
    String key = (String) it.next();
    }
    ------------------------
    utputFile = new FileOutputStream(fileName);
    propertie.store(outputFile, description);
    outputFile.close();
    -----------------------------------------------------------------------------------------
    Class.getResourceAsStream ("/some/pkg/resource.properties");
    ClassLoader.getResourceAsStream ("some/pkg/resource.properties");
    java.util.ResourceBundle rs = java.util.ResourceBundle.getBundle("some.pkg.resource");
    rs.getString("xiaofei");
    -----------------------------------------------------------------------------------------
    写属性文件
    Configuration saveCf = new Configuration();
    saveCf.setValue("min", "10");
    saveCf.setValue("max", "1000");
    saveCf.saveFile(".\config\save.perperties","test");

    总结:javaproperties文件需要放到classpath下面,这样程序才能读取到,有关classpath实际上就是java类或者库的存放路径,在java工程中,properties放到

    class文件一块。在web应用中,最简单的方法是放到web应用的WEB- INF\classes目录下即可,也可以放在其他文件夹下面,这时候需要在设置classpath环境变量的

    时候,将这个文件夹路径加到 classpath变量中,这样也也可以读取到。在此,你需要对classpath有个深刻理解,classpath绝非系统中刻意设定的那个系统环境变

    量,WEB-INF\classes其实也是,java工程的class文件目录也是。

    原文:http://www.cnblogs.com/panjun-Donet/archive/2009/07/17/1525597.html


  • 单元测试和测试驱动开发(TDD)杂谈

    2012-02-23 13:06:34

    软件测试中单元测试和测试驱动开发TDD)杂谈

    在一种传统的结构化编程语言中,比如C,要进行测试的单元一般是函数或子过程。在象C++这样的面向对象的语言中, 要进行测试的基本单元是类。对Ada语言来说,开发人员可以选择是在独立的过程和函数,还是在Ada包的级别上进行单元测试。单元测试的原则同样被扩展到第四代语言(4GL)的开发中,在这里基本单元被典型地划分为一个菜单或显示界面。   经常与单元测试联系起来的另外一些开发活动包括代码走读(Code review),静态分析(Static analysis)和动态分析(Dynamic analysis)。静态分析就是对软件的源代码进行研读,查找错误或收集一些度量数据,并不需要对代码进行编译和执行。动态分析就是通过观察软件运行时的动作,来提供执行跟踪,时间分析,以及测试覆盖度方面的信息。

    最近公司要求重新回顾单元测试的实际效果,作为一个开发经理,我个人对单元测试也有很多疑惑。就个人而言,我自己也写过很多单元测试,也鼓励程序员写单元测试,但实际效果似乎不尽如人意。因此,写了这篇短文,想和大家一起探讨。

    1. 背景介绍

    我所在的公司是一家外资软件公司,主要工作是开发一个复杂的在线系统(java based web applicaiton). 该系统的主要特点是:定制化程度比较高,业务逻辑相当复杂。系统的技术栈是Struts, EJB (JBoss)and Hibernate。我管理的小组一共有10个左右开发人员,6个左右测试人员,平均工作经验在3年以上。

    公司在两年前开始推行单元测试。在开始推行单元测试之前,系统已经正式上线,也就是意味着有海量的没有单元测试的代码。推行之后,应该说投入了相当多的时间,总共覆盖的行数有20k, 其中行覆盖率(line coverage)有55%左右,分支覆盖率(branch coverage)有40%左右。我相信经过这么多尝试,应该说,我带的这个组不是一个单元测试的新手,有资格讨论单元测试的得失。

    2. 实践中的问题和疑惑(开发人员怎么说?)

    我一向主张:一项技术值不值得或者好不好用归根结底是要问实际的使用者和开发者的。作为一个经理,我不倾向于推行一项程序员极力反对的技术,不管这项技术是不是业界的标准或者是评论者的宠儿。一项技术必须要解决实际问题,也就是mark your life easier。所以下面是开发人员的回答。

    2.1为什么需要单元测试和TDD (Test Driven Development)?

    2.2.1 单元测试可以发现代码缺陷(Defect)么?投入/产出比(Defect count/Effort)是多少?

    只能发现待测单元的缺陷,不能发现单元交互(集成)之间的缺陷。在实践过程中,很少有defect通过单元测试发现。

    基本不能用于发现表现层(JSP, Java scripts, css, UI etc)的代码缺陷投入/产出比太高。换句话说,相比于单元测试,人工测试(munual testing)可以很大程度得更快更好的发现系统缺陷。

    2.2.2单元测试可以用来防止Regression Defect么?

    如果我们特地为某个regression defect加了相应的单元测试,那么单元测试在某种程度上可以防止regression defect的再一次出现。但是同样的,单元测试只能防止待测单元中的Regression Defect,而且需要通过猜测来加入相应的测试案例

    2.2.3 单元测试对设计有帮助么?

    单元测试本身不一定能帮助设计。据说TTD可以帮助设计,实践过程中没有很深的体会。

    2.2.4你投入了多少时间写单元测试?需要多少时间维护单元测试?

    单元测试:代码= 2:1,也就是说一行代码需要两行单元测试。也有些人说1:1。

    维护成本基本上决定于单元接口的变化频率:对于一些比较稳定的代码单元,维护成本还可以接受。但对于一些需求变化剧烈的单元,基本上需要重写。在实际实践中,可能的比例为稳定的单元测试:重写的单元测试 = 80%:20%。但是这里有一个悖论:其实我们更希望单元测试可以用于验证(verify)核心单元的正确性,然而这些单元的测试单元确基本上需要重写。这是为什么呢?其中一个可能的原因是:对于一个在线系统(web based application)来说,系统的主要逻辑和用户接口(user interface)绑定过于紧密,所以,用户接口的变化导致从表现层到数据库层的垂直变化。即使业务需求只是加了一个新的属性,但是这个数据将被加入核心的对象当中,所有涉及这个对象的单元测试需要改变。

    2.2.5单元测试的主要挑战是什么?

    挑战之一:如何在多个测试用例之间共享测试数据。

    公司产品支持一个很复杂的在线向导,由七步组成,每一步可以单独保存然后退出,下次继续编辑。如果你想测试最后一步的API,你需要准备很多其他页面的数据。因此,需要花很多时间准备测试数据。另外,公司产品还支持相似功能的其他向导。作为一个程序员,我们一直想在多个类似功能的向导API之间共享测试数据。然而,如果待测对象本身有些微变化,所有共享该数据的测试代码全部需要重写。这是一个巨大的维护费用。

    挑战之二:剧烈的需求变化导致维护成本剧增,收益减少。

    正如2.2.4中描述的,一个典型的在线系统(web based application),通常可以分为三层:表现层,主要是用户界面,包括HTML/JSP/CSS/Java Scripts/Ajex等等;业务层,主要是业务逻辑;数据层,存取数据。根据面向对象设计(OOD)的原则,业务层主要由一组领域对象(Business Object/Domain Object)构成。这些领域对象只提供一组数目相对有限的,接口比较清晰的,时间比较稳定的API。对这组API进行单元测试是有必要的,也是有意义的。

    然而,系统还有相当一部分的代码用于调用不同领域对象之间的API,转变成表现层需要的对象。表现层其他的逻辑还包含大量的代码用于连接不同的页面,以及构建不同的向导。

    正如和绝大多数的系统一样,产品需求的变化是极其剧烈的,可以预测的,不可避免的。在这种情况下,需求变化将导致领域对象API以上的代码(包括绝大多数表现层代码和一部分业务层代码)将发生剧烈变化,与之相应的单元测试代码都需要相应的改变。也就是说,这些代码的单元测试代码的维护成本很好。

    挑战之三:海量的遗留代码(Legacy Codes)

    正如前面描述的,我们是在产品已经上线之后才开始推行单元测试的。因此,大量的遗留代码并不适用于单元测试。换句话说,单元测试必须要在API实现之前予以仔细得考虑。如果API本身没有得到很好的设计,单元测试基本上是不可能的。

    2.2.6 拿什么来衡量单元测试?

    一般来说,业界使用行覆盖率(line coverage)和分支覆盖率(branch coverage)来衡量单元测试的测量。但在实际过程中,我们发现这些衡量标准和我们对单元测试的期望有很大差距:比如说,高覆盖率不见得较少的代码缺陷。高覆盖率也不能防止regression缺陷。高覆盖率也似乎和设计没有直接关联。从另外一个角度说,达到高覆盖率所花费的时间也是相当惊人的。

    从另外一个角度来说,我们希望找到一个方法可以简单直接地衡量单元测试的测量:比如说代码缺陷或regression defect数量。

    3. 聆听和讨论(业界怎么说)

    带着这些问题和困惑,我在网上查询了大量相关资料,牛人的文章和业界的讨论。很容易看出,业界对于单元测试的目标,作用,方法和手段都有很多争议。

    3.1 什么是(不是)单元测试?

    3.1.1 单元测试和发现缺陷无关(http://blog.stevensanderson.com/2009/08/24/writing-great-unit-tests-best-and-worst-practises/)

    【摘要】单元测试不是一个发现缺陷或者检测regression defect的有效方法。其一,单元测试,根据定义,是用来测试特定代码单元。然而,一个系统往往是一个大量单元的复杂集成,单元测试很难发现集成的缺陷。其二,相比单元测试,手工测试或者自动化集成测试更容易用于检测缺陷。

  • 单元测试入门

    2012-02-23 12:49:09

    一个比较最大值的函数

      我们首先引入一个比较最大值的函数。我们传入一个类型为int的数组参数,它将返回最大值的那个元素。代码如下:

     

    public class Largest {
     public static int largest(int[] datas){
      int max = 0;
      
      for(int i = 0 ; i < datas.length ; i++){
       if(max < datas[i]){
        max = datas[i];
       }
      }
      return max;
     }
    }

      可是,如何写我们的测试代码呢?

      直接在Largest类中添加一个main方法,要么重新写一个可运行的类来测试Largest。这样的测试,同样给我们带来了很大的挑战:

      1、验证困难。如何去验证代码的行为和我们的期望一致呢?使用很多的if…else再加上==或equals()来判断?对异常的情况又如何处理呢?混乱的验证,很容易给我们的测试代码带来BUG,让我们对自己的测试不够自信。

      2、测试类无法管理。我们如何直观的得到测试运行成功或失败的消息?用原始的System.out.println()吗?我们能一次运行多个单元测试吗?如果前面的测试运行出现异常,后面的测试还能继续运行吗?如果测试类很多,上百个甚至更多,我们能方便的由控制台输出测试结果吗?

      3、无法统计测试代码覆盖情况。缺少统一的测试代码编写规范和约定,可读性和维护性差。

      不过,面对这些挑战不用沮丧。单元测试框架已经帮我们解决了这些问题,它提供了很多测试的基础设施,让我们能把更多的经历投入到测试代码的编写中来。

      JUnit

      JUnit最初是由Erich Gamma(GoF之一)和Kent Beck(xp和refactor的先驱之一)编写的,它是一个开源Java测试框架,用于编写和运行可重复的测试。

      下面我们逐步介绍如何对Largest类测试:

      一、JUnit的安装。如果你使用的开发工具是Eclipse,不用做任何安装,它已经提供了Junit的支持。否则,你需要去http://www.junit.org/下载Junit安装包。安装非常简单,只要将junit.jar包设置到ClassPath中,让你的Java代码能够找到它就可以了。

      二、编写测试代码。代码如下:

     

        public class LargestTest extends TestCase {
      public void testLargest(){
       int[] datas = {7,8,9};
       assertEquals(9,Largest.largest(datas));
      }
    }

      说明:

      1、测试类一般要继承抽象类TestCase。它实现了各种测试方法,并提供了一个测试过程的架构。

      2、测试代码通过断言(Assert)来判断某个被测试函数是否正常工作。JUnit提供了很多断言函数,用来确定:某个条件是否为真;两个数据是否相等,或者不等,或者其它的一些情况。

      3、测试方法名以“test”开头,这样JUnit框架会自动发现这是一个测试方法。

      三、运行测试类。

      运行测试成功。

      我们的单元测试这样就算完成了吗?不,上面的测试只能算是一次验证而已。我们给的数据中,最大值9是数组的最后一个元素,如果9是第一个元素它还正确吗?如果数据是负数呢?等等。我们的求最大值函数有着很多的边界情况需要单元测试来验证。

      因此,我们在写单元测试之前,一定要对测试做一个周全的计划,预先设置好要测试的内容,可能发生错误的边界条件。

      下面是对Largest做的测试计划

      1、数组元素的位置是否对最大值产生影响?

      [7,8,9] – 9

      [7,9,8] – 9

      [9,8,7] – 9

      2、如果有两个相等的最大值,会出现什么情况呢?

      [7,9,8,9] – 9

      3、如果数组中只有一个元素,结果会怎么样?

      [1] - 1

      4、如果元素都是负数呢?

      [-7,-8,-9] - -7

      完整的测试代码应该如下:

     

    public class LargestTest extends TestCase {
     
     public void testSimple(){
      assertEquals(9,Largest.largest(new int[]{7,8,9}));
     }
     public void testOrder(){
      assertEquals(9,Largest.largest(new int[]{7,9,8}));
      assertEquals(9,Largest.largest(new int[]{9,8,7}));
     }
     public void testDups(){
      assertEquals(9,Largest.largest(new int[]{7,9,8,9}));
     }
     public void testOne(){
      assertEquals(1,Largest.largest(new int[]{1}));
     }
     public void testNegative(){
       assertEquals(-7,Largest.largest(new int[]{-7,-8,-9})); 
     }
    }

    当然,你可以写完一个测试方法就立即来运行它。这次并没有那么幸运了,在运行最后一个测试方法testNegative()时出现了错误:

      junit.framework.AssertionFailedError: expected:<-7> but was:<0>

      at test.junit.LargestTest.testNegative(LargestTest.java:24)

      细心的你,也许在一开始就发现了Largest的这个Bug。原来我们的字段max初始化为0是不对的,应该改为Integer.MIN_VALUE。

      由此我们可以想到,使用单元测试确实可以尽早的发现隐藏的BUG,上一篇我们也说过,越早发现BUG就能节省更多的时间,降低更多的风险。

      这是,我们的单元测试已经完美结束了吗?呵呵,也许你会想到,如果在largest()方法中传入数组为空,又会怎么样呢?这个问题留给我们的读者思考吧。

      写到这里,算是入门结束了吧!关于JUnit的详细介绍,网上有非常多的文章,去google你可以找到一大堆。

  • maven理论学习

    2012-02-23 10:41:47

    1、什么是Maven

             Apache Maven是一个项目管理工具,它包含了一个项目对象模型(Project Object Model),一个标准几何,一个项目生命周期(Project Lifecycle),一个依赖管理系统(Dependency Management System),和用来运行定义在生命周期阶段(phase)中插件(plugin)目标(goal)的逻辑。

    2、什么是pom 

        pom作为项目对象模型。

    基本内容:

          POM包括了所有的项目信息。

          POM定义了最小的maven2元素,允许groupId,artifactId,version等所有需要的元素;groupId,,artifactId, version:描述了依赖的项目唯一标志

    • groupId:项目或者组织的唯一标志,并且配置时生成的路径也是由此生成,如com.taobao.cmp生成的相对路径为:/com/taobao/cmp
    • artifactId:groupId下的表示一个单独项目的唯一标识符,默认为jar
    • version:项目的版本
    • packaging:打包的机制,如pom, jar, maven-plugin, ejb, war, ear, rar, par

     

    <groupId>com.taobao.XXXtest</groupId>

       <artifactId>XXX-qatest</artifactId>

       <version>1.0.0</version>

       <packaging>jar</packaging>

    3POM中的关系: 

    主要为依赖,合成,继承

    3.1依赖关系(dependencies相关): 

    Maven提供传递依赖:

    A->B  B->C  C->D   则 A依赖D;

    A->B->D1.0  A->D2.0 则 A依赖D2.0

    A->E1.0  A->E2.0    则 A依赖E1.0

    这种依赖方式遵循的原则:最近原则、最先原则(since maven 2.0.9)。

    <dependencies>

        <dependency>

             <groupId>junit</groupId>

             <artifactId>junit</artifactId>

             <version>${junit.version}</version>

       </dependency>

    </dependencies>

    3.1.1依赖的范围(scope 

    compile(缺省值 ),test,runtime,provided;

    依赖范围(scope)主源码classpath可用测试源码classpath可用会被打包
    compile 缺省值TRUETRUETRUE
    testFALSETRUEFALSE
    runtimeFALSETRUETRUE
    providedTRUETRUEFALSE

     

     

     

     

     

    Junit包一般来说只有在运行测试的时候需要它,对上一个例子进行改进:

    <dependencies>

        <dependency>

             <groupId>junit</groupId>

             <artifactId>junit</artifactId>

             <version>${junit.version}</version>

             <scope>test</scope>

       </dependency>

    </dependencies>

    再举个例子,在开发javaee应用的时候一定会用到servlet-api,它对于主源码和测试源码都是必要的,因为我们的代码中会引入servlet-api的包。但是,在打包的时候,将其放入WAR包就会有问题,因为web容器会提供servlet-api,如果我们再将其打包就会造成依赖冲突,解决方案如下:

    <dependency>  

      <groupId>javax.servlet</groupId>  

      <artifactId>servlet-api</artifactId>  

      <version>2.4</version>  

      <scope>provided</scope>  

    </dependency>

    3.1.2依赖归类 

    随着项目的增大,依赖越来越多。例如依赖了很多spring的jar:org.springframework:spring;org.springframework:spring-mock;org.springframework: spring-aspects,它们的groupId是相同的,artifactId不同,为了管理其版本,可以采用下面这种方式

    <dependencies>

    <dependency>

                  <groupId>org.springframework</groupId>

                  <artifactId>spring</artifactId>

                  <version>${spring.version}</version>

               </dependency>

               <dependency>

                  <groupId>org.springframework</groupId>

                  <artifactId>spring-mock</artifactId>

                  <version>${spring.version}</version>

                  <scope>test</scope>

               </dependency>

               <dependency>

                  <groupId>org.springframework</groupId>

                  <artifactId>spring-aspects</artifactId>

                  <version>${spring.version}</version>

               </dependency>

    </dependencies>

    <properties>  

                <spring.version>2.0.7</spring.version>  

    </properties> 

    查看(375) 评论(0) 收藏 分享 管理

  • maven-surefire-plugin

    2012-02-23 10:24:25

    pom.xml文件里添加了这个插件:maven-surefire-plugin

                            <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <configuration>
    <includes>
    <include>**/*_Test.java</include>
    </includes>
    <forkMode>pertest</forkMode>
    <argLine>-Xms512m -Xmx1024m -Xss1m</argLine>
    </configuration>
    </plugin>

     Maven的surefire插件中fork属性的配置:  

             Maven运行测试用例时,是通过调用maven的surefire插件并fork一个子进程来执行用例的。forkmode属性中指明是要为每个测试创建一个进程,还是所有测试在同一个进程中完成。 

            forkMode 可设置值有 “never”, “once”, “always” 和 “pertest”  

    1. pretest: 每一个测试创建一个新进程,为每个测试创建新的JVM是单独测试的最彻底方式,但也是最慢的,不适合hudson上持续回归
    2. once:在一个进程中进行所有测试。once为默认设置,在Hudson上持续回归时建议使用默认设置  
    3. always:在一个进程中并行的运行脚本,Junit4.7以上版本才可以使用,surefire的版本要在2.6以上,其中 threadCount执行时,指定可分配的线程数量。只和参数parallel配合使用有效。默认:5 
  • web.xml和pom.xml

    2012-02-22 19:26:06

    web.xml是创建java web工程时自动创建的,比如在eclipse中,新建
    dynamic web project时会自动生成web.xml
    而pom.xml是mavn管理项目工程时生成的,使用maven创建工程的命令会生成,主要作用是jar包版本管理和依赖管理。
741/41234>
Open Toolbar