您的位置:首页 > 编程语言 > Java开发

编写java程序151条建议读书笔记(16)

2017-05-28 18:59 239 查看
建议119:启动线程前stop方法是不可靠的

不使用stop方法进行状态的设置,直接通过判断条件来决定线程是否可启用。对于start方法的缺陷,一般不会引起太大的问题,只是增加了线程启动和停止的精度而已。

建议120:不使用stop方法停止线程

线程启动完毕后,在运行时可能需要中止,Java提供的终止方法只有一个stop,但是不建议使用这个方法,因为它有以下三个问题:(1)stop方法是过时的:从Java编码规则来说,已经过时的方法不建议采用。

(2)stop方法会导致代码逻辑不完整:stop方法是一种" 恶意 " 的中断,一旦执行stop方法,即终止当前正在运行的线程不管线程逻辑是否完整,这是非常危险的。(3)stop方法会破坏原子逻辑:多线程为了解决共享资源抢占的问题,使用了锁概念,避免资源不同步,但是正因为此,stop方法却会带来更大的麻烦,它会丢弃所有的锁,导致原子逻辑受损。既然终止一个线程不能使用stop方法,那怎样才能终止一个正在运行的线程呢?答案也简单,使用自定义的标志位决定线程的执行情况

class SafeStopThread extends Thread {
// 此变量必须加上volatile
/*
* volatile: 1.作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值.
* 2.被设计用来修饰被不同线程访问和修改的变量。如果不加入volatile
* ,基本上会导致这样的结果:要么无法编写多线程程序,要么编译器失去大量优化的机会。
*/
private volatile boolean stop = false;
@Override
public void run() {
// 判断线程体是否运行
while (stop) {
// doSomething
}
}
public void terminate() {
stop = true;
}
}
这是很简单的办法,在线程体中判断是否需要停止运行,即可保证线程体的逻辑完整性,而且也不会破坏原子逻辑,Thread还提供了interrupt中断线程的方法它不能终止一个正在执行着的线程,它只是修改中断标志而已。总之,如果期望终止一个正在运行的线程,则不能使用已过时的stop方法。需要自行编码实现,如此即可保证原子逻辑不被破坏,代码逻辑不会出现异常。当然,如果我们使用的是线程池(比如ThreadPoolExecutor类),那么可以通过shutdown方法逐步关闭池中的线程,它采用的是比较温和、安全的关闭线程方法,完全不会产生类似stop方法的弊端。

建议121:线程优先级只使用三个等级

线程的优先级(Priority)决定了线程获得CPU运行的机会,优先级越高获得的运行机会越大,优先级越低获得的机会越小。Java的线程有10个级别(准确的说是11个级别,级别为0的线程是JVM的,应用程序不能设置该级别),那是不是说级别是10的线程肯定比级别是9的线程先运行呢?我们来看如下一个多线程类:

class TestThread implements Runnable {
public void start(int _priority) {
Thread t = new Thread(this);
// 设置优先级别
t.setPriority(_priority);
t.start();
}
@Override
public void run() {
// 消耗CPU的计算
for (int i = 0; i < 100000; i++) {
Math.hypot(924526789, Math.cos(i));
}
// 输出线程优先级
System.out.println("Priority:" + Thread.currentThread().getPriority());
}
}
public static void main(String[] args) {
//启动20个不同优先级的线程
for (int i = 0; i < 20; i++) {
new TestThread().start(i % 10 + 1);
}
}

该多线程实现了Runnable接口,实现了run方法,注意在run方法中有一个比较占用CPU的计算,该计算毫无意义 这里创建了20个线程,每个线程在运行时都耗尽了CPU的资源,因为优先级不同,线程调度应该是先处理优先级高的,然后处理优先级低的,也就是先执行2个优先级为10的线程,然后执行2个优先级为9的线程,2个优先级为8的线程......但是结果却并不是这样Priority:5  Priority:7 Priority:10 Priority:6 Priority:9。。。(1)并不是严格按照线程优先级来执行的,比如线程优先级为5的线程比优先级为7的线程先执行,优先级为1的线程比优先级为2的线程先执行,很少出现优先级为2的线程比优先级为10的线程先执行(注意,这里是"
很少 ",是说确实有可能出现,只是几率低,因为优先级只是表示线程获得CPU运行的机会,并不代表强制的排序号)。(2)优先级差别越大,运行机会差别越明显,比如优先级为10的线程通常会比优先级为2的线程先执行,但是优先级为6的线程和优先级为5的线程差别就不太明显了,执行多次,你会发现有不同的顺序。这两个现象是线程优先级的一个重要表现,之所以会出现这种情况,是因为线程运行是需要获得CPU资源的,那谁能决定哪个线程先获得哪个线程后获得呢?这是依照操作系统设置的线程优先级来分配的,也就是说,每个线程要运行,需要操作系统分配优先级和CPU资源,对于JAVA来说JVM调用操作系统的接口设置优先级,比如windows操作系统优先级都相同吗?事实上,不同的操作系统线程优先级是不同的,Windows有7个优先级,Linux有140个优先级,Freebsd则由255个(此处指的优先级个数,不同操作系统有不同的分类,如中断级线程,操作系统级等,各个操作系统具体用户可用的线程数量也不相同)。Java是跨平台的系统,需要把这10个优先级映射成不同的操作系统的优先级,于是界定了Java的优先级只是代表抢占CPU的机会大小,优先级越高,抢占CPU的机会越大,被优先执行的可能性越高,优先级相差不大,则抢占CPU的机会差别也不大,这就是导致了优先级为9的线程可能比优先级为10的线程先运行。Java的缔造者们也觉察到了线程优先问题,于是Thread类中设置了三个优先级,此意就是告诉开发者,建议使用优先级常量。在编码时直接使用这些优先级常量,可以说在大部分情况下MAX_PRIORITY的线程会比MIN_PRIORITY的线程优先运行,但是不能认为是必然会,不能把这个优先级做为核心业务的必然条件,Java无法保证优先级高肯定会先执行,只能保证高优先级有更多的执行机会。因此,建议在开发时只使用此三类优先级( MIN_PRIORITY
、NORM_PRIORITY 、MAX_PRIORITY ),没有必要使用其他7个数字,这样也可以保证在不同的操作系统上优先级的表现基本相同。如果优先级相同呢?这很好办,也是由操作系统决定的。基本上是按照FIFO原则(先入先出,First Input First Output),但也是不能完全保证。

建议122:使用线程异常处理器提升系统可靠性

编写一个Socket应用,监听指定端口,实现数据包的接收和发送逻辑,这在早期系统间进行数据交互是经常使用的,这类接口通常需要考虑两个问题:一个是避免线程阻塞,保证接收的数据尽快处理;二是:接口的稳定性和可靠性问题,数据包很复杂,接口服务的系统也很多,一旦守候线程出现异常就会导致Socket停止,这是非常危险的,那有什么办法避免?Java1.5版本以后在Thread类中增加了setUncaughtExceptionHandler方法,实现了线程异常的捕捉和处理。如果Socket应用出现了不可预测的异常是否可以自动重启呢?其实使用线程异常处理器很容易解决,来看一个异常处理器应用实例,代码如下:

class TcpServer implements Runnable {
// 创建后即运行
public TcpServer() {
Thread t = new Thread(this);
t.setUncaughtExceptionHandler(new TcpServerExceptionHandler());
t.start();
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(1000);
System.out.println("系统正常运行:" + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}      // 抛出异常
throw new RuntimeException();
}
// 异常处理器
private static class TcpServerExceptionHandler implements
Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
// 记录线程异常信息
System.out.println("线程" + t.getName() + " 出现异常,自行重启,请分析原因。");
e.printStackTrace();
// 重启线程,保证业务不中断
new TcpServer();
}
}
}
这段代码的逻辑比较简单,在TcpServer类创建时启动一个线程,提供TCP服务,例如接收和发送文件,具体逻辑在run方法中实现。同时设置了该线程出现运行期异常(也就是Uncaught Exception)时,由TcpServerExceptionHandler异常处理器来处理异常。那么TcpServerExceptionHandler做什么事呢?两件事:1、记录异常信息,以便查找问题

2、重新启动一个新线程,提供不间断的服务。有了这两点,TcpServer就可以稳定的运行了,即使出现异常也能自动重启,客户端代码比较简单,只需要new TcpServer()即可。结果分析出当Thread-0出现异常时,系统自动重启了Thread-1线程,继续提供服务,大大提高了系统的性能,在实际环境中应用,则需要注意以下三个方面:

1、共享资源锁定:如果线程产生异常的原因是资源被锁定,自动重启会增加系统的负担,无法提供不间断服务。例如一个即时通信服务(XMPP Server)出现信息不能写入的情况,即使再怎么启动服务,也是无法解决问题的。在此情况下最好的办法是停止所有的线程,释放资源。2、脏数据引起系统逻辑混乱:异常的产生中断了正在执行的业务逻辑,特别是如果正在处理一个原子操作(像即时通讯服务器的用户验证和签到这两个事件应该在一个操作中处理,不允许出现验证成功,但签到不成功的情况),但如果此时抛出了运行期异常就有可能会破坏正常的业务逻辑,例如出现用户认证通过了,但签到不成功的情况,在这种情境下重启应用服务器,虽然可以提供服务,但对部分用户却产生了逻辑异常。3、内存溢出:线程异常了,但由该线程创建的对象并不会马上回收,如果再重亲启动新线程,再创建一批对象,特别是加入了场景接管,就非常危险了,例如即时通信服务,重新启动一个新线程必须保证原在线用户的透明性,即用户不会察觉服务重启,在此种情况下,就需要在线程初始化时加载大量对象以保证用户的状态信息,但是如果线程反复重启,很可能会引起OutOfMemory内存泄露问题。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: