关于使用线程池控制任务超时的问题

[size=large]实现一个在规定的时间内完成一个任务,使用线程池实现,如果在规定的时间内,没完成,即结束该线程任务。以下是我的代码:

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class My {
private static ExecutorService executor = [b]Executors.newCachedThreadPool()[/b];//定义一个线程池
/**
* @param args
*/
public static void main(String[] args) {
Boolean result = false;
Future future = executor.submit(new MyJob());//将任务提交到线程池中
try {
result = future.get(200, TimeUnit.MILLISECONDS);//设定在200毫秒的时间内完成
} catch (InterruptedException e) {

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

        e.printStackTrace();
    } catch (TimeoutException e) {
        System.out.println("Time is out");//抛出超时异[size=xx-small][/size][size=large][/size]常时打印
    }
}

}

//业务job
import java.util.concurrent.Callable;
//实现Callable接口
public class MyJob implements [b]Callable[/b] {

@Override
public Boolean call() {
    //do job here
    long startTime = System.currentTimeMillis();
    for(int i=0;System.currentTimeMillis()-startTime<2000;i++){}//模拟业务执行超过2000毫秒,即已经超时
    System.out.println("continue...");
    return true;
}

}

执行void函数时输出结果如下:
Time is out
continue...

这和我的要求不符,我原本是希望在抛出异常之后任务即结束,不再执行System.out.println("continue...");
请问如何解决[/size]

[quote]在这个job中的call方法应为有for()循环可以找到在哪中断线程,但是如果只是在连接数据库的话,那就无法知道执行到哪,应该加入中断线程的代码,这应该在外面控制,而不应该在job里面实现中断[/quote]

线程的中断在JDK1.0版本中提供了stop()方法,但这个方法极度不安全,会不管
当前对象的状态而直接释放所有的monitor,结束线程,会导致对象处于错误的状态。
所以已经不被推荐使用。

试想,有个任务,A向B转账10000¥,需要先从A账户上扣款10000¥,再将B账号加
10000¥,如果A账号扣款完,这是从外面强行杀死线程,会导致什么后果?

中止一个线程最好的方式就是使用中断,在外面通知线程中断,线程将中断状态设置
为true,而具体是否要中断,中断时需要如何处理以保证数据状态是正确的,这些
应交给任务自己处理。

在什么地方控制中断,应该任务中控制,由你的业务逻辑决定,就算是
操作数据库也一样可以设置中断点,或者说更应该判断中断点,来决定是否回滚
事务。

这好像没什么好办法

future.get(200, TimeUnit.MILLISECONDS)这个只是等结果的时间

在外面控制时间,200毫秒后,执行
future.cancel(true);

[quote]
catch (TimeoutException e) {
System.out.println("Time is out");//抛出超时异[size=xx-small][/size]常时打印
}
[/quote]
在你的System.out.print()语句下,添加一行代码:
[code="java"]
future.cancel(true);
[/code]
看看行不行

在for循环后面抛个异常这样挺好哈
if (System.currentTimeMillis() - startTime >=2000)
throw new TimeoutException();
或者在你的job里实现取消策略,在需要时自己取消。

Future future = executor.submit(new MyJob());// 将任务提交到线程池中
executor.shutdown();//没人提交了该shutdown()了吧

[code="java"]
public class My {
private static ExecutorService executor = Executors.newCachedThreadPool();// 定义一个线程池

/**
 * @param args
 */
public static void main(String[] args) {
    Boolean result = false;
    Future<Boolean> future = executor.submit(new MyJob());// 将任务提交到线程池中
    try {
        result = future.get(200, TimeUnit.MILLISECONDS);// 设定在200毫秒的时间内完成

// future.cancel(true);
} catch (InterruptedException e) {

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

        e.printStackTrace();
    } catch (TimeoutException e) {
        System.out.println("Time is out");// 抛出超时异[size=xx-small][/size]常时打印
        executor.shutdown();//出现异常即可终止任务。
    }
    executor.shutdown();
    System.out.println(result);
}

}
[/code]

需要调用 executor.shutdown();
终止当前线程任务。

[code="java"]
catch (TimeoutException e) {
System.out.println("Time is out");//抛出超时异常时打印
boolean res = future.cancel(true);

System.out.println("res:" + res);
}
[/code]
你这么改一下,看看 res 的值是true 还是 false;

[code="java"]
public class My {

private static ExecutorService executor = Executors.newCachedThreadPool();// 定义一个线程池
public static void main(String[] args) {
    Future<Boolean> future = executor.submit(new MyJob());// 将任务提交到线程池中
    executor.shutdown();  //关闭线程池 不允许再提交任务,并不是关闭线程池中的线程
    try {
        future.get(200, TimeUnit.MILLISECONDS);// 设定在200毫秒的时间内完成
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    } catch (TimeoutException e) {
        future.cancel(true);//关闭超时的这个任务
        //executor.shutdownNow(); //因为这个任务超时,终止了所有的线程.
        System.out.println("Time is out");// 抛出超时异[size=xx-small][/size]常时打印
    }
}

/**
 * 
 * 任务。其实完全可以或应该在任务重控制自己何时超时,
 * 当然我顺着你的思路来了
 */
public static class MyJob implements Callable<Boolean> {

    public Boolean call() throws Exception {
        long startTime = System.currentTimeMillis();

        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            return false;   //这里要响应future的中断啊,
                            //返回false吧,或者超时了抛个异常也行,这就是所谓的中断策略
                            //Java5以后的线程终止方法被废了,很多时候终止策略应该自己来实现
        }
        System.out.println("continue...");
        return true;
    }

}

}
[/code]

我验证过了在"time is out"的打印后面加future.cancel(true);是可行的,不知道你怎么验证的?
} catch (TimeoutException e) {
System.out.println("Time is out");//抛出超时异[size=xx-small][/size]常时打印
future.cancel(true);
}

[quote]res为true[/quote]
呃,那这就奇怪了啊。明明任务已经被取消了,怎么还会打印出下面那句话呢。郁闷啊。。。

[quote]是不是使用线程池提交,线程池将任务分配给多个线程处理了,cancel()后仍然将任务分配给其他的线程执行?[/quote]这个需要看源码啊。但我认为,应该是针对每一个被submit上去的任务开一个线程来执行

你能把你的for循环里面模仿超时的语句贴出来吗?我用的是Thread.sleep();

[quote]使用thread能实现吗?还要有返回值的 [/quote]
使用 Thread.sleep() 的话,你可以这么写:
[code="java"]
try{
Thread.sleep(2000);
} catch (InterruptedException e) {
return false;
}
System.out.println("continue...");
return true;
[/code]

主要是要实现自己的终止策略
比如在for循环中可以加入这样的判断
[code="java"]
for(;;){
if(Thread.currentThread().isInterrupted())
return false; //这就是来响应future.cancel啊,你不响应他,咋停啊
}
[/code]
Java 并发编程实践这本书,看了基本上可以解决大部分问题了

[quote]public class MyJob implements Callable {

@Override
public Boolean call() {
//do job here
long startTime = System.currentTimeMillis();
for(int i=0;System.currentTimeMillis()-startTime<2000;i++){}//模拟业务执行超过2000毫秒,即已经超时
System.out.println("continue...");
return true;
}
} [/quote]

你这个测试任务写的有问题,根本没有控制中断,for循环会一直执行到结束。
要在循环中加入线程中断的判断。
Thread.currentThread().interrupted()

[quote]public class MyJob implements Callable {

@Override
public Boolean call() {
//do job here
long startTime = System.currentTimeMillis();
for(int i=0;System.currentTimeMillis()-startTime<2000;i++){
if(Thread.currentThread().interrupted()){
return false;
}
}//模拟业务执行超过2000毫秒,即已经超时
System.out.println("continue...");
return true;
}
} [/quote]

在外面执行future.cancel(true)这时就能结束任务了。

我习惯的做法是在工作线程内部设置一个boolean变量,如果外界想让线程结束就设置一下这个变量。在线程内部会判断这个变量,看是否要退出。

一个这种的办法是获取MyJob当前的线程;在MyJob对象中声明一个变量thread
[code="java"]import java.util.concurrent.Callable;
//实现Callable接口
public class MyJob implements Callable {
[b] public Thread thread = null;[/b]

@Override
public Boolean call() throws Exception {
//do job here
long startTime = System.currentTimeMillis();
[b]thread = Thread.currentThread();[/b]//获取当前线程 for(int i=0;System.currentTimeMillis()-startTime<2000;i++){}//模拟业务执行超过2000毫秒,即已经超时

    System.out.println("continue..."); 

    return true; 
}

}[/code]

在My对象中当超时,就对该工作任务的线程进行终止。
[code="java"]Boolean result = false;
[b]MyJob job = new MyJob();[/b] Future future = executor.submit(job);// 将任务提交到线程池中
try {
result = future.get(200, TimeUnit.MILLISECONDS);// 设定在200毫秒的时间内完成
// future.cancel(true);
} catch (InterruptedException e) {

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

        e.printStackTrace();
    } catch (TimeoutException e) {
        System.out.println("Time is out");// 抛出超时异[size=xx-small][/size]常时打印
        [b]job.thread.stop();[/b]       }[/code]

[quote]我也想用stop()[/quote]
stop() 已经是不推荐使用了,就算你获得了JOB所在的线程,请也别使用这个方法

[quote]
例子很好,不过要在任务中控制,不知道该如何在任务中设置中断点
比如说简单的获取数据库connection,我要在规定时间内判断,若没连接成功就中断线程
[/quote]
因为你获得不到这个JOB所在的线程,所以你没有办法来中断它的。

[quote]
难道就无解了吗?
[/quote]
暂时没想出来,晚上回去我有开发环境了,我再试试看

一般涉及到网络连接的地方,都会提供超时接口,getConnection也不例外,
不需要自己来控制超时。
[code="java"]

/**
* Sets the maximum time in seconds that a driver will wait
* while attempting to connect to a database.

*
* @param seconds the login time limit in seconds; zero means there is no limit
* @see #getLoginTimeout
*/
public static void setLoginTimeout(int seconds) {
loginTimeout = seconds;
}
[/code]

[quote]xiaoyang0601 写道
一个这种的办法是获取MyJob当前的线程;在MyJob对象中声明一个变量thread
Java代码

import java.util.concurrent.Callable;

//实现Callable接口

public class MyJob implements Callable {

public Thread thread = null; //定义一个当前任务线程
@Override

public Boolean call() throws Exception {

//do job here

long startTime = System.currentTimeMillis();

thread = Thread.currentThread();//获取当前线程,,外界可以访问
for(int i=0;System.currentTimeMillis()-startTime<2000;i++){}//模拟业务执行超过2000毫秒,即已经超时

    System.out.println("continue...");    

    return true;    
}   

}

import java.util.concurrent.Callable;
//实现Callable接口
public class MyJob implements Callable {
[b] public Thread thread = null;[/b]

@Override
public Boolean call() throws Exception {
//do job here
long startTime = System.currentTimeMillis();
[b]thread = Thread.currentThread();[/b]//获取当前线程 for(int i=0;System.currentTimeMillis()-startTime<2000;i++){}//模拟业务执行超过2000毫秒,即已经超时

    System.out.println("continue..."); 

    return true; 
}

}

在My对象中当超时,就对该工作任务的线程进行终止。
Java代码

Boolean result = false;

MyJob job = new MyJob();//将提交的任务
Future future = executor.submit(job);// 将任务提交到线程池中

try {

result = future.get(200, TimeUnit.MILLISECONDS);// 设定在200毫秒的时间内完成

// future.cancel(true);

} catch (InterruptedException e) {

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

        e.printStackTrace();   
    } catch (TimeoutException e) {   
        System.out.println("Time is out");// 抛出超时异[size=xx-small][/size]常时打印   
        job.thread.stop();//访问MyJob线程。
 }  

Boolean result = false;
[b]MyJob job = new MyJob();[/b] Future future = executor.submit(job);// 将任务提交到线程池中
try {
result = future.get(200, TimeUnit.MILLISECONDS);// 设定在200毫秒的时间内完成
// future.cancel(true);
} catch (InterruptedException e) {

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

        e.printStackTrace();
    } catch (TimeoutException e) {
        System.out.println("Time is out");// 抛出超时异[size=xx-small][/size]常时打印
        [b]job.thread.stop();[/b]       }

我也想用stop(),但是job.thread取不到该线程啊

[/quote]

能访问到啊。你仔细看看代码。在MyJob中声明一个任务线程,这个任务线程就是当前任务的,在My对象中,声明一个MyJob job = new MyJob();
Future future = executor.submit(job);//

在超时异常代码中
job.thread.stop();是可以访问的。你或许访问修饰符有误。
还有你提的这个问题,中超时不再处理以后的任务,但这个超时的后的任务放在任务中,欠妥,可以提取出来,进行重构,先进性必要的超时处理,如果没有超时在My对象中进行处理业务,如果超时就不再处理。

[code="java"]package dl.java.iteye;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.Callable;

public class My {
private static ExecutorService executor = Executors.newCachedThreadPool();// 定义一个线程池
/**
* @param args
*/
public static void main(String[] args) {
Boolean result = false;
MyJob job = new MyJob();
Future future = executor.submit(job);// 将任务提交到线程池中
try {
result = future.get(200, TimeUnit.MILLISECONDS);// 设定在200毫秒的时间内完成
} catch (InterruptedException e) {

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

        e.printStackTrace();
    } catch (TimeoutException e) {
        System.out.println("Time is out");// 抛出超时异[size=xx-small][/size]常时打印
        job.thread.stop();
    }
}

//业务job 
//实现Callable接口 
static class MyJob implements Callable<Boolean> {
   public Thread thread = null;
    @Override
    public Boolean call() throws Exception {
        //do job here 
        long startTime = System.currentTimeMillis(); //获取当前运行线程
        thread = Thread.currentThread();
        for(int i=0;System.currentTimeMillis()-startTime<2000;i++){}//模拟业务执行超过2000毫秒,即已经超时 
        System.out.println("continue..."); 
        return true; 
    }
}

}

[/code]

这是一个案例,你根据实际情况,修改吧。你可以复制,运行一下。打印结果:

Time is out

改了一下你原先的代码
[code="java"]
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.Callable;

public class My {
// 业务job

// 实现Callable接口
static class MyJob implements Callable<Boolean> {

    public Boolean call() {
        // do job here
        long startTime = System.currentTimeMillis();
        for (int i = 0; System.currentTimeMillis() - startTime < 2000; i++) {
            //测试当前线程是否已经中断,配合future.cancel(true)
            if (Thread.interrupted())
                return false;
        }// 模拟业务执行超过2000毫秒,即已经超时
        System.out.println("continue...");
        return true;
    }
}

private static ExecutorService executor = Executors.newCachedThreadPool();// 定义一个线程池

/**
 * @param args
 */
public static void main(String[] args) {
    Boolean result = false;
    Future<Boolean> future = executor.submit(new MyJob());// 将任务提交到线程池中
    try {
        result = future.get(200, TimeUnit.MILLISECONDS);// 设定在200毫秒的时间内完成
    } catch (InterruptedException e) {

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

        e.printStackTrace();
    } catch (TimeoutException e) {// 超时异常
        System.out.println("Time is out");
        future.cancel(true);// 中断执行此任务的线程
    }
}

} [/code]

测试通过,不会打印"continue..."

你对自己要做什么可能很清楚,但你对线程池能做什么确实很模糊,自己把自己绕晕了!!!
回滚事务,如果超时事务回滚到操作以前的场景!

楼主cancle地方不对,中断异常后返回也有问题,仔细看下面:
[code="java"]
public class Test {

private static ExecutorService executor = Executors.newCachedThreadPool();// 定义一个线程池

/**
 * @param args
 */
public static void main(String[] args) {
    Boolean result = false;
    Future<Boolean> future = executor.submit(new MyJob());// 将任务提交到线程池中
    try {
        result = future.get(20, TimeUnit.MILLISECONDS);// 设定在200毫秒的时间内完成
        // future.cancel(true);
    } catch (InterruptedException e) {

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

        e.printStackTrace();
    } catch (TimeoutException e) {
        System.out.println("Time is out");// 抛出超时异[size=xx-small][/size]常时打印
        //executor.shutdown();// 出现异常即可终止任务。
        [color=red]future.cancel(true);[/color]
    }
    //executor.shutdown();
    System.out.println(result);
}

}

class MyJob implements Callable {

@SuppressWarnings("static-access")
@Override
public Boolean call() {
    // do job here

// long startTime = System.currentTimeMillis();
// for (int i = 0; System.currentTimeMillis() - startTime < 2000; i++) {
// System.out.println(i);
// }
try {
Thread.currentThread().sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
[color=red]return false; [/color]
}
// 模拟业务执行超过2000毫秒,即已经超时
System.out.println("continue...");
return true;
}
}
[/code]

String N= "";

String M= "";

String id= "";

String pwd = "";

protected void doPost(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException
{

this.M = req.getParameter("M").trim();
this.N= req.getParameter("N").trim();
this.id = req.getParameter("id").trim();
this.pwd = req.getParameter("pwd").trim();

/................../
}

当多个系统同时调用该servlet的时候,出现了servlet的非线程安全的问题。比如有两个系统同时调用该servlet,代码中的N、M、id、pwd的值会出现,一个线程的数据被另一个线程的数据“覆盖”的问题。
[color=red] 我的问题是,第一:是否跟这几个变量是全部变量有关,如果设置为局部变量会出现这个问题吗?第二:使用多个servlet能解决这个问题,但是是最好的办法吗?[/color]