posts - 51, comments - 17, trackbacks - 0, articles - 9
  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

synchronized的一个简单例子

public class TextThread
{

 /**
  * @param args
  */
 public static void main(String[] args)
 {
  // TODO 自动生成方法存根
        TxtThread tt = new TxtThread();
        new Thread(tt).start();
        new Thread(tt).start();
        new Thread(tt).start();
        new Thread(tt).start();
 }

}
class TxtThread implements Runnable
{
 int num = 100;
 String str = new String();
 public void run()
 {
  while (true)
  {
   synchronized(str)
   {
   if (num>0)
   {
    try
    {
     Thread.sleep(10);
    }
    catch(Exception e)
    {
     e.getMessage();
    }
    System.out.println(Thread.currentThread().getName()+ "this is "+ num--);
   }
   }
  }
 }
}

上面的例子中为了制造一个时间差,也就是出错的机会,使用了Thread.sleep(10)


Java对多线程的支持与同步机制深受大家的喜爱,似乎看起来使用了synchronized关键字就可以轻松地解决多线程共享数据同步问题。到底如何?――还得对synchronized关键字的作用进行深入了解才可定论。

总的说来,synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。如果再细的分类,synchronized可作用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上。

在进一步阐述之前,我们需要明确几点:

A.无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问。

B.每个对象只有一个锁(lock)与之相关联。

C.实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。

接着来讨论synchronized用到不同地方对代码产生的影响:

 

假设P1、P2是同一个类的不同对象,这个类中定义了以下几种情况的同步块或同步方法,P1、P2就都可以调用它们。

 

1.  把synchronized当作函数修饰符时,示例代码如下:

Public synchronized void methodAAA()

{

//….

}

这也就是同步方法,那这时synchronized锁定的是哪个对象呢?它锁定的是调用这个同步方法对象。也就是说,当一个对象P1在不同的线程中执行这个同步方法时,它们之间会形成互斥,达到同步的效果。但是这个对象所属的Class所产生的另一对象P2却可以任意调用这个被加了synchronized关键字的方法。

上边的示例代码等同于如下代码:

public void methodAAA()

{

synchronized (this)      //  (1)

{

       //…..

}

}

 (1)处的this指的是什么呢?它指的就是调用这个方法的对象,如P1。可见同步方法实质是将synchronized作用于object reference。――那个拿到了P1对象锁的线程,才可以调用P1的同步方法,而对P2而言,P1这个锁与它毫不相干,程序也可能在这种情形下摆脱同步机制的控制,造成数据混乱:(

2.同步块,示例代码如下:

            public void method3(SomeObject so)

              {

                     synchronized(so)

{

       //…..

}

}

这时,锁就是so这个对象,谁拿到这个锁谁就可以运行它所控制的那段代码。当有一个明确的对象作为锁时,就可以这样写程序,但当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象)来充当锁:

class Foo implements Runnable

{

       private byte[] lock = new byte[0];  // 特殊的instance变量

    Public void methodA()

{

       synchronized(lock) { //… }

}

//…..

}

注:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。

3.将synchronized作用于static 函数,示例代码如下:

      Class Foo

{

public synchronized static void methodAAA()   // 同步的static 函数

{

//….

}

public void methodBBB()

{

       synchronized(Foo.class)   //  class literal(类名称字面常量)

}

       }

   代码中的methodBBB()方法是把class literal作为锁的情况,它和同步的static函数产生的效果是一样的,取得的锁很特别,是当前调用这个方法的对象所属的类(Class,而不再是由这个Class产生的某个具体对象了)。

记得在《Effective Java》一书中看到过将 Foo.class和 P1.getClass()用于作同步锁还不一样,不能用P1.getClass()来达到锁这个Class的目的。P1指的是由Foo类产生的对象。

可以推断:如果一个类中定义了一个synchronized的static函数A,也定义了一个synchronized 的instance函数B,那么这个类的同一对象Obj在多线程中分别访问A和B两个方法时,不会构成同步,因为它们的锁都不一样。A方法的锁是Obj这个对象,而B的锁是Obj所属的那个Class。

 

小结如下:

搞清楚synchronized锁定的是哪个对象,就能帮助我们设计更安全的多线程程序。


 

还有一些技巧可以让我们对共享资源的同步访问更加安全:

1.  定义private 的instance变量+它的 get方法,而不要定义public/protected的instance变量。如果将变量定义为public,对象在外界可以绕过同步方法的控制而直接取得它,并改动它。这也是JavaBean的标准实现方式之一。

2.  如果instance变量是一个对象,如数组或ArrayList什么的,那上述方法仍然不安全蛭蓖饨缍韵笸ü齡et方法拿到这个instance对象的引用后,又将其指向另一个对象,那么这个private变量也就变了,岂不是很危险。 这个时候就需要将get方法也加上synchronized同步,并且,只返回这个private对象的clone()――这样,调用端得到的就是对象副本的引用了。

synchronized的一个简单例子

public class TextThread
{

 /**
  * @param args
  */
 public static void main(String[] args)
 {
  // TODO 自动生成方法存根
        TxtThread tt = new TxtThread();
        new Thread(tt).start();
        new Thread(tt).start();
        new Thread(tt).start();
        new Thread(tt).start();
 }

}
class TxtThread implements Runnable
{
 int num = 100;
 String str = new String();
 public void run()
 {
  while (true)
  {
   synchronized(str)
   {
   if (num>0)
   {
    try
    {
     Thread.sleep(10);
    }
    catch(Exception e)
    {
     e.getMessage();
    }
    System.out.println(Thread.currentThread().getName()+ "this is "+ num--);
   }
   }
  }
 }
}

上面的例子中为了制造一个时间差,也就是出错的机会,使用了Thread.sleep(10)


Java对多线程的支持与同步机制深受大家的喜爱,似乎看起来使用了synchronized关键字就可以轻松地解决多线程共享数据同步问题。到底如何?――还得对synchronized关键字的作用进行深入了解才可定论。

总的说来,synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。如果再细的分类,synchronized可作用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上。

在进一步阐述之前,我们需要明确几点:

A.无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问。

B.每个对象只有一个锁(lock)与之相关联。

C.实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。

接着来讨论synchronized用到不同地方对代码产生的影响:

 

假设P1、P2是同一个类的不同对象,这个类中定义了以下几种情况的同步块或同步方法,P1、P2就都可以调用它们。

 

1.  把synchronized当作函数修饰符时,示例代码如下:

Public synchronized void methodAAA()

{

//….

}

这也就是同步方法,那这时synchronized锁定的是哪个对象呢?它锁定的是调用这个同步方法对象。也就是说,当一个对象P1在不同的线程中执行这个同步方法时,它们之间会形成互斥,达到同步的效果。但是这个对象所属的Class所产生的另一对象P2却可以任意调用这个被加了synchronized关键字的方法。

上边的示例代码等同于如下代码:

public void methodAAA()

{

synchronized (this)      //  (1)

{

       //…..

}

}

 (1)处的this指的是什么呢?它指的就是调用这个方法的对象,如P1。可见同步方法实质是将synchronized作用于object reference。――那个拿到了P1对象锁的线程,才可以调用P1的同步方法,而对P2而言,P1这个锁与它毫不相干,程序也可能在这种情形下摆脱同步机制的控制,造成数据混乱:(

2.同步块,示例代码如下:

            public void method3(SomeObject so)

              {

                     synchronized(so)

{

       //…..

}

}

这时,锁就是so这个对象,谁拿到这个锁谁就可以运行它所控制的那段代码。当有一个明确的对象作为锁时,就可以这样写程序,但当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象)来充当锁:

class Foo implements Runnable

{

       private byte[] lock = new byte[0];  // 特殊的instance变量

    Public void methodA()

{

       synchronized(lock) { //… }

}

//…..

}

注:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。

3.将synchronized作用于static 函数,示例代码如下:

      Class Foo

{

public synchronized static void methodAAA()   // 同步的static 函数

{

//….

}

public void methodBBB()

{

       synchronized(Foo.class)   //  class literal(类名称字面常量)

}

       }

   代码中的methodBBB()方法是把class literal作为锁的情况,它和同步的static函数产生的效果是一样的,取得的锁很特别,是当前调用这个方法的对象所属的类(Class,而不再是由这个Class产生的某个具体对象了)。

记得在《Effective Java》一书中看到过将 Foo.class和 P1.getClass()用于作同步锁还不一样,不能用P1.getClass()来达到锁这个Class的目的。P1指的是由Foo类产生的对象。

可以推断:如果一个类中定义了一个synchronized的static函数A,也定义了一个synchronized 的instance函数B,那么这个类的同一对象Obj在多线程中分别访问A和B两个方法时,不会构成同步,因为它们的锁都不一样。A方法的锁是Obj这个对象,而B的锁是Obj所属的那个Class。

 

小结如下:

搞清楚synchronized锁定的是哪个对象,就能帮助我们设计更安全的多线程程序。


 

还有一些技巧可以让我们对共享资源的同步访问更加安全:

1.  定义private 的instance变量+它的 get方法,而不要定义public/protected的instance变量。如果将变量定义为public,对象在外界可以绕过同步方法的控制而直接取得它,并改动它。这也是JavaBean的标准实现方式之一。

2.  如果instance变量是一个对象,如数组或ArrayList什么的,那上述方法仍然不安全蛭蓖饨缍韵笸ü齡et方法拿到这个instance对象的引用后,又将其指向另一个对象,那么这个private变量也就变了,岂不是很危险。 这个时候就需要将get方法也加上synchronized同步,并且,只返回这个private对象的clone()――这样,调用端得到的就是对象副本的引用了。




1.
public class Thread1 implements Runnable{

 //当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,
 //一个时间内只能有一个线程得到执行。
 //另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块
 public void run(){
  synchronized (this){
   for(int i=0; i<5; i++){
    System.out.println(Thread.currentThread().getName() + " synchronize loop " + i);
   }
  }
 }
 
 public static void main(String[] args) {

  Thread1 thread1 = new Thread1();
  Thread thread2 = new Thread(thread1, "A");
  Thread thread3 = new Thread(thread1, "B");
  thread2.start();
  thread3.start();//thead3必须等待thread2的执行完毕就可立即执行
 }

}


2.

public class Thread2 {

 public void method1(){
  synchronized(this){
   int i = 5;
   while(i-- > 0){
    System.out.println(Thread.currentThread().getName() + " : " + i);
   
    try{
     Thread.sleep(500);
     }
    catch(InterruptedException  ie){
     ie.printStackTrace();
     }
   }
  }
 }
 
 //当一个线程访问object的一个synchronized(this)同步代码块时,
 //另一个线程仍然可以访问该object中的非synchronized(this)同步代码块
 public void method2(){
  int i = 5;
  while(i-- > 0){
   System.out.println(Thread.currentThread().getName() + " : " + i);
   
   try{
    Thread.sleep(500);
   }catch(InterruptedException ie){
    ie.printStackTrace();
   }
  }
 }
 
 public static void main(String[] args) {

  final Thread2 thread2 = new Thread2();
  Thread thread1 = new Thread(new Runnable(){
   public void run(){
    thread2.method1();
   }
  }, "Thread1");
  
  Thread thread3 = new Thread(new Runnable(){
   public void run(){
    thread2.method2();
   }
  }, "Thread2");
  
  thread1.start();
  thread3.start();//thead3不用等待thread1的执行完毕就可立即执行
 }

3.

public class Thread3 {

 class Inner{
  private void method1(){
   int i = 5;
   while(i-- > 0){
    System.out.println(Thread.currentThread().getName() + " : Inner.method1() = " + i);
    try{
     Thread.sleep(500);
    }catch(InterruptedException ie){
     ie.printStackTrace();
    }
   }
  }
  
  //尽管线程thread1获得了对Inner的对象锁,但由于线程thread2访问的是同一个Inner中的非同步部分。
  //所以两个线程互不干扰。
  private void method2(){
   int i = 5;
   while(i-- > 0){
    System.out.println(Thread.currentThread().getName() + " : Inner.method2() = " + i);
    try{
     Thread.sleep(500);
    }catch(InterruptedException ie){
     ie.printStackTrace();
    }
   }
  }
 }

 
 public void method1(Inner inner){
  synchronized (inner){//使用对象锁
   inner.method1();
  }
 }
 
 public void method2(Inner inner){
  inner.method2();
 }
 
 
 public static void main(String[] args){
  final Thread3 thread3 = new Thread3();
  final Inner inner = thread3.new Inner();
  
  Thread thread1 = new Thread(new Runnable(){
   public void run(){
    thread3.method1(inner);
   }
  }, "Thread1");
  
  Thread thread2 = new Thread(new Runnable(){
   public void run(){
    thread3.method2(inner);
   }
  }, "Thread2");
  
  thread1.start();
  thread2.start();//thread2无需等待thread1运行完毕,就可执行,
 }
}

4.

public class Thread4 {

 class Inner{
  private void method1(){
   int i = 5;
   while(i-- > 0){
    System.out.println(Thread.currentThread().getName() + " : Inner.method1() = " + i);
    try{
     Thread.sleep(500);
    }catch(InterruptedException ie){
     ie.printStackTrace();
    }
   }
  }
  
  //尽管线程thread1与thread2访问了同一个Inner对象中两个毫不相关的部分,但因为thread1先获得了对Inner的对象锁,
  //所以thread2对Inner.mmethod2()的访问也被阻塞,因为method2()是Inner中的一个同步方法。
  private synchronized void method2(){
   int i = 5;
   while(i-- > 0){
    System.out.println(Thread.currentThread().getName() + " : Inner.method2() = " + i);
    try{
     Thread.sleep(500);
    }catch(InterruptedException ie){
     ie.printStackTrace();
    }
   }
  }
 }

 
 public void method1(Inner inner){
  synchronized (inner){//使用对象锁
   inner.method1();
  }
 }
 
 public void method2(Inner inner){
  inner.method2();
 }
 
 
 public static void main(String[] args){
  final Thread4 thread4 = new Thread4();
  final Inner inner = thread4.new Inner();
  
  Thread thread1 = new Thread(new Runnable(){
   public void run(){
    thread4.method1(inner);
   }
  }, "Thread1");
  
  Thread thread2 = new Thread(new Runnable(){
   public void run(){
    thread4.method2(inner);
   }
  }, "Thread2");
  
  thread1.start();
  thread2.start();//thread2要等待thread1运行完才能执行,因为method2是一个同步方法
 }
}

5.

import java.text.SimpleDateFormat;
import java.util.Date;

public class TestThread extends Thread{

 private static Integer threadCounterLock;//用于同步,防止数据被写乱,可以是任意的对象
 private static int threadCount;//静态变量用于本类的线程计数器
 
 static{
  threadCount = 0;
  threadCounterLock = new Integer(0);
 }
 
 public TestThread(){
  super();
 }
 
 public synchronized static void incThreadCount(){
  threadCount++;
  System.out.println("thread count after enter: " + threadCount);
 }
 
 public synchronized static void decThreadCount(){
  threadCount--;
  System.out.println("thread count after leave: " + threadCount);
 }
 
 public void run(){
//  synchronized(threadCounterLock){//同步
//   threadCount++;
//   System.out.println("thread count after enter: " + threadCount);
//  }
  
  incThreadCount();//此语句作用同上面注释掉的同步代码块
  
  final long nSleepMilliSecs = 1000; //循环中的休眠时间

  
  long nCurrentTime = System.currentTimeMillis();
  long nEndTime = nCurrentTime + 10000;//运行截止时间

  SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  
  try{
   while(nCurrentTime < nEndTime){
    nCurrentTime = System.currentTimeMillis();
    System.out.println("Thread " + this.hashCode() + ", current time: " + simpleDateFormat.format(new Date(nCurrentTime)));
    
    try{
     sleep(nSleepMilliSecs);
    }catch(InterruptedException ie){
     ie.printStackTrace();
    }
   }
  }finally{
//   synchronized(threadCounterLock){
//    threadCount--;
//    System.out.println("thread count after leave: " + threadCount);
//   }
   
   decThreadCount();//此语句作用同上面注释掉的同步代码块
  }
  
 }
 
 public static void main(String[] args){
  TestThread[] testThread = new TestThread[2];
  for(int i=0; i<2; i++){
   testThread[i] = new TestThread();
   testThread[i].start();
  }
 }
}



posted @ 2007-04-09 20:54 chenweicai 阅读(1088) | 评论 (0)编辑 收藏


1。类中定义内部类

public class Outer {

 private int outer_i = 100;//内部类可以访问外部类的变量
 
 void test(){
  Inner inner = new Inner();
  inner.display();
 }
 
 //当一个类中的程序代码要用到另外一个类的实例对象,
 //而另外一个类的程序代码又要访问第一个类的成员时
 // 将另外一个类作成第一个类的内部类。
 class Inner{
  
  private int inner_i;
  //内部类的成员只有在内部类的范围之内是可知的,
  //并不能被外部类使用
  
  void display(){
   System.out.println("display: outer_i = " + outer_i);
  }
 }
}


 public static void main(String[] args) {

  Outer outer = new Outer();
  outer.test();
 }

2.

public class Outer {

 private int size = 1;
 
 public void print(){
  Inner inner = new Inner();
  inner.method(11);
 }
 
 public class Inner{
  private int size = 3;
  
  public void method(int size){
   size++;//method的形参
   this.size++;//Inner类中的成员变量
   Outer.this.size++;//Outer类中的成员变量
   
   System.out.println("method size is :" +size);
   System.out.println("Inner size is :" + this.size);
   System.out.println("Outer size is :" + Outer.this.size);
  }
 }
 
 public static void main(String[] args){
  Outer outer = new Outer();
  outer.print();
 }
}

3.内部类被外部引用

public class Outer {

 private int size = 10;
 
 //内部类声明为public 可从外部类之外被调用
 public class Inner{
  public void print(){
   System.out.println(++size);
  }
 }
}

public class test {


 public static void main(String[] args) {

  Outer outer = new Outer();
  Outer.Inner inner = outer.new Inner();
  inner.print();
 }
}

4.方法中定义内部类

public class Outer {

 int outer_i = 100;
 String str = new String("Between");
 
 void test(){
  for(int i=0; i<5; i++){
   
   class Inner{
    void display(){
     System.out.println("display: outer_i = " + outer_i);
    }
   }
   
   Inner inner = new Inner();
   inner.display();
  }
 }
 
 //在方法中定义的内部类只能访问方法中的final类型的局部变量
 //因为用final定义的局部变量相当于一个常量,她的生命周期超出了方法运行的生命周期
 public void amethod(final int iArgs){
  int it315;//此变量内部类不能访问
  
  class Bicycle{
   public void sayhello(){
    System.out.println(str);
    System.out.println(iArgs);
   }
  }
  Bicycle bicycle = new Bicycle();
  bicycle.sayhello();
 }
}


 public static void main(String[] args) {

  Outer outer = new Outer();
  outer.test();
  outer.amethod(18);
  
 }

posted @ 2007-04-08 22:13 chenweicai 阅读(123) | 评论 (0)编辑 收藏

1.静态变量

public class Chinese {

 static String country = "中国";
 
 String name;
 int age;
 
 void singOurCountry(){
  System.out.println("啊, 亲爱的" + country);
 }
}

 public static void main(String[] args) {

  System.out.println("Chinese country is :" + Chinese.country);//类.成员
  Chinese ch1 = new Chinese();
  System.out.println("Chinese country is :" + ch1.country);//对象.成员
        ch1.singOurCountry();
 }


注:1. 不能把任何方法内的变量声明为static类型
        2. 静态成员变量为该类的各个实例所共享,所以在内存中该变量只有一份,
            经常用来计数统计
如:
class A{
     private static int  count = 0;

      public A(){
          count = count + 1;  
    }

public void finalize(){//在垃圾回收时,finalize方法被调用
       count = count - 1;
    }
}


2.静态方法
public class Chinese {

 //在静态方法里,只能直接调用同类中的其他静态成员,不能直接访问类中的非静态成员和方法
 //因为对如非静态成员和方法,需先创建类的实例对象后才可使用。
 //静态方法不能使用关键字this和super,道理同上
 //main方法是静态方法,不能直接访问该类中的非静态成员,必须先创建该类的一个实例
 static void sing(){
  System.out.println("啊...");
 }
 
 void singOurCountry(){
  sing();//类中的成员方法可以直接访问静态成员方法
  System.out.println("啊...祖国...");
 }
}

 public static void main(String[] args) {

  Chinese.sing();//类.方法
  
  Chinese ch1 = new Chinese();
  ch1.sing();//对象名.方法
  ch1.singOurCountry();
 }


3. 静态代码块
public class StaticCode {

 static String country;
 
 static{
  country = "Chine";
  System.out.println("Static code is loading...");
 }
}

public class TestStatic {

 static{
  System.out.println("TestStatic code is loading..."); 
 }
 
 public static void main(String[] args){
  System.out.println("begin executing main method...");
  //类中的静态代码将会自动执行,尽管产生了类StaticCode的两个实例对象,
  //但其中的静态代码块只被执行了一次。同时也反过来说明:类是在第一次被使用时
  //才被加载,而不是在程序启动时就加载程序中所有可能要用到的类
  new StaticCode();
  new StaticCode();
 }
 
}



posted @ 2007-04-08 20:12 chenweicai 阅读(127) | 评论 (0)编辑 收藏

java实现多线程有两种方法:
一种是继承Thread类

如下面例子:
public class TestThread extends Thread {

 //实现多线程方法一:继承Thread类,并重载run方法
 public void run(){
  //要将一段代码在一个新的线程上运行,该代码应写在run函数中
  while(true){
   System.out.println(Thread.currentThread().getName() + "is running...");
  }
 }
}



public class ThreadDemo2 {

 public static void main(String[] args) {
  
  //启动一个新的线程,不是直接调用Thread类子类对象的run方法,
  //而是调用Thread类子类对象的start方法,Thread类对象的start方法
  //将产生新的线程,并在该线程上运行该Thread类对象中的run方法,根据
  //多态性 实际上运行的是Thread子类的run方法
  TestThread testThread = new TestThread();
  testThread.start();
  
  while(true){
   System.out.println("main thread is running...");
  }
 }

}



方法二是:实现接口Runnable中的run函数

如下列所示

//实现多线程方法二:实现Runnable接口,重载run函数,
//大多数情况下,如果只想重写 run() 方法,而不重写其他 Thread 方法,
//那么应使用 Runnable 接口。这很重要,
//因为除非程序员打算修改或增强类的基本行为,否则不应为该类创建子类。
public class TestThread implements Runnable {

 public void run() {
  // TODO Auto-generated method stub
  System.out.println(Thread.currentThread().getName() + "is running......");
 }
 
}


 public static void main(String[] args) {
  // TODO Auto-generated method stub
  TestThread testThread = new TestThread();//创建TestThread类的一个实例
  Thread thread = new Thread(testThread);//创建一个Thread类的实例
  thread.start();//使线程进入runnable状态
  
  while(true){
   System.out.println("main thread is running...");
  }
 }


2.这两种方法的区别:实现Runnable接口适合多个相同的程序代码访问处理同一资源


如下列所示:
//四个售票窗口售同一中车票,总共有100张,要求这四个售票多线程进行
public class TicketRunnable implements Runnable {

 private int tickets = 100;
 public void run() {
   while(true){
    if(tickets > 0){
     System.out.println(Thread.currentThread().getName() +
       " is saling ticket " + tickets--);
    }
   }
 }

}

public class TicketRunnableDemo {

 public static void main(String[] args){
  //创建了四个线程,每个线程调用了同一各TicketRunnable对象中run方法,
  //访问的是同一个对象中的变量tickets,从而满足了我们的要求。
  //实现Runnable接口适合多个相同的程序代码访问处理同一资源
  TicketRunnable ticketrunnable = new TicketRunnable();
  new Thread(ticketrunnable).start();
  new Thread(ticketrunnable).start();
  new Thread(ticketrunnable).start();
  new Thread(ticketrunnable).start();
 }
}

而继承继承Thread类,并重载run方法不能达到此目的
如下所示:
public class TicketThread extends Thread {

 private int tickets = 100;
 
 public void run(){
  while(true){
   if(tickets>0){
    System.out.println(Thread.currentThread().getName() +
      "is saling ticket " + tickets--);
   }
  }
 }
}

public class TicketThreadDemo {

 public static void main(String[] args){
  //我们希望的是四个线程共售100,我们创建四个线程
  //但结果与我们希望的相反,各个线程各售100张票
  new TicketThread().start();
  new TicketThread().start();
  new TicketThread().start();
  new TicketThread().start();
  
//  如果我们象下面这样写的话,只有一个线程在售票,没有实现四个线程并发售票
//  TicketThread ticketThread = new TicketThread();
//  ticketThread.start();
//  ticketThread.start();
//  ticketThread.start();
//  ticketThread.start();
 }
}

3.后台线程

public class DaemonTest {
 public static void main(String[] args){
  TicketRunnable ticketRunnable = new TicketRunnable();
  Thread thread = new Thread(ticketRunnable);
  thread.setDaemon(true);//将此线程设置为后台线程,当进程中只有后台线程时,进程就会结束
  thread.start();
 }
}

4.联合线程
(还没搞懂)

5. 线程同步

在上面的售票程序中,线程可能是不安全的,

比如我们修改一下:

public class ThreadRunnable implements Runnable{
 
 private int tickets = 100;
 public void run() {
   while(true){
    if(tickets > 0){
     try{
      Thread.sleep(10);//使该线程暂停,
     }catch(Exception e){
      System.out.println(e.getMessage());
     }
     System.out.println(Thread.currentThread().getName() +
       " is saling ticket " + tickets--);
    }
   }
 }
}

再次测试时,发现可能会打印-1 -2 这样的ticket

所以我们要修改一下,使该程序是线程安全的,

public class TicketSynch implements Runnable{
 
 private int tickets = 100;
 String str = new String("");//此对象放在run函数之外
 
 public void run(){
  while(true){
   synchronized(str){//同步代码块
    if(tickets > 0){
     try{
      Thread.sleep(10);
     }
     catch(Exception e){
      System.out.println(e.getMessage());
     }
     System.out.println(Thread.currentThread().getName()
       +" is saling ticket " + tickets--);
    }
   }
  }
 }

}

但是 同步以牺牲程序的性能为代价

5. 同步函数

public class SynchMethod implements Runnable{

 private int tickets = 100;
 public void run(){
  while(true){
   sale();
  }
 }
 
 public synchronized void sale(){
  if(tickets > 0){
   try{
    Thread.sleep(10);
   }catch(Exception e){
    System.out.println(e.getMessage());
   }
   System.out.println(Thread.currentThread().getName()
     + "is saling ticket " + tickets--);
  }
 }
}

 public static void main(String[] args){
  //创建了四个线程,每个线程调用了同一各TicketRunnable对象中run方法,
  //访问的是同一个对象中的变量tickets,从而满足了我们的要求。
  //实现Runnable接口适合多个相同的程序代码访问处理同一资源
  SynchMethod synchMethod = new SynchMethod();
  new Thread(synchMethod).start();
  new Thread(synchMethod).start();
  new Thread(synchMethod).start();
  new Thread(synchMethod).start();
 }

6.代码块和函数间的同步
public class MethodAndBlockSyn implements Runnable{

 private int tickets = 100;
 String str = new String("");
 
 public void run(){
  if(str.equals("method")){
   while(true){
    sale();
   }
  }
  else{
   synchronized(str){//同步代码块
    if(tickets > 0){
     try{
      Thread.sleep(10);
     }
     catch(Exception e){
      System.out.println(e.getMessage());
     }
     System.out.println(Thread.currentThread().getName()
       +" is saling ticket " + tickets--);
    }
   }
  }
 }
 
 
 public synchronized void sale(){
  if(tickets > 0){
   try{
    Thread.sleep(10);
   }catch(Exception e){
    System.out.println(e.getMessage());
   }
   System.out.println(Thread.currentThread().getName()
     + "is saling ticket " + tickets--);
  }
 }
}


 public static void main(String[] args){
  MethodAndBlockSyn test = new MethodAndBlockSyn();
  new Thread(test).start();// 这个线程调用同步代码块
  
  try{
   Thread.sleep(1);//让线程等待100ms 是为了让该线程得到CPU
   //避免该线程未得到CPU就将str设置为method,从而使此线程也是调用同步函数
  }catch(Exception e)
  {
  }
  test.str = new String("method");
  new Thread(test).start();//这个线程调用同步函数
 }

 

7. 线程间通讯

wait: 告诉当前线程放弃监视器并进入睡眠状态,知道有其他线程进入同一监视器并调用notify为止。

notify:唤醒同一对象监视器中调用wait的第一个线程。类似饭馆有一个空位置后通知所有等待就餐顾客中的第一位可以入座的情况

notifyAll:唤醒同一对象监视器中调用wait的所有线程,具有最高优先级的程序先被唤醒并执行。

以上三个方法只能在synchronized方法中调用

public class Record {

 private String name = "陈世美";
 private String sex = "女";
 boolean bFull = false;
 
 public synchronized void put(String name, String sex)
 {
  if(bFull)
   try {
    wait();
   } catch (InterruptedException e1) {
    // TODO Auto-generated catch block
    e1.printStackTrace();
   }
  
  this.name = name;
  
  try{
   Thread.sleep(10);
  }
  catch(Exception e){
   System.out.println(e.getMessage());
  }
  
  this.sex = sex;
  
  bFull = true;
  notify();
 }
 
 public synchronized void get(){
  if(!bFull)
   try {
    wait();
   } catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   }
  System.out.println(name + "--->" + sex);
  
  bFull = false;
  notify();
 }
}




public class Productor implements Runnable {

 Record record = null;
 
 public Productor(Record record){
  this.record = record;
 }
 
 public void run(){
  int i = 0;
  
  while(true){
   if(i==0)
    record.put("陈陈陈", "男");
   else
    record.put("陈世美", "女");
   i = (i+1)%2;
  }
 }
}

public class Consumer implements Runnable {

 Record record = null;
 
 public Consumer(Record record){
  this.record = record;
 }
 
 public void run(){
  while(true){
   record.get();
  }
 }
}

 public static void main(String[] args) {
  // TODO Auto-generated method stub

  Record record = new Record();
  new Thread(new Productor(record)).start();
  new Thread(new Consumer(record)).start();
 }



 

 

posted @ 2007-04-08 17:14 chenweicai 阅读(184) | 评论 (0)编辑 收藏

 我们知道,Java利用ClassLoader将类载入内存,并且在同一应用中,可以有很多个ClassLoader,通过委派机制,把装载的任务传递给上级的装载器的,依次类推,直到启动类装载器(没有上级类装载器)。如果启动类装载器能够装载这个类,那么它会首先装载。如果不能,则往下传递。当父类为null时,JVM内置的类(称为:bootstrap class loader)就会充当父类。想想眼下的越来越多用XML文件做配置文件或者是描述符、部署符。其实这些通过XML文档描述的配置信息最终都要变成Java类,基实都是通过ClassLoader来完成的。URLClassLoader是ClassLoader的子类,它用于从指向 JAR 文件和目录的 URL 的搜索路径加载类和资源。也就是说,通过URLClassLoader就可以加载指定jar中的class到内存中。

下面来看一个例子,在该例子中,我们要完成的工作是利用URLClassLoader加载jar并运行其中的类的某个方法。

首先我们定义一个接口,使所有继承它的类都必须实现action方法,如下:

public   interface  ActionInterface  {
    
public  String action();
}

完成后将其打包为testInterface.jar文件。

接下来新建一工程,为了编译通过,引入之前打好的testInterface.jar包。并创建TestAction类,使它实现ActionInterface接口。如下:

 

public   class  TestAction  implements  ActionInterface  {
    
public  String action()  {
        
return   " com.mxjava.TestAction.action " ;
    }

}

完成后将其打包为test.jar,放在c盘根目录下。下面要做的就是利用URLClassLoader加载并运行TestAction的action方法,并将返回的值打印在控制台上。

新建一工程,引入testInterface.jar包。并创建一可执行类(main方法),在其中加入如下代码:

URL url  =   new  URL(“file:C: / test.jar”);
URLClassLoader myClassLoader 
=   new  URLClassLoader( new  URL[]  { url } );
Class myClass 
=  myClassLoader.loadClass(“com.mxjava.TestAction”);
ActionInterface action 
=  (ActionInterface)myClass.newInstance();
System.out.println(action.action());

  在上面的例子中,首先利用URLClassLoader加载了C:\test.jar包,将其中的com.mxjava.TestAction类载入内存,将其强制转型为testInterface包中的ActionInterface类型,最后调用其action方法,并打印到控制台中。

  执行程序后,在控制台上如期打印出我们想要的内容。但是,事情并没有那么简单,当我们将该代码移动web应用中时,就会抛出异常。原来,Java为我们提供了三种可选择的ClassLoader:
1. 系统类加载器或叫作应用类加载器 (system classloader or application classloader)
2. 当前类加载器
3. 当前线程类加载器

  在上例中我们使用javac命令来运行该程序,这时候使用的是系统类加载器 (system classloader)。这个类加载器处理 -classpath下的类加载工作,可以通过ClassLoader.getSystemClassLoader()方法调用。 ClassLoader 下所有的 getSystemXXX()的静态方法都是通过这个方法定义的。在代码中,应该尽量少地调用这个方法,以其它的类加载器作为代理。否则代码将只能工作在简单的命令行应用中。当在web应用中时,服务器也是利用ClassLoader来加载class的,由于ClassLoader的不同,所以在强制转型时JVM认定不是同一类型。(在JAVA中,一个类用其完全匹配类名(fully qualified class name)作为标识,这里指的完全匹配类名包括包名和类名。但在JVM中一个类用其全名和一个加载类ClassLoader的实例作为唯一标识。因此,如果一个名为Pg的包中,有一个名为Cl的类,被类加载器KlassLoader的一个实例kl1加载,Cl的实例,即C1.class在JVM中表示为(Cl, Pg, kl1)。这意味着两个类加载器的实例(Cl, Pg, kl1) 和 (Cl, Pg, kl2)是不同的,被它们所加载的类也因此完全不同,互不兼容的。)为了能够使程序正确运行,我们首要解决的问题就是,如何将URLClassLoader加载的类,同当前ClassLoader保持在同一类加载器中。解决方法很简单,利用java提供的第三种ClassLoader—当前线程类加载器即可。jdk api文档就会发现,URLClassLoader提供了三种构造方式:

// 使用默认的委托父 ClassLoader 为指定的 URL 构造一个新 URLClassLoader。 
URLClassLoader(URL[] urls) 
// 为给定的 URL 构造新 URLClassLoader。 
URLClassLoader(URL[] urls, ClassLoader parent) 
// 为指定的 URL、父类加载器和 URLStreamHandlerFactory 创建新 URLClassLoader。
URLClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) 

接下来要做的就是,在构造URLClassLoader时,将当前线程类加载器置入即可。如下:

URLClassLoader myClassLoader  =   new  URLClassLoader( new  URL[]  { url } , Thread.currentThread().getContextClassLoader());

总结:
  
Java是利用ClassLoader来加载类到内存的,ClassLoader本身是用java语言写的,所以我们可以扩展自己的ClassLoader。利用URLClassLoader可以加载指定jar包中的类到内存。在命行上利用URLClassLoader加载jar时,是使用系统类加载器来加载class的,所以在web环境下,就会出错。这是因为JVM中一个类用其全名和一个加载类ClassLoader的实例作为唯一标识的。我们只要利用URLClassLoader的第二种构造方法并传入当前线程类加载器即可解决。

posted @ 2007-04-06 16:55 chenweicai 阅读(307) | 评论 (0)编辑 收藏

 

 不变模式(Immutable Pattern)顾名思义,它的状态在它的生命周期内是永恒的(晕,永恒的日月星晨,对象如人,
太渺小,谈不上永恒!
),不会改变的.对于其中的不变类(Immutable Class),它的实例可以在运行期间保持状态永远不会被
改变,所以不需要采取共享互斥机制来保护,如果运用得当可以节省大量的时间成本.

 请注意上面这段话,不变模式其中的不变类,说明不变类只是不变模式中一个组成部分,不变类和与之相辅的可变
类,以及它们之间的关系才共同构成不变模式!所以在涉及不变模式的时候一定要研究一个类是不变的还是可变的(Mutable).
在jdk中的String类和StringBuffer类就组成了一个不变模式.

还是先看具体的例子:

final class Dog{
    private final String name;
    private final int age;
    public Dog(String name,int age){
        this.name = name;
        this.age = age;
    }
   
    public String getName(){return this.name;}
    public int getAge(){return this.age;}
   
    public String toString(){
        return "Dog's name = " + this.name + ",age = " + this.age;
    }
}

1.Dog类本身被声明为final,可以保证它本身的状态不会被子类扩展方法所改变.
2.Dog类的所有成员变量都是final的,保证它在构造后不会被重新赋值.而且Dog类所有属性是private的,只提供getter访问.
3.Dog类的能传入的参数本身是Immutable的.这一点非常重要将在下面具体说明.
以上条件都是必要条件,而不是充要条件.

class DisplayDog extends Thread{
    private Dog dog;
    public DisplayDog(Dog dog){
        this.dog = dog;
    }
   
    public void run(){
        while(true){
            System.out.println(this.getName() + " display: " + dog);
        }
    }
}

DisplayDog类是把一个Dog对象传入后,不断显示这个dog的属性.我们会同时用多个线程来显示同一dog对象,看看它们在共享
同一对象时对象的状态:
public class Test {
    public static void main(String[] args) throws Exception {
        Dog dog = new Dog("Sager",100);
        new DisplayDog(dog).start();
        new DisplayDog(dog).start();
        new DisplayDog(dog).start();
    }
}
运行这个例子你可以等上一个月,虽然运行一年都正常并不能说明第366天不出现异常,但我们可以把这样的结果认为是一种
说明.多个线程共享一个不变类的实例时,这个实例的状态不会发生改变.事实上它没有地方让你去改变.
在临界区模式中有些操作必须只允许有一个线程操作,而这个类本身以及对它的访问类中并不需要进行临界区保护,这就让多
个线程不必等待从而提高了性能.

既然有这么好的优势,那我们在需要临界区保护的对象为什么不都设计成不变类呢?

1.不变类设计起来有一定难度.对于上面这个用来示例的Dog,由于其本身的属性,方法都很简单,我们还可以充分地考虑到可
以改变它状态的各种情况.但对于复杂的类,要保证它的不变性,是一个非常吃力的工作.
 
不变类中,任何一个必要都件都不是充要条件,虽然连老骨灰都没有这么说过,但我还是要真诚地目光深邃语气凝重地告诉你.
没有任何条件是充要条件的意思就是如果任何一个必要条件你没考虑到,那它就会无法保证类的不可变性.没有规范,没有模
板,完全看一人设计人员的经验和水平.也许你自以为考虑很全面的一个"不变类"在其他高手面前轻而易举地就"可变"了.

2.不变类的种种必要条件限制了类设计的全面性,灵活性.这点不用多说,简单说因为是不变类,所以你不能A,因为是不变类,你
不能B.

当然,如果你是一人很有经验的设计者,你能成功地设计一个不变类,但因为它的限制而失去一些功能,你就要以使用与之相辅
的可变类.并且它们之间可以相互转换,在需要不变性操作的时候以不变类提供给用户,在需要可变性操作的时候以可变类提供
给用户.

在jdk中String被设计为不可变类,一旦生成一个String对象,它的所有属性就不会被变,任何方法要么返回这个对象本身的原
始状态,要么抛弃原来的字符串返回一个新字符串,而绝对不会返回被修改了的字符串对象.
但是很多时候返回新字符串抛弃原来的字符串对象这样的操作太浪费资源了.特别是在循环地操作的时候:

 String s = "Axman";
 for(int i=0;i<1000*1000;i++) s += "x";这样的操作是致命的.
那么这种时候需要将原始的不变的s包装成可变的StringBuffer来操作,性能的改变可能是成千上万倍:

        StringBuffer sb = new StringBuffer(s); //将不变的String包装成可变的String;
        for(int i=0;i<1000*1000;i++)
            sb.append("x");
        s = new String(sb); //将可变类封装成不变类.虽然可以调用toString(),但那不是可变到不变的转换.

在将可变类封装到不变类的时候要特别小心.因为你传入的引用在外面是可以被修改的.所以即使你不变类本身不能去改变属
性但属性有一个外部引用.可以在外面修改:

final class MutableDog{
    private String name;
    private int age;
    public MutableDog(String name,int age){
        this.name = name;
        this.age = age;
    }
    public synchronized void setDog(String name,int age){
        this.name = name;
        this.age = age;
    }
    public String getName(){return this.name;}
    public int getAge(){return this.age;}

    public synchronized String toString(){
        return "Dog's name = " + this.name + ",age = " + this.age;
    }
   
     public synchronized ImmatableDog getImmatableDog(){
         return new ImmatableDog(this);
     }
}

final class ImmatableDog{
    private final String name;
    private final int age;
    public ImmatableDog(String name,int age){
        this.name = name;
        this.age = age;
    }
   
    public ImmatableDog(MutableDog dog){
        this.name = dog.getName();
        this.age = dog.getAge();
    }   
   
    public String getName(){return this.name;}
    public int getAge(){return this.age;}
   
    public String toString(){
        return "Dog's name = " + this.name + ",age = " + this.age;
    }
}


MutableDog类是可变的,可以满足我们利用对象的缓冲来让对象成为表示另一个实体的功能.但它们之间
随时可以根据需要相互转换,但是我们发现:
    public ImmatableDog(MutableDog dog){
        this.name = dog.getName();
        this.age = dog.getAge();
    }
这个方法是不安全的.当一个属性为"Sager",100的dog被传进来后,执行this.name = dog.getName();后,
如果线程切换到其它线程执行,那么dog的属性就可能是"p4",80,这时再执行this.age = dog.getAge();
我们就会得到一个属性为"Sager",80的这样一个错误的不可变对象.这是一个非常危险的陷井.在这里我们
可以通过同上来解决:
    public ImmatableDog(MutableDog dog){
        synchronized(dog){
            this.name = dog.getName();
            this.age = dog.getAge();
        }
    }
注意这里同步的MutableDog,它将会和MutableDog的setDog产生互斥.它们都需要获取同一MutableDog对象的
锁,如果MutableDog的setDog不是方法同步(synchronized(this)),即使ImmatableDog(MutableDog dog)中同步
了dog,也不能保证安全,它们需要在同一对象上互斥.

posted @ 2007-03-29 15:58 chenweicai 阅读(118) | 评论 (0)编辑 收藏

简单工厂模式(缺点:每增加一个具体产品时 ,就要修改工厂方法,工厂方法负责了所有具体产品的创建)

举个例子:
------------------
public interface Fruit {

 void grow();
 
 void harvest();
 
 void plant();
}
-------------------
public class Apple implements Fruit {

 private int treeAge;
 
 public void grow() {
  // TODO Auto-generated method stub
  System.out.println("Apple is growing...");
  
 }

 public void harvest() {
  // TODO Auto-generated method stub
  System.out.println("Apple has been harvested.");
 }

 public void plant() {
  // TODO Auto-generated method stub
  System.out.println("Apple has been planted.");
  
 }

 public int getTreeAge() {
  return treeAge;
 }

 public void setTreeAge(int treeAge) {
  this.treeAge = treeAge;
 }
 
}
-------------------
public class Grape implements Fruit {

 private boolean seedless;
 
 
 
 public boolean isSeedless() {
  return seedless;
 }

 public void setSeedless(boolean seedless) {
  this.seedless = seedless;
 }

 public void grow() {
  // TODO Auto-generated method stub
  System.out.println("Grape is growing...");
 }

 public void harvest() {
  // TODO Auto-generated method stub
  System.out.println("Grape has been harvested.");
 }

 public void plant() {
  // TODO Auto-generated method stub
  System.out.println("Grape has been planted.");
  
 }

 
}


---------------------------
public class Strawberry implements Fruit {

 public void grow() {
  // TODO Auto-generated method stub
  System.out.println("Strawberry is growing...");
 }

 public void harvest() {
  // TODO Auto-generated method stub
  System.out.println("Strawberry has been harvested.");
 }

 public void plant() {
  // TODO Auto-generated method stub
  System.out.println("Strawberry has been planted.");
 }
}

-------------------------
public class FruitGardener {

 //静态工厂方法
 public static Fruit factory(String which) throws BadFruitException{
  if(which.equalsIgnoreCase("apple")){
   return new Apple();
  }
  else if(which.equalsIgnoreCase("strawberry")){
   return new Strawberry();
  }
  else if(which.equalsIgnoreCase("grape")){
   return new Grape();
  }
  else{
   throw new BadFruitException("Bad fruit request");
  }
 }
}

---------------------------
public class BadFruitException extends Exception {

 public BadFruitException(String msg){
  super(msg);
 }
}


--------------------------
public class Client {

 public static void main(String[] args){
  
  try{
   Fruit apple = (Fruit)FruitGardener.factory("Apple");
   System.out.println("apple is class: " + apple.getClass().getName());
   apple.plant();
   apple.grow();
   apple.harvest();
   System.out.println();
   
   Fruit grape = (Fruit)FruitGardener.factory("grape");
   System.out.println("grape is class: " + grape.getClass().getName());
   grape.plant();
   grape.grow();
   grape.harvest();
   System.out.println();
   
   Fruit strawberry = (Fruit)FruitGardener.factory("strawberry");
   System.out.println("strawberry is class: " + strawberry.getClass().getName());
   strawberry.plant();
   strawberry.grow();
   strawberry.harvest();
   
  }catch(BadFruitException e){
   e.printStackTrace();
  }
 }
}


2 工厂方法模式:解决了简单工厂模式的缺点, 将每一个具体产品的创建工作交给对应的具体工厂角色去解决

举个例子:
---------------------
public interface FruitGardener {

 public Fruit factory();
}


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

public class AppleGardener implements FruitGardener{

 public Fruit factory() {
  return new Apple();
 }
}

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

public class GrapeGardener implements FruitGardener{

 public Fruit factory() {
  return new Grape();
 }
}


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

public class StrawGardener implements FruitGardener{

 public Fruit factory() {
  return new Strawberry();
 }
}

-----------------
public class Client {

 private static FruitGardener applegardener, grapegardener, strawgardener;
 private static Fruit apple, grape, strawberry;
 
 public static void main(String[] args){
  applegardener = new AppleGardener();
  apple = applegardener.factory();
  System.out.println("apple is class: " + apple.getClass().getName());
  apple.plant();
  apple.grow();
  apple.harvest();
  System.out.println();
  
  grapegardener = new GrapeGardener();
  grape = grapegardener.factory();
  System.out.println("grape is class: " + grape.getClass().getName());
  grape.plant();
  grape.grow();
  grape.harvest();
  System.out.println();

  strawgardener = new StrawGardener();
  strawberry = strawgardener.factory();
  System.out.println("strawberry is class: " + strawberry.getClass().getName());
  strawberry.plant();
  strawberry.grow();
  strawberry.harvest();
 }
}


3 抽象工厂模式:解决多产品簇问题

举个例子:

-------------------
public interface Fruit {

// public String getName();
}

public class NorthernFruit implements Fruit{

 private String name;

 public String getName() {
  return name;
 }

 public void setName(String name) {
  this.name = name;
 }

 public NorthernFruit(String name) {
  super();
  // TODO Auto-generated constructor stub
  this.name = name;
 }
 
 
}



public class TropicalFruit implements Fruit{

 private String name;

 public String getName() {
  return name;
 }

 public void setName(String name) {
  this.name = name;
 }

 public TropicalFruit(String name) {
  super();
  // TODO Auto-generated constructor stub
  this.name = name;
 }
 
 
}



public interface Veggie {

}


public class TropicalVeggie implements Veggie{
 
 private String name;

 public String getName() {
  return name;
 }

 public void setName(String name) {
  this.name = name;
 }

 public TropicalVeggie(String name) {
  super();
  // TODO Auto-generated constructor stub
  this.name = name;
 }
 
 

}


public class NorthernVeggie implements Veggie{

 private String name;

 public String getName() {
  return name;
 }

 public void setName(String name) {
  this.name = name;
 }

 public NorthernVeggie(String name) {
  super();
  // TODO Auto-generated constructor stub
  this.name = name;
 }
 
 
}

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

public interface Gardener {

 public Fruit createFruit(String name);
 
 public Veggie createVeggie(String name);
}

public class NorthernGardener implements Gardener {

 public Fruit createFruit(String name) {
  return new NorthernFruit(name);
 }

 public Veggie createVeggie(String name) {
  return new NorthernVeggie(name);
 }

 
}



public class TropicalGardener implements Gardener {

 public Fruit createFruit(String name) {
  return new TropicalFruit(name);
 }

 public Veggie createVeggie(String name) {
  return new TropicalVeggie(name);
 }

 
}


public class Client {

 private static Gardener tropicalgardener ,northerngardener;
 private static Fruit northernfruit, tropicalfruit;
 private static Veggie northernveggie, tropicalveggie;
 
 public static void main(String[] args){
  tropicalgardener = new TropicalGardener();
  tropicalfruit = tropicalgardener.createFruit("tropicalfruit");
  //System.out.println(tropicalfruit.getName());
  tropicalveggie = tropicalgardener.createVeggie("tropicalveggie");
  
  northerngardener = new NorthernGardener();
  northernfruit = northerngardener.createFruit("northernfruit");
  northernveggie = northerngardener.createVeggie("northernveggie");
 }
 
}

posted @ 2007-03-29 13:18 chenweicai 阅读(285) | 评论 (0)编辑 收藏

假设在你的应用中使用一些对象,你如何拷贝你的对象呢?最明显的方法是讲一个对象简单的赋值给另一个,就像这样:

    obj2 = obj1;
    但是这个方法实际上没有拷贝对象而仅仅是拷贝了一个对象引用,换换言之,在你执行这个操作后仍然只有一个对象,但是多出了一个对该对象的引用。

    如果这个看似明显的方法不能正常工作,那么如何实际的拷贝一个对象呢?为什么不试试Object.clone呢?这个方法对Object的所有子类都是可用的。例如:

package clone.clone1;

public class ClassA implements Cloneable{//要继承Cloneable接口,否则会抛出异常
 private int x;

 public ClassA(int x) {
  super();
  // TODO Auto-generated constructor stub
  this.x = x;
 }

 //要覆盖clone方法,因为Object.clone()是protect类型
    //Object.clone完成的是对象的“浅”拷贝,即简单的成员到成员的拷贝。
 //它不做“深度”拷贝,即成员或者数组指向的对象的递归拷贝
 public Object clone(){
  // TODO Auto-generated method stub
  try{
   return super.clone();
  }catch(CloneNotSupportedException e){
   throw new InternalError(e.toString());
  }
 }

 public int getX() {
  return x;
 }

 public void setX(int x) {
  this.x = x;
 }
 
 
}


package clone.clone1;

public class Test {

 /**
  * @param args
  */
 public static void main(String[] args) {
  // TODO Auto-generated method stub
   ClassA a = new ClassA(12);
   ClassA b = (ClassA)a.clone();
   ClassA c = a;//此处多出了一个对该对象的引用,并没有实现克隆原对象
   System.out.println("Object A's X is :" + a.getX());
   System.out.println("Object B's X is :" + b.getX());
   System.out.println("Object C's X is :" + c.getX());
   a.setX(16);
   b.setX(18);
   System.out.println("After set,Object A's X is :" + a.getX());
   System.out.println("After set,Object B's X is :" + b.getX());
   System.out.println("After set,Object C's X is :" + c.getX());
 }
}


Object.clone完成的是对象的“浅”拷贝,即简单的成员到成员的拷贝。它不做“深度”拷贝,即成员或者数组指向的对象的递归拷贝。
package clone.clone2;

import java.util.HashMap;

public class ClassA implements Cloneable{
 
 public HashMap hashmap;//成员对象不是简单的数据,而是复杂的数据类型

 public ClassA() {
  hashmap = new HashMap();
  hashmap.put("key1", "value1");
  hashmap.put("key2", "value2");
 }
 
 public Object clone(){//不能简单的调用super.clone方法,
  //否则在新的对象中的复杂数据成员只是原对象的一个引用
  try{
   ClassA obj = (ClassA)super.clone();
   obj.hashmap = (HashMap)hashmap.clone();
   return obj;
  }catch(CloneNotSupportedException e){
  throw new InternalError(e.toString());
  }
 }

}


package clone.clone2;

public class test {

 public static void main(String[] args) {
  // TODO Auto-generated method stub

  ClassA a = new ClassA();
  ClassA b = (ClassA)a.clone();
  a.hashmap.remove("key1");
  System.out.println(b.hashmap.get("key1"));
  System.out.println(a.hashmap.get("key1"));
 }

}

posted @ 2007-03-27 19:37 chenweicai 阅读(452) | 评论 (1)编辑 收藏

 观察者模式是对象的行为模式,顾名思义,即存在观察者和被观察者。 观察者模式可以让多个观察者同时监听同一个被观察对象,当被观察对象发生变化时,并通知所有观察者,使各个观察者能作出相应的响应。适当地运用观察者模式,能提高自身代码的设计水平。

  观察者模式理解和编码都比较简单,通常包括以下步骤:

  1. 设计观察者接口类;
  2. 观察者类实现该接口;
  3. 设计被观察者抽象类,该类中提供一些方法,如:添加观察者对象,删除观察者对象,把事件通知给各个观察者对象;
  4. 设计被观察者类,继承被观察者抽象类,在该类中,可以根据需要在该类中,可以定义方法:被观察者是否发生变化

  以上四步,即完成了观察者模式的设计。下面代码分类进行描述以上步骤:

package Observer;

import java.util.Enumeration;
import java.util.Vector;

abstract public class Subject {

 private Vector observersVector = new java.util.Vector();
 
 public void attach(Observer observer){
  observersVector.addElement(observer);
  System.out.println("Attached an observer.");
 }
 
 public void detach(Observer observer){
  observersVector.removeElement(observer);
 }
 
 public void notifyObservers(){
  java.util.Enumeration enumeration = observers();
  while(enumeration.hasMoreElements()){
   System.out.println("Before notifying");
   ((Observer)enumeration.nextElement()).update();
  }
 }
 
 public Enumeration observers(){
  return ((java.util.Vector)observersVector.clone()).elements();
 }
}


package Observer;

public class ConcreteSubject extends Subject{

 private String state;
 
 public void change(String newState){
  state = newState;
  this.notifyObservers();
 }
}


package Observer;

public interface Observer {

 void update();
}


package Observer;

public class ConcreteObserver implements Observer{
 
 public void update(){
  System.out.println("I am notified.");
 }

}


package Observer;

public class Client {

 private static ConcreteSubject subject;
 private static Observer observer1;
 private static Observer observer2;
 
 public static void main(String[] args){
  subject = new ConcreteSubject();
  observer1 = new ConcreteObserver();
  observer2 = new ConcreteObserver();
  
  subject.attach(observer1);
  subject.attach(observer2);
  
  subject.change("new state");
 }
}


运行结果是:

Attached an observer.
Attached an observer.
Before notifying
I am notified.
Before notifying
I am notified.


在java中提供了Observerable类和Observer接口来实现观察者模式

public class Observable
extends Object

此类表示模型视图范例中的 observable 对象,或者说“数据”。可将其子类化,表示应用程序想要观察的对象(即被观察者)。

一个 observable 对象可以有一个或多个观察者。观察者可以是实现了 Observer 接口的任意对象。一个 observable 实例改变后,调用 ObservablenotifyObservers 方法的应用程序会通过调用观察者的 update 方法来通知观察者该实例发生了改变。

未指定发送通知的顺序。Observable 类中所提供的默认实现将按照其注册的重要性顺序来通知 Observers,但是子类可能改变此顺序,从而使用非固定顺序在单独的线程上发送通知,或者也可能保证其子类遵从其所选择的顺序。

注意,此通知机制与线程无关,并且与 Object 类的 waitnotify 机制完全独立。

新创建一个 observable 对象时,其观察者集合是空的。当且仅当 equals 方法为两个观察者返回 true 时,才认为它们是相同的。

public interface Observer

一个可在观察者要得到 observable 对象更改通知时可实现 Observer 接口的类。 
 给个用java机制来实现观察者模式的例子
在java机制中Observable类相当于抽象主题角色,我们定义具体主题角色来继承该类
Observer 接口相当于抽象观察者角色,我们定义具体观察者角色来实现该接口
所以这两个抽象角色我们不用编写了 只需编写两个具体角色

//----具体观察对象角色
package Observer.javaObserver;

import java.util.Observable;

public class Product extends Observable{

 private String name;
 private float price;
 
 public String getName(){
  return name;
 }
 
 public void setName(String name){
  this.name = name;
  // 设置变化点
  setChanged();
  notifyObservers(name);//通知观察者
 }

 public float getPrice() {
  return price;
 }

 public void setPrice(float price) {
  this.price = price;
  //设置变化点
  setChanged();
  notifyObservers(new Float(price));
 }
 
 public void saveToDb(){
  System.out.println("saveToDb");
 }
}

//------------ 两个具体观察者角色
package Observer.javaObserver;

import java.util.Observable;
import java.util.Observer;

public class NameObserver implements Observer {

 private String name = null;
 
 public void update(Observable obj, Object arg){
  if(arg instanceof String){
   name = (String)arg;
   System.out.println("NameObserver:name changed to " + name);
  }
 }
}

package Observer.javaObserver;

import java.util.Observable;
import java.util.Observer;

public class PriceObserver implements Observer{

 private float price=0;
 
 public void update(Observable obj, Object arg){
  if(arg instanceof Float){
   price = ((Float)arg).floatValue();
   System.out.println("PriceObserver: price change to" + price);
  }
 }
}

//----测试程序

package Observer.javaObserver;

import java.util.Observer;

public class Test {

 public static void main(String[] args){
  Product product = new Product();
  Observer nameObs = new NameObserver();
  Observer priceObs = new PriceObserver();
  
  product.addObserver(nameObs);
  product.addObserver(priceObs);
  
  product.setName("apple");
  product.setPrice(9.22f);
  
  product.setName("Apple");
  product.setPrice(9.88f);
  }
}


//---运行结果

NameObserver:name changed to apple
PriceObserver: price change to9.22
NameObserver:name changed to Apple
PriceObserver: price change to9.88



posted @ 2007-03-24 17:03 chenweicai 阅读(872) | 评论 (0)编辑 收藏

对于这个系列里的问题,每个学Java的人都应该搞懂。当然,如果只是学Java玩玩就无所谓了。如果你认为自己已经超越初学者了,却不很懂这些问题,请将你自己重归初学者行列。内容均来自于CSDN的经典老贴。

问题一:我声明了什么!

String s = "Hello world!";

许多人都做过这样的事情,但是,我们到底声明了什么?回答通常是:一个String,内容是“Hello world!”。这样模糊的回答通常是概念不清的根源。如果要准确的回答,一半的人大概会回答错误。
这个语句声明的是一个指向对象的引用,名为“s”,可以指向类型为String的任何对象,目前指向"Hello world!"这个String类型的对象。这就是真正发生的事情。我们并没有声明一个String对象,我们只是声明了一个只能指向String对象的引用变量。所以,如果在刚才那句语句后面,如果再运行一句:

String string = s;

我们是声明了另外一个只能指向String对象的引用,名为string,并没有第二个对象产生,string还是指向原来那个对象,也就是,和s指向同一个对象。

问题二:"=="和equals方法究竟有什么区别?

==操作符专门用来比较变量的值是否相等。比较好理解的一点是:
int a=10;
int b=10;
则a==b将是true。
但不好理解的地方是:
String a=new String("foo");
String b=new String("foo");
则a==b将返回false。

根据前一帖说过,对象变量其实是一个引用,它们的值是指向对象所在的内存地址,而不是对象本身。a和b都使用了new操作符,意味着将在内存中产生两个内容为"foo"的字符串,既然是“两个”,它们自然位于不同的内存地址。a和b的值其实是两个不同的内存地址的值,所以使用"=="操作符,结果会是false。诚然,a和b所指的对象,它们的内容都是"foo",应该是“相等”,但是==操作符并不涉及到对象内容的比较。
对象内容的比较,正是equals方法做的事。

看一下Object对象的equals方法是如何实现的:
boolean equals(Object o){

return this==o;

}
Object对象默认使用了==操作符。所以如果你自创的类没有覆盖equals方法,那你的类使用equals和使用==会得到同样的结果。同样也可以看出,Object的equals方法没有达到equals方法应该达到的目标:比较两个对象内容是否相等。因为答案应该由类的创建者决定,所以Object把这个任务留给了类的创建者。

看一下一个极端的类:
Class Monster{
private String content;
...
boolean equals(Object another){ return true;}

}
我覆盖了equals方法。这个实现会导致无论Monster实例内容如何,它们之间的比较永远返回true。

所以当你是用equals方法判断对象的内容是否相等,请不要想当然。因为可能你认为相等,而这个类的作者不这样认为,而类的equals方法的实现是由他掌握的。如果你需要使用equals方法,或者使用任何基于散列码的集合(HashSet,HashMap,HashTable),请察看一下java doc以确认这个类的equals逻辑是如何实现的。

问题三:String到底变了没有?

没有。因为String被设计成不可变(immutable)类,所以它的所有对象都是不可变对象。请看下列代码:

String s = "Hello";
s = s + " world!";

s所指向的对象是否改变了呢?从本系列第一篇的结论很容易导出这个结论。我们来看看发生了什么事情。在这段代码中,s原先指向一个String对象,内容是"Hello",然后我们对s进行了+操作,那么s所指向的那个对象是否发生了改变呢?答案是没有。这时,s不指向原来那个对象了,而指向了另一个String对象,内容为"Hello world!",原来那个对象还存在于内存之中,只是s这个引用变量不再指向它了。
通过上面的说明,我们很容易导出另一个结论,如果经常对字符串进行各种各样的修改,或者说,不可预见的修改,那么使用String来代表字符串的话会引起很大的内存开销。因为String对象建立之后不能再改变,所以对于每一个不同的字符串,都需要一个String对象来表示。这时,应该考虑使用StringBuffer类,它允许修改,而不是每个不同的字符串都要生成一个新的对象。并且,这两种类的对象转换十分容易。
同时,我们还可以知道,如果要使用内容相同的字符串,不必每次都new一个String。例如我们要在构造器中对一个名叫s的String引用变量进行初始化,把它设置为初始值,应当这样做:
public class Demo {
private String s;
...
public Demo {
s = "Initial Value";
}
...
}
而非
s = new String("Initial Value");
后者每次都会调用构造器,生成新对象,性能低下且内存开销大,并且没有意义,因为String对象不可改变,所以对于内容相同的字符串,只要一个String对象来表示就可以了。也就说,多次调用上面的构造器创建多个对象,他们的String类型属性s都指向同一个对象。
上面的结论还基于这样一个事实:对于字符串常量,如果内容相同,Java认为它们代表同一个String对象。而用关键字new调用构造器,总是会创建一个新的对象,无论内容是否相同。
至于为什么要把String类设计成不可变类,是它的用途决定的。其实不只String,很多Java标准类库中的类都是不可变的。在开发一个系统的时候,我们有时候也需要设计不可变类,来传递一组相关的值,这也是面向对象思想的体现。不可变类有一些优点,比如因为它的对象是只读的,所以多线程并发访问也不会有任何问题。当然也有一些缺点,比如每个不同的状态都要一个对象来代表,可能会造成性能上的问题。所以Java标准类库还提供了一个可变版本,即StringBuffer。

问题四:final关键字到底修饰了什么?

final使得被修饰的变量"不变",但是由于对象型变量的本质是“引用”,使得“不变”也有了两种含义:引用本身的不变,和引用指向的对象不变。

引用本身的不变:
final StringBuffer a=new StringBuffer("immutable");
final StringBuffer b=new StringBuffer("not immutable");
a=b;//编译期错误

引用指向的对象不变:
final StringBuffer a=new StringBuffer("immutable");
a.append(" broken!"); //编译通过

可见,final只对引用的“值”(也即它所指向的那个对象的内存地址)有效,它迫使引用只能指向初始指向的那个对象,改变它的指向会导致编译期错误。至于它所指向的对象的变化,final是不负责的。这很类似==操作符:==操作符只负责引用的“值”相等,至于这个地址所指向的对象内容是否相等,==操作符是不管的。

理解final问题有很重要的含义。许多程序漏洞都基于此----final只能保证引用永远指向固定对象,不能保证那个对象的状态不变。在多线程的操作中,一个对象会被多个线程共享或修改,一个线程对对象无意识的修改可能会导致另一个使用此对象的线程崩溃。一个错误的解决方法就是在此对象新建的时候把它声明为final,意图使得它“永远不变”。其实那是徒劳的。

问题五:到底要怎么样初始化!

本问题讨论变量的初始化,所以先来看一下Java中有哪些种类的变量。
1. 类的属性,或者叫值域
2. 方法里的局部变量
3. 方法的参数

对于第一种变量,Java虚拟机会自动进行初始化。如果给出了初始值,则初始化为该初始值。如果没有给出,则把它初始化为该类型变量的默认初始值。

int类型变量默认初始值为0
float类型变量默认初始值为0.0f
double类型变量默认初始值为0.0
boolean类型变量默认初始值为false
char类型变量默认初始值为0(ASCII码)
long类型变量默认初始值为0
所有对象引用类型变量默认初始值为null,即不指向任何对象。注意数组本身也是对象,所以没有初始化的数组引用在自动初始化后其值也是null

对于两种不同的类属性,static属性与instance属性,初始化的时机是不同的。instance属性在创建实例的时候初始化,static属性在类加载,也就是第一次用到这个类的时候初始化,对于后来的实例的创建,不再次进行初始化。这个问题会在以后的系列中进行详细讨论。

对于第二种变量,必须明确地进行初始化。如果再没有初始化之前就试图使用它,编译器会抗议。如果初始化的语句在try块中或if块中,也必须要让它在第一次使用前一定能够得到赋值。也就是说,把初始化语句放在只有if块的条件判断语句中编译器也会抗议,因为执行的时候可能不符合if后面的判断条件,如此一来初始化语句就不会被执行了,这就违反了局部变量使用前必须初始化的规定。但如果在else块中也有初始化语句,就可以通过编译,因为无论如何,总有至少一条初始化语句会被执行,不会发生使用前未被初始化的事情。对于try-catch也是一样,如果只有在try块里才有初始化语句,编译部通过。如果在catch或finally里也有,则可以通过编译。总之,要保证局部变量在使用之前一定被初始化了。所以,一个好的做法是在声明他们的时候就初始化他们,如果不知道要出事化成什么值好,就用上面的默认值吧!

其实第三种变量和第二种本质上是一样的,都是方法中的局部变量。只不过作为参数,肯定是被初始化过的,传入的值就是初始值,所以不需要初始化。

问题六:instanceof是什么东东?

instanceof是Java的一个二元操作符,和==,>,<是同一类东东。由于它是由字母组成的,所以也是Java的保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例,返回boolean类型的数据。举个例子:

String s = "I AM an Object!";
boolean isObject = s instanceof Object;

我们声明了一个String对象引用,指向一个String对象,然后用instancof来测试它所指向的对象是否是Object类的一个实例,显然,这是真的,所以返回true,也就是isObject的值为True。
instanceof有一些用处。比如我们写了一个处理账单的系统,其中有这样三个类:

public class Bill {//省略细节}
public class PhoneBill extends Bill {//省略细节}
public class GasBill extends Bill {//省略细节}

在处理程序里有一个方法,接受一个Bill类型的对象,计算金额。假设两种账单计算方法不同,而传入的Bill对象可能是两种中的任何一种,所以要用instanceof来判断:

public double calculate(Bill bill) {
if (bill instanceof PhoneBill) {
//计算电话账单
}
if (bill instanceof GasBill) {
//计算燃气账单
}
...
}
这样就可以用一个方法处理两种子类。

然而,这种做法通常被认为是没有好好利用面向对象中的多态性。其实上面的功能要求用方法重载完全可以实现,这是面向对象变成应有的做法,避免回到结构化编程模式。只要提供两个名字和返回值都相同,接受参数类型不同的方法就可以了:

public double calculate(PhoneBill bill) {
//计算电话账单
}

public double calculate(GasBill bill) {
//计算燃气账单
}

所以,使用instanceof在绝大多数情况下并不是推荐的做法,应当好好利用多态。

 

posted @ 2007-03-23 20:17 chenweicai 阅读(145) | 评论 (0)编辑 收藏

仅列出标题
共6页: 上一页 1 2 3 4 5 6 下一页