Java 创建太多线程有什么缺点吗?
我有一个方法,它执行一些api调用以在filenet存储库上保存一个映像,然后在两个数据库表上执行一些日志记录,整个方法需要花费大量时间来执行,因此我考虑将日志记录分离到一个单独的线程中,如下所示:Java 创建太多线程有什么缺点吗?,java,multithreading,thread-safety,Java,Multithreading,Thread Safety,我有一个方法,它执行一些api调用以在filenet存储库上保存一个映像,然后在两个数据库表上执行一些日志记录,整个方法需要花费大量时间来执行,因此我考虑将日志记录分离到一个单独的线程中,如下所示: public void createNewDocument(){ doSaveImage(); new Thread() { @Override
public void createNewDocument(){
doSaveImage();
new Thread()
{
@Override
public void run()
{
try{
new LogDAO().addLog(log);
new ReportsDAO().addCreatedDocumentLog(productivityReport);
}catch(Exception e){
e.printStackTrace();
}
}
}.start();
}
对于单个用户,它工作正常,没有问题,所以我的问题是,这被认为是一个糟糕的设计,当多个用户同时调用此方法(内存问题?)或安全问题时,它会导致问题吗 打开线程并不是一个坏主意,只关心很少的事情 数据库连接打开/关闭 事务不会跨越,每个线程都有自己的事务 如果在任何事务中发生任何错误/异常,则回滚事务 工作线程 还有你如何管理你的线程,线程是否在可运行实例中,或者转到>池,每次实例化等等。这里谈论的是threadpoool,它是如何管理的
拥有多个线程的主要问题是,更改线程上下文意味着开销 交叉点有更多线程会对您的系统造成不利影响,这取决于您有多少请求,以及每个线程在空闲状态下花费了多少时间(例如,等待I/O或DB操作发生) 最安全的方法是创建一个线程池(它重用线程,并配置为具有最大数量的并发线程),然后找到更适合您的系统的数量。据我所知,大多数服务器都支持这种开箱即用的方式 此外,您的设计还有一个缺点,即您正在接受操作过程中发生的任何错误。即使操作没有成功,您的客户也会认为该操作已成功执行。如果你负担得起,请仔细考虑。同时拥有多个(数百个)线程不是一个好主意 首先,您的CPU可能只有4或8个内核,因此它不能同时运行超过那么多线程——让100个线程同时运行是没有用的,大多数线程都会等待CPU内核自由运行它们 线的制造成本相对较高。每个线程都有一个线程。此调用堆栈的默认大小取决于平台,但通常范围为几百KB到几MB。这意味着,如果启动100个线程,所有这些线程的调用堆栈已经占用了大量内存(高达数百MB) 您应该使用线程池。Java在
Java.util.concurrent
包中有一个完整的线程池框架
例如:
public void createNewDocument(ExecutorService executorService) {
doSaveImage();
executorService.execute(new Runnable() {
@Override
public void run() {
try {
new LogDAO().addLog(log);
new ReportsDAO().addCreatedDocumentLog(productivityReport);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
您可以使用Executors
中的某个工厂方法创建ExecutorService
,例如:
ExecutorService executorService = Executors.newFixedThreadPool(4);
正如@ankur singhal指出的,在线程方面还有其他更重要的因素 但是,要回答您关于线程数量的问题:一台普通PC在正常的一天中有大约1500个线程处于活动状态(在所有应用程序和操作系统本身中)。您可以打开Windows任务管理器,并在“性能”选项卡中亲自检查 这表明普通PC可以轻松处理数千个线程。 在服务器上,如果您的应用程序是唯一正在运行的应用程序,那么您可能有500-1000个线程,而不会遇到任何问题 然而,仅仅因为你可以,并不意味着你应该!如果您每小时有一百万个请求,那么您的线程池可能有500个线程。如果每小时有10000个请求,那么线程池可能有25个或50个 在任何情况下,使用纯线程的
ExecutorService
都是一种很好的设计实践
而且,当应用程序退出时,不要忘记调用
shutdown
。您建议在每次调用createNewDocument()
时创建并销毁一个新线程。创建和销毁线程比唤醒现有的、长期工作线程的成本更高
java.util.concurrent.ThreadPoolExecutor类的一个实例将为您管理一个工作线程池。每次您的程序想要执行任务时,它都可以向ThreadPoolExecutor提供一个Runnable
对象或一个Callable
对象,并且执行器将唤醒将执行该任务的一个池线程
如果您不经常调用createNewDocument(),那么您建议的调用方式没有问题,但是如果您养成了使用标准库和其他库为您提供的更高级别服务(例如ThreadPoolExecutor)的习惯,您将成为一名更好的开发人员。因为
i) 它很容易导致死锁状态
ii)线程过多会影响问题的时间复杂性
iii)当持有锁的线程的时间片过期时,会出现另一个问题。所有等待锁的线程现在必须等待持有线程获得另一个时间片并释放锁。如果锁的实现是公平的,即锁是按照先到先得的顺序获得的,那么问题就更糟了。如果一个等待的线程被挂起,那么在它后面等待的所有线程都将被阻止获取锁这就像有人在签出线上睡着了一样启动线程可能会很慢(取决于操作系统)。在创建新文档的每次调用中,我都会创建一个新的ExecutorService?还应该在ExecutorService关闭时调用吗?否,您不应该为每个文档创建新的ExecutorService
。创建一次执行器服务,然后重新使用。当程序停止时,您需要调用ExecutorService
上的Shutdownow()
或shutdownNow()
,或者不再需要创建任何新文档。