首页 » 编写高质量代码:改善Java程序的151个建议 » 编写高质量代码:改善Java程序的151个建议全文在线阅读

《编写高质量代码:改善Java程序的151个建议》第9章 多线程和并发

关灯直达底部

We're here to put a dent in the universe.Otherwise why else even be here?

活着就是为了改变世界,难道还有其他原因吗?

——Steve Paul Jobs(史蒂夫·乔布斯)

多线程技术可以更好地利用系统资源,减少对用户的响应时间,提高系统的性能和效率,但同时也增加了系统的复杂性和运维难度,特别是在高并发、大压力、高可靠性的项目中,线程资源的同步、抢占、互斥等都需要慎重考虑,以避免产生性能损耗和线程死锁。

建议118:不推荐覆写start方法

多线程比较简单的实现方式是继承Thread类,然后覆写run方法,在客户端程序中通过调用对象的start方法即可启动一个线程,这是多线程程序的标准写法。不知道读者是否还能回想起自己的第一个多线程demo呢?估计一般是这样写的:


class MultiThread extends Thread{

@Override

public void start(){

//调用线程体

run();

}

@Override

public void run(){

//MultiThread do something.

}

}


覆写run方法,这好办,写上自己的业务逻辑即可,但为什么要覆写start方法呢?最常见的理由是:要在客户端中调用start方法启动线程,不覆写start方法怎么启动run方法呢?于是乎就覆写了start方法,在方法内调用run方法。客户端代码是一个标准程序,代码如下:


public static void main(Stringargs){

//多线程对象

MultiThread multiThread=new MultiThread();

//启动多线程

multiThread.start();

}


相信读者都能看出这是一个错误的多线程应用,main方法根本就没有启动一个子线程,整个应用程序中只有一个主线程在运行,并不会创建任何其他的线程。对此,有很简单的解决办法,只要删除MultiThread类中的start方法即可。

然后呢?就结束了吗?是的,很多时候确实到此结束了。找我解惑的同事或朋友中,很少有人会问为什么不必而且不能覆写start方法,仅仅就是因为“多线程应用就是这样写的”这个原因吗?

要说明这个问题,就需要看一下Thread类的源代码了。Thread类的start方法的代码如下。


public synchronized void start(){

//判断线程状态,必须是未启动状态

if(threadStatus!=0)

throw new IllegalThreadStateException();

//加入线程组中

group.add(this);

//分配栈内存,启动线程,运行run方法

start0();

//在启动前设置了停止状态

if(stopBeforeStart){

stop0(throwableFromStop);

}

}

//本地方法

private native void start0();


这里的关键是本地方法start0,它实现了启动线程、申请栈内存、运行run方法、修改线程状态等职责,线程管理和栈内存管理都是由JVM负责的,如果覆盖了start方法,也就是撤消了线程管理和栈内存管理的能力,这样如何启动一个线程呢?事实上,不需要关注线程和栈内存的管理,只需要编码者实现多线程的逻辑即可(即run方法体),这也是JVM比较聪明的地方,简化多线程应用。

那可能有读者要问了:如果确实有必要覆写start方法,那该如何处理呢?这确实是一个罕见的要求,不过,要覆写也很容易,只要在start方法中加上super.start即可,代码如下:


class MultiThread extends Thread{

@Override

public void start(){

/*线程启动前的业务处理*/

super.start();

/*线程启动后的业务处理*/

}

@Override

public void run(){

//MultiThread do something.

}

}


注意看start方法,调用了父类的start方法,没有主动调用run方法,这是由JVM自行调用的,不用我们显式实现,而且是一定不能实现。此方式虽然解决了“覆写start方法”的问题,但是基本上无用武之地,到目前为止还没有发现一定要覆写start方法的多线程应用,所有要求覆写start的场景,都可以用其他的方式来实现,例如类变量、事件机制、监听等方式。

注意 继承自Thread类的多线程类不必覆写start方法。