题记
使用JAVA也有些时间了,头脑里总会闪现出一些似是而非的问题。于是,我考虑建立一个专题,专门来讨论这些问题,初步把它定位在FAQ的形式上。这是第一篇,初步讨论关于同步的问题。
为什么需要同步?
以下是一简单计数器的例子:
<%@ page contentType="text/html;charset=GB2312" %>
<%@ page import="java.io.*" %>
<html>
<body>
<%!
public int number;
private String countFile;
public int number(){
try{
ServletContext application=getServletContext();
countFile=application.getRealPath("/");
BufferedReader file=new BufferedReader(new FileReader(countFile+"hits.txt"));
number=java.lang.Integer.parseInt(file.readLine());
number++;
}
catch(Exception e){
System.out.println(e);
}
return number;
}
public synchronized void counter(){
number();
try{
File f=new File(countFile+"hits.txt");
PrintWriter pw=new PrintWriter(new FileWriter(f));
pw.print(number);
pw.close();
}
catch(Exception e){
System.out.println(e);
}
}
%>
<%
if(session.isNew()){
counter();
}
out.println(number);
%>
</body>
</html>
关键词synchronized定义了同步的概念,它可以作为方法修饰符,亦可作为方法内语句。在多线程条件下它限制了对共享资源的访问。方法counter()锁存地获取和释放IO资源,即持有该对象的lock。这种机制可以避免一个线程在进行文件读写操作时,其他线程如果也操作该资源,会抛出异常的情况。
什么时候需要同步机制?
第一,如果对象的更新影响到只读方法,那么只读方法也应被定义为同步的,例如上面的IO操作。第二,如果两个或两个以上的线程都修改一个对象,那么把执行修改的方法定义为同步的。
减少同步
增加无谓的同步控制,几乎和遗漏必要的同步一样糟糕。据文献记载,没有使用同步机制的操作几乎要比使用这一机制的快数倍!原因在于锁存地获取和释放资源,不再通过monitorenter和monitorexit操作码来进行,而是会检查ACC_SYNCHRONIZED属性标记,这一操作是要花费相当的代价。并且,过渡的同步控制可能导致代码死锁,并发度降低。所以应该避免无谓的同步。
注意问题
synchronized关键词一般用于虚拟锁,锁住的不是方法或代码,而是调用这个方法的对象。例如,对于一个构造函数Test(int n),由于变量n的不同,可以构造出不同的Test对象a和b,如果a、b同时访问同步区域,结果只会使synchronized形同虚设!
a和b如果要同时访问同步方法,只有该同步方法为static时才能达到同步的目的,否则,a调用a的方法,b调用b的方法,即使方法声明为同步的,但在不同对象域中它们根本达不到同步的目的。