科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道Java 线程应该注意的问题

Java 线程应该注意的问题

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

Java 线程应该注意的问题

作者: toumei 来源:赛迪网技术社区 2007年11月29日

关键字: 问题 java

  • 评论
  • 分享微博
  • 分享邮件
Java的线程编程非常简单。但有时会看到一些关于线程的错误用法。下面列出一些应该注意的问题。 bu>@p8 0  
,z;9C7o]  
   1.同步对象的恒定性All java objects are references. _Fr#Sw2A_  
9GBpNVZ-  
  对于局部变量和参数来说,java里面的int, float, double, boolean等基本数据类型,都在栈上。这些基本类型是无法同步的;java里面的对象(根对象是Object),全都在堆里,指向对象的reference在栈上。 Hb Ckj  
RQ c3  
  java中的同步对象,实际上是对于reference所指的“对象地址”进行同步。 "S7@R   
MWKgK^ J   
  需要注意的问题是,千万不要对同步对象重新赋值。举个例子。 OYV+ 1,  
E4rF0l  
  class A implements Runnable{ _tNDaU/m  
# Ww`  
  Object lock = new Object(); +Tg$1gI)  
i [X"ik  
  void run(){ &m-@3r g  
d'QQ sJDi(  
  for(...){ /EmxI5:Y  
uD,}gR,\  
  synchronized(lock){ $OFg6  
r/_%GROok  
  // do something BqBw,QL  
lyZJX P*  
  ... gWolSJp<h  
a^<,2(y[  
  lock = new Object();   }   }   }   run函数里面的这段同步代码实际上是毫无意义的。因为每一次lock都给重新分配了新的对象的reference,每个线程都在新的reference同步。 (yFLZ  
>Yi1B@@  
  大家可能觉得奇怪,怎么会举这么一个例子。因为我见过这样的代码,同步对象在其它的函数里被重新赋了新值。  2H=2e  
mNK#A  
  这种问题很难查出来。 Q[X~cV,*  
a8t/=Iu  
  所以,一般应该把同步对象声明为final. x'xZc#G@  
v"`Z<J]~!s  
  final Object lock = new Object(); xT?6=/iN  
 PG^*.R@i  
  使用Singleton Pattern 设计模式来获取同步对象,也是一种很好的选择。 u~I9Ob^  
\ZoMEoOR  
  2.如何放置共享数据实现线程,有两种方法,一种是继承Thread类,一种是实现Runnable接口。 WLG2kSPm  
+[mE.L0G  
  上面举的例子,采用实现Runnable接口的方法。本文推荐这种方法。 w),*<`'|2~  
e^4Viq/!  
  首先,把需要共享的数据放在一个实现Runnable接口的类里面,然后,把这个类的实例传给多个Thread的构造方法。这样,新创建的多个Thread,都共同拥有一个Runnable实例,共享同一份数据。 T"=qfoA/  
;!V<&nW^~  
  如果采用继承Thread类的方法,就只好使用static静态成员了。如果共享的数据比较多,就需要大量的static静态成员,令程序数据结构混乱,难以扩展。这种情况应该尽量避免。 kCcDg,4W  
Cw0UgjO\  
  编写一段多线程代码,处理一个稍微复杂点的问题。两种方法的优劣,一试便知。 {HrL{qy&F  
KwHLJ&9Xw  
  3.同步的粒度线程同步的粒度越小越好,即,线程同步的代码块越小越好。尽量避免用synchronized修饰符来声明方法。尽量使用synchronized(anObject)的方式,如果不想引入新的同步对象,使用synchronized(this)的方式。而且,synchronized代码块越小越好。 uy#cF]mSd  
xwr{M D@r  
  4.线程之间的通知这里使用“通知”这个词,而不用“通信”这个词,是为了避免词义的扩大化。 _ }WQWk  
&T&_ieU  
  线程之间的通知,通过Object对象的wait()和notify() 或notifyAll() 方法实现。 rMBwBfF7  
N.1!0|  
  下面用一个例子,来说明其工作原理: / eK/8=Az  
(,gUAer  
  假设有两个线程,A和B。共同拥有一个同步对象,lock。 _]Q"#EDp  
's\o} + 4  
  1.首先,线程A通过synchronized(lock) 获得lock同步对象,然后调用lock.wait()函数,放弃lock同步对象,线程A停止运行,进入等待队列。 S^dSL.lT/  
HCg|5(6  
  2.线程B通过synchronized(lock) 获得线程A放弃的lock同步对象,做完一定的处理,然后调用 lock.notify() 或者lock.notifyAll() 通知等待队列里面的线程A。 #I'(w  
Qt#s! 3  
  3.线程A从等待队列里面出来,进入ready队列,等待调度。 ;KaHu)_5  
8 w1R   
  4.线程B继续处理,出了synchronized(lock)块之后,放弃lock同步对象。 tz%#,Z.&sm  
m5Qs=aK  
  5.线程A获得lock同步对象,继续运行。 l; >6l.K  
F!WuW9x![  
  例子代码如下: !W`-+{\  
{PoeZM me  
  public class SharedResource implements Runnable{ 5~l"B"x  
Klg$gg?#  
  Object lock = new Object(); N$LGj>h  
}pzvP* _U  
  public void run(){ ,#2A P  
R[)'cHz.  
  // 获取当前线程的名称。 iHS43oR  
YUT%b84d^  
  String threadName = Thread.currentThread().getName(); [WOr7gF[7  
Q+P 0I  
  if( “A”.equals(threadName)){ M.IYXn|(U  
qY-I0}"6  
  synchronized(lock){ //线程A通过synchronized(lock) 获得lock同步对象 { l^l  
J`6q$h)  
  try{ 8s>!;)  
$pv8, G  
  System.out.println(“ A gives up lock.”); h,Y7)mJ{  
. ]6 #p  
  lock.wait(); // 调用lock.wait()函数,放弃lock同步对象, Y7bu G<]&  
p4f/h6E`  
  // 线程A停止运行,进入等待队列。 @:_:'U$R-E  
!ldwQiW  
  }catch(InterruptedException e){   }   // 线程A重新获得lock同步对象之后,继续运行。 yJ=/'?O  
z'w^*Wo}  
  System.out.println(“ A got lock again and continue to run.”); fcqg+:E"  
<e v"mSl  
  } // end of synchronized(lock)   }   if( “B”.equals(threadName)){ l)oEegT  
0}<a<lck  
  synchronized(lock){//线程B通过synchronized(lock) 获得线程A放弃的lock同步对象 Gy4ndq  
1`Msy*!_9  
  System.out.println(“B got lock.”); tjRdaU  
yh !G7i  
  lock.notify(); //通知等待队列里面的线程A,进入ready队列,等待调度。 y)l mbC&  
Bg+_LUm>6  
  //线程B继续处理,出了synchronized(lock)块之后,放弃lock同步对象。 SDPg0kTgr  
Z'}hLpN  
  System.out.println(“B gives up lock.”); 3\v1c-IW  
FQnRWNn  
  } // end of synchronized(lock) R ctf~?  
^0wONKC  
  boolean hasLock = Thread.holdsLock(lock); // 检查B是否拥有lock同步对象。 Dr,/ So  
N<eh=6  
  System.out.println(“B has lock ? -- ” hasLock); // false.   }   }   }   public class TestMain{ 4YXPAp@e<  
kI5y6Fc  
  public static void main(){ F.MR&SLci  
}/Iy*P(~0  
  Runnable resource = new SharedResource(); aI -Zu  
Qf&wz9$SQ.  
  Thread A = new Thread(resource,”A”); Mtmg=''B5  
R4{V 'T",  
  A.start(); e"VD6i@&  
9#;+TVTd,  
  // 强迫主线程停止运行,以便线程A开始运行。 DkY~*zulgC  
\4G,y,0Hi  
  try { <k]d+hC  
_ q@)q  
  Thread.sleep(500); rX\8Q[Ge  
#s]pS\ iU  
  }catch(InterruptedException e){   }   Thread B = new Thread(resource,”B”); GN p>" A@K  
#LK!+<2F !  
  B.start();   }   } >H2n<5NUq  
_o_R7'rKk  
   5.跨类的同步对象对于简单的问题,可以把访问共享资源的同步代码都放在一个类里面。 TE0h_pw1g  
cMb 2N$)  
  但是对于复杂的问题,我们需要把问题分为几个部分来处理,需要几个不同的类来处理问题。这时,就需要在不同的类中,共享同步对象。比如,在生产者和消费者之间共享同步对象,在读者和写者之间共享同步对象。 X\^-.&  
(4u9CZ`/  
  如何在不同的类中,共享同步对象。有几种方法实现, VK\P~nG%  
*9Z9u"CD  
  (1)前面讲过的方法,使用static静态成员,(或者使用Singleton Pattern.) < . aj  
4 *mzZI  
  (2)用参数传递的方法,把同步对象传递给不同的类。 Ay, rZ  
RO]mF3(  
  (3)利用字符串常量的“原子性”。 s~{bR)  
lH ,'jfxO  
  对于第三种方法,这里做一下解释。一般来说,程序代码中的字符串常量经过编译之后,都具有唯一性,即,内存中不会存在两份相同的字符串常量。 [N@'*M  
lUa]e'T  
  (通常情况下,C ,C语言程序编译之后,也具有同样的特性。) EvaQM$  
Jw+"lyICr  
  比如,我们有如下代码。 e:9B)._Y  
j7!t\+  
  String A = “atom”; z._%6~c\?  
iA0KGt|v  
  String B = “atom”; LwYJ@;>  
ZVJ{5c_'  
  我们有理由认为,A和B指向同一个字符串常量。即,A==B。 y+xx5x  
|xm9 w  
  注意,声明字符串变量的代码,不符合上面的规则。 zE<)!'M  
gxJ0~KA,B  
  String C= new String(“atom”); G;N0xG*em  
AR 1GHtE  
  String D = new String(“atom”); -6NrZ~.  
=t(.,y  
  这里的C和D的声明是字符串变量的声明,所以,C != D。 <7n( =P  
>` y%  
  有了上述的认识,我们就可以使用字符串常量作为同步对象。 N'ss~A\  
| N:N(V  
  比如我们在不同的类中,使用synchronized(“myLock”), “myLock”.wait(),“myLock”.notify(), 这样的代码,就能够实现不同类之间的线程同步。 )LvmNVnCo  
XGIsR 9J  
  本文并不强烈推荐这种用法,只是说明,有这样一种方法存在。 #h`9_*}  
L5)11ZE\gU  
  本文推荐第二种方法,(2)用参数传递的方法,把同步对象传递给不同的类。
查看本文来源
    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章