java线程编程注意问题

8 篇文章 0 订阅
2 篇文章 0 订阅
Java的线程编程非常简单。但有时会看到一些关于线程的错误用法。下面列出一些应该注意的问题。

   Java的线程编程非常简单。但有时会看到一些关于线程的错误用法。下面列出一些应该注意的问题。

   1.同步对象的恒定性All java objects are references.

  对于局部变量和参数来说,java里面的int, float, double, boolean等基本数据类型,都在栈上。这些基本类型是无法同步的;java里面的对象(根对象是Object),全都在堆里,指向对象的reference在栈上。

  java中的同步对象,实际上是对于reference所指的“对象地址”进行同步。

  需要注意的问题是,千万不要对同步对象重新赋值。举个例子。

  class A implements Runnable{

  Object lock = new Object();

  void run(){

  for(...){

  synchronized(lock){

  // do something

  ...

  lock = new Object();   }   }   }   run函数里面的这段同步代码实际上是毫无意义的。因为每一次lock都给重新分配了新的对象的reference,每个线程都在新的reference同步。

  大家可能觉得奇怪,怎么会举这么一个例子。因为我见过这样的代码,同步对象在其它的函数里被重新赋了新值。

  这种问题很难查出来。

  所以,一般应该把同步对象声明为final.

  final Object lock = new Object();

  使用Singleton Pattern 设计模式来获取同步对象,也是一种很好的选择。

  2.如何放置共享数据实现线程,有两种方法,一种是继承Thread类,一种是实现Runnable接口。

  上面举的例子,采用实现Runnable接口的方法。本文推荐这种方法。

  首先,把需要共享的数据放在一个实现Runnable接口的类里面,然后,把这个类的实例传给多个Thread的构造方法。这样,新创建的多个Thread,都共同拥有一个Runnable实例,共享同一份数据。

  如果采用继承Thread类的方法,就只好使用static静态成员了。如果共享的数据比较多,就需要大量的static静态成员,令程序数据结构混乱,难以扩展。这种情况应该尽量避免。

  编写一段多线程代码,处理一个稍微复杂点的问题。两种方法的优劣,一试便知。

  3.同步的粒度线程同步的粒度越小越好,即,线程同步的代码块越小越好。尽量避免用synchronized修饰符来声明方法。尽量使用synchronized(anObject)的方式,如果不想引入新的同步对象,使用synchronized(this)的方式。而且,synchronized代码块越小越好。

  4.线程之间的通知这里使用“通知”这个词,而不用“通信”这个词,是为了避免词义的扩大化。

  线程之间的通知,通过Object对象的wait()和notify() 或notifyAll() 方法实现。

  下面用一个例子,来说明其工作原理:

  假设有两个线程,A和B。共同拥有一个同步对象,lock。

  1.首先,线程A通过synchronized(lock) 获得lock同步对象,然后调用lock.wait()函数,放弃lock同步对象,线程A停止运行,进入等待队列。

  2.线程B通过synchronized(lock) 获得线程A放弃的lock同步对象,做完一定的处理,然后调用 lock.notify() 或者lock.notifyAll() 通知等待队列里面的线程A。

  3.线程A从等待队列里面出来,进入ready队列,等待调度。

  4.线程B继续处理,出了synchronized(lock)块之后,放弃lock同步对象。

  5.线程A获得lock同步对象,继续运行。

  例子代码如下:

  public class SharedResource implements Runnable{

  Object lock = new Object();

  public void run(){

  // 获取当前线程的名称。

  String threadName = Thread.currentThread().getName();

  if( “A”.equals(threadName)){

  synchronized(lock){ //线程A通过synchronized(lock) 获得lock同步对象

  try{

  System.out.println(“ A gives up lock.”);

  lock.wait(); // 调用lock.wait()函数,放弃lock同步对象,

  // 线程A停止运行,进入等待队列。

  }catch(InterruptedException e){   }   // 线程A重新获得lock同步对象之后,继续运行。

  System.out.println(“ A got lock again and continue to run.”);

  } // end of synchronized(lock)   }   if( “B”.equals(threadName)){

  synchronized(lock){//线程B通过synchronized(lock) 获得线程A放弃的lock同步对象

  System.out.println(“B got lock.”);

  lock.notify(); //通知等待队列里面的线程A,进入ready队列,等待调度。

  //线程B继续处理,出了synchronized(lock)块之后,放弃lock同步对象。

  System.out.println(“B gives up lock.”);

  } // end of synchronized(lock)

  boolean hasLock = Thread.holdsLock(lock); // 检查B是否拥有lock同步对象。

  System.out.println(“B has lock ? -- ” hasLock); // false.   }   }   }   public class TestMain{

  public static void main(){

  Runnable resource = new SharedResource();

  Thread A = new Thread(resource,”A”);

  A.start();

  // 强迫主线程停止运行,以便线程A开始运行。

  try {

  Thread.sleep(500);

  }catch(InterruptedException e){   }   Thread B = new Thread(resource,”B”);

  B.start();   }   }

   5.跨类的同步对象对于简单的问题,可以把访问共享资源的同步代码都放在一个类里面。

  但是对于复杂的问题,我们需要把问题分为几个部分来处理,需要几个不同的类来处理问题。这时,就需要在不同的类中,共享同步对象。比如,在生产者和消费者之间共享同步对象,在读者和写者之间共享同步对象。

  如何在不同的类中,共享同步对象。有几种方法实现,

  (1)前面讲过的方法,使用static静态成员,(或者使用Singleton Pattern.)

  (2)用参数传递的方法,把同步对象传递给不同的类。

  (3)利用字符串常量的“原子性”。

  对于第三种方法,这里做一下解释。一般来说,程序代码中的字符串常量经过编译之后,都具有唯一性,即,内存中不会存在两份相同的字符串常量。

  (通常情况下,C ,C语言程序编译之后,也具有同样的特性。)

  比如,我们有如下代码。

  String A = “atom”;

  String B = “atom”;

  我们有理由认为,A和B指向同一个字符串常量。即,A==B。

  注意,声明字符串变量的代码,不符合上面的规则。

  String C= new String(“atom”);

  String D = new String(“atom”);

  这里的C和D的声明是字符串变量的声明,所以,C != D。

  有了上述的认识,我们就可以使用字符串常量作为同步对象。

  比如我们在不同的类中,使用synchronized(“myLock”), “myLock”.wait(),“myLock”.notify(), 这样的代码,就能够实现不同类之间的线程同步。

  本文并不强烈推荐这种用法,只是说明,有这样一种方法存在。

  本文推荐第二种方法,(2)用参数传递的方法,把同步对象传递给不同的类。

关于java多线程一些心得 - Java综合 - Java - ITeye论坛(转载)
分类: JAVA 2011-06-29 17:08 86人阅读 评论(0) 收藏 举报
1、主线程死掉之后 ,所有在主线程上启动的线程并不会死掉
2、通过把线程加入ThreadGroup可以操作其他同组线程的生命
3、如果想在子线程中杀掉主线程,直接把主线程的Thread的传入子线程即可
4、要想关闭多线程程序,需要把当前所有的线程全部杀死才能关闭
5、各个线程互补影响~父子线程也不会有影响



千言万语汇总就那么几句话

When a Java Virtual Machine starts up, there is usually a single non-daemon thread (which typically calls the method named main of some designated class). The Java Virtual Machine continues to execute threads until either of the following occurs:

* The exit method of class Runtime has been called and the security manager has permitted the exit operation to take place.
* All threads that are not daemon threads have died, either by returning from the call to the run method or by throwing an exception that propagates beyond the run method. 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值