2009年1月29日星期四

<转帖>synchronized

一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。

二、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。

三、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。

四、第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。

五、以上规则对其它对象锁同样适用.

一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。

package ths;

public class Thread1 implements Runnable
{
public void run()
{
synchronized(this)
{
for (int i = 0; i < 5; i++)
{
System.out.println(Thread.currentThread().getName() + " synchronized loop " + i);
}
}
}
public static void main(String[] args)
{
Thread1 t1 = new Thread1();
Thread ta = new Thread(t1, "A");
Thread tb = new Thread(t1, "B");
ta.start();
tb.start();
}
}

结果:

A synchronized loop 0
A synchronized loop 1
A synchronized loop 2
A synchronized loop 3
A synchronized loop 4
B synchronized loop 0
B synchronized loop 1
B synchronized loop 2
B synchronized loop 3
B synchronized loop 4

二、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。
package ths;

public class Thread2
{
public void m4t1()
{
synchronized(this)
{
int i = 5;
while( i-- > 0)
{
System.out.println(Thread.currentThread().getName() + " : " + i);
try
{
Thread.sleep(500);
}
catch (InterruptedException ie)
{
}
}
}
}
public void m4t2()
{
int i = 5;
while( i-- > 0)
{
System.out.println(Thread.currentThread().getName() + " : " + i);
try
{
Thread.sleep(500);
}
catch (InterruptedException ie)
{
}
}
}
public static void main(String[] args)
{
final Thread2 myt2 = new Thread2();
Thread t1 = new Thread(
new Runnable()
{
public void run()
{
myt2.m4t1();
}
}, "t1");
Thread t2 = new Thread(
new Runnable()
{
public void run()
{
myt2.m4t2();
}
}, "t2");
t1.start();
t2.start();
}
}


结果:

t1 : 4
t2 : 4
t1 : 3
t2 : 3
t1 : 2
t2 : 2
t1 : 1
t2 : 1
t1 : 0
t2 : 0

四、第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。

//修改Thread2.m4t2()方法如下:

public synchronized void m4t2() {
int i = 5;
while( i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}

结果:

t1 : 4
t1 : 3
t1 : 2
t1 : 1
t1 : 0
t2 : 4
t2 : 3
t2 : 2
t2 : 1
t2 : 0

五、以上规则对其它对象锁同样适用:

package ths;

public class Thread3 {
class Inner {
private void m4t1() {
int i = 5;
while(i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : Inner.m4t1()=" + i);
try {
Thread.sleep(500);
} catch(InterruptedException ie) {
}
}
}
private void m4t2() {
int i = 5;
while(i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : Inner.m4t2()=" + i);
try {
Thread.sleep(500);
} catch(InterruptedException ie) {
}
}
}
}
private void m4t1(Inner inner) {
synchronized(inner) { //使用对象锁
inner.m4t1();
}
}
private void m4t2(Inner inner) {
inner.m4t2();
}
public static void main(String[] args) {
final Thread3 myt3 = new Thread3();
final Inner inner = myt3.new Inner();
Thread t1 = new Thread(
new Runnable() {
public void run() {
myt3.m4t1(inner);
}
}, "t1"
);
Thread t2 = new Thread(
new Runnable() {
public void run() {
myt3.m4t2(inner);
}
}, "t2"
);
t1.start();
t2.start();
}
}



}
public static void main(String[] args) {
final Thread3 myt3 = new Thread3();
final Inner inner = myt3.new Inner();
Thread t1 = new Thread(
new Runnable() {
public void run() {
myt3.m4t1(inner);
}
}, "t1"
);
Thread t2 = new Thread(
new Runnable() {
public void run() {
myt3.m4t2(inner);
}
}, "t2"
);
t1.start();
t2.start();
}
}



现在在Inner.m4t2()前面加上synchronized:

private synchronized void m4t2() {
int i = 5;
while(i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : Inner.m4t2()=" + i);
try {
Thread.sleep(500);
} catch(InterruptedException ie) {
}
}
}

结果:

尽管线程t1与t2访问了同一个Inner对象中两个毫不相关的部分,但因为t1先获得了对Inner的对象锁,所以t2对Inner.m4t2()的访问也被阻塞,因为m4t2()是Inner中的一个同步方法。

t1 : Inner.m4t1()=4
t1 : Inner.m4t1()=3
t1 : Inner.m4t1()=2
t1 : Inner.m4t1()=1
t1 : Inner.m4t1()=0
t2 : Inner.m4t2()=4
t2 : Inner.m4t2()=3
t2 : Inner.m4t2()=2
t2 : Inner.m4t2()=1
t2 : Inner.m4t2()=

<转帖>在JAVA 源程序中编写SQL语句时使用ORACLE 绑定变量

在JAVA 源程序中编写SQL语句时使用ORACLE 绑定变量( bind variable )

在JAVA中的SQL 语句的编写方面,没有使用ORACLE 绑定变量,很大程度上降低了数据库的性能,表现在两个方面:

1、SQL语句硬分析(Hard Parse)太多,严重消耗CPU资源,延长了SQL语句总的执行时间

SQL语句的执行过程分几个步骤:语法检查、分析、执行、返回结果。其中分析又分为硬分析(Hard Parse)和软分析(Soft Parse)。
一条SQL语句通过语法检查后,Oracle 会先去shared pool 中找是否有相同的sql,如果找着了,就叫软分析,然后执行SQL语句。
硬分析主要是检查该sql所涉及到的所有对象是否有效以及权限等关系,然后根据RBO或CBO模式生成执行计划,然后才执行SQL语句。
可以看出,硬分析比软分析多了很多动作,而这里面的关键是“在shared pool 中是否有相同的sql”,而这就取决于是否使用绑定变量。

2、共享池中SQL语句数量太多,重用性极低,加速了SQL语句的老化,导致共享池碎片过多。
共享池中不同的SQL语句数量巨大,根据LRU原则,一些语句逐渐老化,最终被清理出共享池;这样就导致shared_pool_size 里面命中率
下降,共享池碎片增多,可用内存空间不足。而为了维护共享池内部结构,需要使用latch,一种内部生命周期很短的lock,这将使用大量
的cpu 资源,使得性能急剧下降。
不使用绑定变量违背了oracle 的shared pool 的设计的原则,违背了这个设计用来共享的思想。

编写java 程序时,我们习惯都是定义JAVA 的程序变量,放入SQL 语句中,如
String v_id = 'xxxxx';
String v_sql = 'select name from table_a where id = ' + v_id ;

以上代码,看起来是使用了变量v_id ,但这却是java 的程序变量,而不是oracle 的绑定变量,语句传递到数据库后,此java 的程序变量
已经被替换成具体的常量值,变成:
select * from table_a where name = 'xxxxx' ;

假定这个语句第一次执行,会进行硬分析。后来,同一段java 代码中v_id 值发现变化(v_id = 'yyyyyy'),数据库又接收到这样的语句:
select * from table_a where name = 'yyyyyy' ;

ORACLE 并不认为以上两条语句是相同的语句,因此对第二条语句会又做一次硬分析。这两条语句的执行计划可是一样的!

其实,只需将以上java 代码改成以下这样,就使用了oracle 的绑定变量:
String v_id = 'xxxxx';
String v_sql = 'select name from table_a where id = ? '; //嵌入绑定变量
stmt = con.prepareStatement( v_sql );
stmt.setString(1, v_id ); //为绑定变量赋值
stmt.executeQuery();

在Java中,结合使用setXXX 系列方法,可以为不同数据类型的绑定变量进行赋值,从而大大优化了SQL 语句的性能。

<转帖>java连接常见数据库的连接字符串

1.MySQL(http://www.mysql.com)mm.mysql-2.0.2-bin.jar

Class.forName("org.gjt.mm.mysql.Driver");

cn=DriverManager.getConnection("jdbc:mysql://MyDbComputerNameOrIP:3306/myDatabaseName",sUsr,sPwd);

2.PostgreSQL(http://www.de.postgresql.org)pgjdbc2.jar

Class.forName("org.postgresql.Driver");

cn=DriverManager.getConnection("jdbc:postgresql://MyDbComputerNameOrIP/myDatabaseName",sUsr,sPwd);

3.Oracle(http://www.oracle.com/ip/deploy/database/oracle9i/)classes12.zip

Class.forName("oracle.jdbc.driver.OracleDriver");

cn=DriverManager.getConnection("jdbc:oracle:thin:@MyDbComputerNameOrIP:1521:ORCL",sUsr,sPwd);

4.Sybase(http://jtds.sourceforge.net)jconn2.jar

Class.forName("com.sybase.jdbc2.jdbc.SybDriver");

cn=DriverManager.getConnection("jdbc:sybase:Tds:MyDbComputerNameOrIP:2638",sUsr,sPwd);

//(Default-Username/Password:"dba"/"sql")

5.MicrosoftSQLServer(http://jtds.sourceforge.net)

Class.forName("net.sourceforge.jtds.jdbc.Driver");

cn=DriverManager.getConnection("jdbc:jtds:sqlserver://MyDbComputerNameOrIP:1433/master",sUsr,sPwd);

6.MicrosoftSQLServer(http://www.microsoft.com)

Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver");

cn=DriverManager.getConnection("jdbc:microsoft:sqlserver://MyDbComputerNameOrIP:1433;databaseName=master",sUsr,sPwd);

7.ODBC

Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

Connectioncn=DriverManager.getConnection("jdbc:odbc:"+sDsn,sUsr,sPwd);

8.DB2

Class.forName("Com.ibm.db2.jdbc.net.DB2Driver");

Stringurl="jdbc:db2://192.9.200.108:6789/SAMPLE"

cn=DriverManager.getConnection(url,sUsr,sPwd);

<转帖>Java 中的变量

Java 中的变量

作者:终南 <li.zhongnan@hotmail.com>

一、变量分类

Java语言规定4种类型的变量:

(1)实例变量

也就是在类中声明的不用static关键字修饰的变量。类的每一个实例保存在其变量中的值各不相同,与每个对象相关。由于每个实例都有一份该变量,因此称为实例变量。

(2)类变量

如果在类中用static关键字声明一个变量,那么该变量就是一个类变量。static关键字告诉Java系统该变量在内存中只有一份拷贝,无论类的那个实例访问该变量,所得到的都是同一个东西。即各个实例共享该变量,就好像类拥有该变量似的,因此称为类变量。

以上两类变量直接在类中声明,用来保存对象状态,被称之为类的域,或者域变量。可以用修饰符来修改域变量,对其访问权限作出限定。

(3)本地变量

类似于域变量,用来保存类方法中的临时状态。本地变量在类的方法中、或者方法内的语句块中,其作用域限于声明其的方法或者语句块内。声明一个变量不需要特殊的关键字来指定。

(4)参数

传递给方法的参数也使一种变量,通常参数声明放在方法名称后的括号内。需要注意的是,参数是变量,不是域,其只在相应的方法内可见。

二、变量的数据类型

变量的数据类型可以分为两大类:基本类型和对象类型。

Java语言支持8种基本类型:

byte:8位

short:16位

int:32位

long:64位

float:32位

double:64位

boolean:true或false,Java规范对其位数没有要求

char:16位,Unicode字符

从Java角度来说,对象类型就是其顶级父类为Object的类所代表的类型,通常就是定义的一个类。一个抽象出来的类。

为了方便对字符串的操作,Java提供了java.lang.String这个类。但是String是一个类,不是基本数据类型。

三、声明与初始化

Java是一种强类型的语言,这意味着在使用变量之前,必须先对其进行声明。声明意味着指定变量的类型,这样编译器就知道如何操作该变量了。

声明的语法比较简单,由变量的数据类型加变量名称组成,如:

int n;

表示声明了一个变量,其名称为n,其所代表的类型为int。

String name;

表示声明了一个变量,其名称为name,其所代表的类型对对象类型String。

声明一个变量仅仅代表了一个计划的开始,只是设计好了蓝图而已。在正式使用之前,还需要对变量进行初始化。

就好比你准备创建一所学校,声明表示你给其起了一个名称。这时候,当别人提起该学校的名称时,别人之时明白这是一所学校(数据类型),名称所代表的学校还是什么都没有,空空如也。初始化则意味着按照蓝图创建好了您想要的东西,意味着你的学校建起来了,有了雏形了。

具体到Java中,变量的声明意味着分配了小块内存,该内存将用来保存一个对象所占据内存的地址,该内所保存的是一个所声明类型的变量。变量初始化意味着系统给这个对象占据的内存分配了足够的地址,设置好了相关信息,并且将这个对象占据的内存地址保存在上述小块内存中。

从理论上来说,变量在使用之前分为声明和初始化两个步骤。但是,实际上这两个步骤并不是那么很明显的被区分开来。

例如,语句:

int n = 10;

就同时完成了这两个步骤:声明一个int变量n,同时初始化给其赋一个初始值10。

语句:

String name = "John";

则声明了一个String类型的变量name,其被初始化为"John"。

而前面说列举的语句:int n; String name;,则仅仅表示声明了一个int变量n和一个String类型的变量name。

对于域变量(实例变量和类变量),在使用之前并不一定要明确对其进行初始化。在Java的语法中,当声明于变量时,会自动对用一个默认值对其进行初始化。

Java按照以下规则对变量进行默认初始化:

数据类型 默认值(域变量)
byte 0
short 0
int 0
long 0L
float 0.0f
double 0.0d
char '\u0000'
String (or any object) null
boolean false

如果是局部变量,那么变量在使用之前必须初始化。

注:以下图示与说明是套用指针的概念作的解释,理解比较偏颇,只为便于理解变量声明和初始化的概念,由于涉及编译的问题,实际的情况差别很大。个人认为,只是由于声明和初始化是一个比较难于理解和解释的概念,因此出此下策进行解释。

以下我根据我的理解画的关于变量内存的示意图:

说明:

(1)声明一个变量 Object var;

系统会给该变量分配一个内存位置0x100(应该是编译时分配的),这就是声明,然后当以后用到改变量时,

a,如果是基本类型,程序就去该内存位置取值,系统会根据变量的数据类型决定去多少,例如int类型取4个字节,如果是long则取八个字节。说道这里就可以明白为什么要指定变量的数据类型的原因了吧,因为只有这样系统才能知道如何解释内存中的信息。

由于是基本类型,程序直接取值,因此就少了图中0x100指向0x200这些示意。基本数据类型较小,很容易处理,没有必要指来指去。

b。如果是对象类型,程序就去该内存位置取值,将其作为一个地址,然后再去这个地址找对象的值。

(2)初始化变量 var = new String("hello");

如果是基本类型,初始化的形式如:int var = 10;,那么程序就会在在0x100中方一个10。

如果采用域变量默认初始化,系统会将 0 放在 0x100中。

如果是对象类型,那么程序首先分配一块内存,构建好String对象,然后将这块内存(如0x200)的地址保存到0x100中。

如果采用域变量默认初始化或者执行var = null;,那么就会将 0x0放在 0x100中。

对于局部变量,如果没有初始化,程序不会对0x100内存作处理,因此其值是不确定的。当然如果不显式初始化,编译都通不过。

四、数组

数组可以看成一个容器变量,它一定数量多个相同类型的值。

与普通变量类似,在使用数组之前,需要首先声明,声明数组的基本语法也与声明普通变量类似,再类型后面加[]即可。如

int[] ids;

声明了一个一维数组ids,用来保存int类型的值,在实际应用时,可以保存学生的学号。

int[][] scores;

声明了一个二维数组scores,用来保存int类型的值,二维数组可以看成是一张表格,在实际应用时,可以保存学生各科学习成绩。

与普通变量变量不同的是,在使用数组前还需要创建数组,和初始化数组中的值。

数组是一个变量,因此需要一小块内存保存数组实际数据在内存中的位置,而实际数据又是一个个变量,因此还需要一个个小块内存来保存每个变量实际数据在内存中的位置。创建数组就是完成这个工作,在这个阶段必须告诉系统你需要多少个小块内存,也就是你打算使用几个数据。

创建数组相当于执行了多个单变量声明,如果你想使用数据,还必须对每个数据(即一个个变量)进行初始化。

具体地说,

ids = new int[2];

表示创建了一个数组,将要保存2个数据。

ids[0] = 10;

ids[1] = 20;

表示初始化数组中的值。

以下我根据我的理解画的关于数组内存的示意图:

说明:

(1)声明一个数组 Object[] var;

系统会给该变量数组一个内存位置0x100(应该是编译时分配的),这就是声明。

(2)创建数组 var = new String[2];

系统会分配一块连续的内存0x200(如果不连续,那不是数组了),用来保存两个对象的内存地址。然后将这块内存的地址保存到0x100中。

(3)初始化数组中的值

如果是基本类型,有可能在创建数组时,就不是分配内存用来保存地址了,而是直接用来保存数组的值了,因此初始化也就是将值直接写到创建数组时分配的内存空间中了。

如果是对象类型,那么则分别先分配内存创建对象,然后将该内存的地址写到创建数组时分配的对应内存中。

五、其他

1、数组复制

System类提供了一个arrayCopy函数可以很方便的用来复制数组。其原形为:

public static void arraycopy(Object src,
int srcPos,
Object dest,
int destPos,
int length)

举例:

public class ArrayCopyDemo {
public static void main(String[] args) {
char[] copyFrom = { 'd', 'e', 'c', 'a', 'f', 'f', 'e',
'i', 'n', 'a', 't', 'e', 'd' };
char[] copyTo = new char[7];

System.arraycopy(copyFrom, 2, copyTo, 0, 7);
System.out.println(new String(copyTo));
}
}

2、转换
int n = (int)12.9;
System.out.println(n);

输出:12

3、字符串赋初值

String str1 = new String("hello");
String str2 = new String("hello");
System.out.println(str1 == str2);

输出:false

String str1 = "hello";
String str2 = "hello";
System.out.println(str1 == str2);

输出:true

<转帖>在Java中执行其它程序

在Java中执行其它程序

作者:终南 <li.zhongnan@hotmail.com>

在编写Java程序时,有时候需要在Java程序中执行另外一个程序。

1、启动程序

Java提供了两种方法用来启动其它程序:

(1)使用Runtime的exec()方法

(2)使用ProcessBuilder的start()方法

不管在哪种操作系统下,程序具有基本类似的一些属性。一个程序启动后就程序操作系统的一个进程,进程在执行的时候有自己的环境变量、有自己的工作目录。Runtime和ProcessBuilder提供了不同的方式来启动程序,设置启动参数、环境变量和工作目录。

能够在Java中执行的外部程序,必须是一个实际存在的可执行文件,对于shell下的内嵌命令是不能直接执行的。

采用Runtime的exec执行程序时,首先要使用Runtime的静态方法得到一个Runtime,然后调用Runtime的exec方法。可 以将要执行的外部程序和启动参数、环境变量、工作目录作为参数传递给exec方法,该方法执行后返回一个Process代表所执行的程序。

Runtime有六个exec方法,其中两个的定义为:

public Process exec(String[] cmdarray, String[] envp, File dir)

public Process exec(String command, String[] envp, File dir)

cmdarray和command为要执行的命令,可以将命令和参数作为一个字符串command传递给exec()方法,也可以将命令和参数一个一个的方在数组cmdarray里传递给exec()方法。

envp为环境变量,以name=value的形式放在数组中。dir为工作目录。

可以不要dir参数,或者不要envp和dir参数,这样就多出了其它4个exec()方法。如果没有dir参数或者为null,那么新启动的进程 就继承当前java进程的工作目录。如果没有envp参数或者为null,那么新启动的进程就继承当前java进程的环境变量。

也可以使用ProcessBuilder类启动一个新的程序,该类是后来添加到JDK中的,而且被推荐使用。通过构造函数设置要执行的命令以及参 数,或者也可以通过command()方法获取命令信息后在进行设置。通过directory(File directory) 方法设置工作目录,通过environment()获取环境变量信息来修改环境变量。

在使用ProcessBuilder构造函数创建一个新实例,设置环境变量、工作目录后,可以通过start()方法来启动新程序,与Runtime的exec()方法一样,该方法返回一个Process对象代表启动的程序。

ProcessBuilder与Runtime.exec()方法的不同在于ProcessBuilder提供了 redirectErrorStream(boolean redirectErrorStream) 方法,该方法用来将进程的错误输出重定向到标准输出里。即可以将错误输出都将与标准输出合并。

2、Process

不管通过那种方法启动进程后,都会返回一个Process类的实例代表启动的进程,该实例可用来控制进程并获得相关信息。Process 类提供了执行从进程输入、执行输出到进程、等待进程完成、检查进程的退出状态以及销毁(杀掉)进程的方法:

(1) void destroy()
杀掉子进程。
一般情况下,该方法并不能杀掉已经启动的进程,不用为好。

(2) int exitValue()
返回子进程的出口值。
只有启动的进程执行完成、或者由于异常退出后,exitValue()方法才会有正常的返回值,否则抛出异常。

(3)InputStream getErrorStream()
获取子进程的错误流。
如果错误输出被重定向,则不能从该流中读取错误输出。

(4)InputStream getInputStream()
获取子进程的输入流。
可以从该流中读取进程的标准输出。

(5)OutputStream getOutputStream()
获取子进程的输出流。
写入到该流中的数据作为进程的标准输入。

(6) int waitFor()
导致当前线程等待,如有必要,一直要等到由该 Process 对象表示的进程已经终止。

通过该类提供的方法,可以实现与启动的进程之间通信,达到交互的目的。

3、从标准输出和错误输出流读取信息

从启动其他程序的Java进程看,已启动的其他程序输出就是一个普通的输入流,可以通过getInputStream()和getErrorStream来获取。

对于一般输出文本的进程来说,可以将InputStream封装成BufferedReader,然后就可以一行一行的对进程的标准输出进行处理。

4、举例

(1)Runtime.exec()

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;

public class Test1 {
public static void main(String[] args) {
try {
Process p = null;
String line = null;
BufferedReader stdout = null;

//list the files and directorys under C:\
p = Runtime.getRuntime().exec("CMD.exe /C dir", null, new File("C:\\"));
stdout = new BufferedReader(new InputStreamReader(p
.getInputStream()));
while ((line = stdout.readLine()) != null) {
System.out.println(line);
}
stdout.close();

//echo the value of NAME
p = Runtime.getRuntime().exec("CMD.exe /C echo %NAME%", new String[] {"NAME=TEST"});
stdout = new BufferedReader(new InputStreamReader(p
.getInputStream()));
while ((line = stdout.readLine()) != null) {
System.out.println(line);
}
stdout.close();
} catch (Exception e) {
e.printStackTrace();
}
}

}

(2)ProcessBuilder

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

public class Test2 {
public static void main(String[] args) {
try {
List list = new ArrayList();
ProcessBuilder pb = null;
Process p = null;
String line = null;
BufferedReader stdout = null;

//list the files and directorys under C:\
list.add("CMD.EXE");
list.add("/C");
list.add("dir");
pb = new ProcessBuilder(list);
pb.directory(new File("C:\\"));
p = pb.start();

stdout = new BufferedReader(new InputStreamReader(p
.getInputStream()));
while ((line = stdout.readLine()) != null) {
System.out.println(line);
}
stdout.close();

//echo the value of NAME
pb = new ProcessBuilder();
pb.command(new String[] {"CMD.exe", "/C", "echo %NAME%"});
pb.environment().put("NAME", "TEST");
p = pb.start();

stdout = new BufferedReader(new InputStreamReader(p
.getInputStream()));
while ((line = stdout.readLine()) != null) {
System.out.println(line);
}
stdout.close();
} catch (Exception e) {
e.printStackTrace();
}
}

}

5、获取进程的返回值

通常,一个程序/进程在执行结束后会向操作系统返回一个整数值,0一般代表执行成功,非0表示执行出现问题。有两种方式可以用来获取进程的返回值。 一是利用waitFor(),该方法是阻塞的,执导进程执行完成后再返回。该方法返回一个代表进程返回值的整数值。另一个方法是调用 exitValue()方法,该方法是非阻塞的,调用立即返回。但是如果进程没有执行完成,则抛出异常。

6、阻塞的问题

由Process代表的进程在某些平台上有时候并不能很好的工作,特别是在对代表进程的标准输入流、输出流和错误输出进行操作时,如果使用不慎,有可能导致进程阻塞,甚至死锁。

如果将以上事例中的从标准输出重读取信息的语句修改为从错误输出流中读取:

stdout = new BufferedReader(new InputStreamReader(p
.getErrorStream()));

那么程序将发生阻塞,不能执行完成,而是hang在那里。

当进程启动后,就会打开标准输出流和错误输出流准备输出,当进程结束时,就会关闭他们。在以上例子中,错误输出流没有数据要输出,标准输出流中有数 据输出。由于标准输出流中的数据没有被读取,进程就不会结束,错误输出流也就不会被关闭,因此在调用readLine()方法时,整个程序就会被阻塞。为 了解决这个问题,可以根据输出的实际先后,先读取标准输出流,然后读取错误输出流。

但是,很多时候不能很明确的知道输出的先后,特别是要操作标准输入的时候,情况就会更为复杂。这时候可以采用线程来对标准输出、错误输出和标准输入进行分别处理,根据他们之间在业务逻辑上的关系决定读取那个流或者写入数据。

针对标准输出流和错误输出流所造成的问题,可以使用ProcessBuilder的redirectErrorStream()方法将他们合二为一,这时候只要读取标准输出的数据就可以了。

当在程序中使用Process的waitFor()方法时,特别是在读取之前调用waitFor()方法时,也有可能造成阻塞。可以用线程的方法来解决这个问题,也可以在读取数据后,调用waitFor()方法等待程序结束。

总之,解决阻塞的方法应该有两种:

(1)使用ProcessBuilder类,利用redirectErrorStream方法将标准输出流和错误输出流合二为一,在用start()方法启动进程后,先从标准输出中读取数据,然后调用waitFor()方法等待进程结束。

如:

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

public class Test3 {
public static void main(String[] args) {
try {
List list = new ArrayList();
ProcessBuilder pb = null;
Process p = null;
String line = null;
BufferedReader stdout = null;

//list the files and directorys under C:\
list.add("CMD.EXE");
list.add("/C");
list.add("dir1");
pb = new ProcessBuilder(list);
pb.directory(new File("C:\\"));
//merge the error output with the standard output
pb.redirectErrorStream(true);
p = pb.start();

//read the standard output
stdout = new BufferedReader(new InputStreamReader(p
.getInputStream()));
while ((line = stdout.readLine()) != null) {
System.out.println(line);
}
int ret = p.waitFor();
System.out.println("the return code is " + ret);

stdout.close();

} catch (Exception e) {
e.printStackTrace();
}
}

}

(2)使用线程

import java.util.*;
import java.io.*;

class StreamWatch extends Thread {
InputStream is;

String type;

List output = new ArrayList();

boolean debug = false;

StreamWatch(InputStream is, String type) {
this(is, type, false);
}

StreamWatch(InputStream is, String type, boolean debug) {
this.is = is;
this.type = type;
this.debug = debug;
}

public void run() {
try {
PrintWriter pw = null;

InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line = null;
while ((line = br.readLine()) != null) {
output.add(line);
if (debug)
System.out.println(type + ">" + line);
}
if (pw != null)
pw.flush();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}

public List getOutput() {
return output;
}
}

public class Test5 {
public static void main(String args[]) {
try {
List list = new ArrayList();
ProcessBuilder pb = null;
Process p = null;

// list the files and directorys under C:\
list.add("CMD.EXE");
list.add("/C");
list.add("dir1");
pb = new ProcessBuilder(list);
pb.directory(new File("C:\\"));
p = pb.start();

// process error and output message
StreamWatch errorWatch = new StreamWatch(p.getErrorStream(),
"ERROR");
StreamWatch outputWatch = new StreamWatch(p.getInputStream(),
"OUTPUT");

// start to watch
errorWatch.start();
outputWatch.start();

//wait for exit
int exitVal = p.waitFor();

//print the content from ERROR and OUTPUT
System.out.println("ERROR: " + errorWatch.getOutput());
System.out.println("OUTPUT: " + outputWatch.getOutput());

System.out.println("the return code is " + exitVal);

} catch (Throwable t) {
t.printStackTrace();
}
}
}

7、在Java中执行Java程序

执行一个Java程序的关键在于:

(1)知道JAVA虚拟机的位置,即java.exe或者java的路径

(2)知道要执行的java程序的位置

(3)知道该程序所依赖的其他类的位置

举一个例子,一目了然。

(1)待执行的Java类

public class MyTest {
public static void main(String[] args) {
System.out.println("OUTPUT one");
System.out.println("OUTPUT two");
System.err.println("ERROR 1");
System.err.println("ERROR 2");
for(int i = 0; i <>

(2)执行该类的程序


import java.util.*;
import java.io.*;

class StreamWatch extends Thread {
InputStream is;

String type;

List output = new ArrayList();

boolean debug = false;

StreamWatch(InputStream is, String type) {
this(is, type, false);
}

StreamWatch(InputStream is, String type, boolean debug) {
this.is = is;
this.type = type;
this.debug = debug;
}

public void run() {
try {
PrintWriter pw = null;

InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line = null;
while ((line = br.readLine()) != null) {
output.add(line);
if (debug)
System.out.println(type + ">" + line);
}
if (pw != null)
pw.flush();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}

public List getOutput() {
return output;
}
}

public class Test6 {
public static void main(String args[]) {
try {
List list = new ArrayList();
ProcessBuilder pb = null;
Process p = null;

String java = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java";
String classpath = System.getProperty("java.class.path");
// list the files and directorys under C:\
list.add(java);
list.add("-classpath");
list.add(classpath);
list.add(MyTest.class.getName());
list.add("hello");
list.add("world");
list.add("good better best");

pb = new ProcessBuilder(list);
p = pb.start();

System.out.println(pb.command());

// process error and output message
StreamWatch errorWatch = new StreamWatch(p.getErrorStream(),
"ERROR");
StreamWatch outputWatch = new StreamWatch(p.getInputStream(),
"OUTPUT");

// start to watch
errorWatch.start();
outputWatch.start();

//wait for exit
int exitVal = p.waitFor();

//print the content from ERROR and OUTPUT
System.out.println("ERROR: " + errorWatch.getOutput());
System.out.println("OUTPUT: " + outputWatch.getOutput());

System.out.println("the return code is " + exitVal);

} catch (Throwable t) {
t.printStackTrace();
}
}
}