Спецификация Java EE и многопоточность
Я пишу приложение Java EE, используя Struts и Spring. В одной из операций происходит интенсивная обработка базы данных и, следовательно, проблемы с производительностью. Что я хочу знать, могу ли я использовать многопоточность здесь? Я думаю, что спецификация Java EE не позволяет создавать собственные потоки, кроме тех, которые созданы сервером (я использую Weblogic). Пожалуйста, проведите меня через это.
3 ответа
Этот вопрос всплывает время от времени.
Согласно спецификации это не разрешено. Лучшая страница для просмотра: Q / A: J2EE Restrictions
Тем не менее, есть способы порождения потоков, особенно в Weblogic с WorkManager
,
Смотрите эти вопросы:
- Как EJB может распараллелить длинный процесс, интенсивно использующий процессор?
- Почему порождение потоков в контейнере J2EE не рекомендуется?
- J2EE программисты не пишут в файлы
Тот факт, что первый нацелен на EJB, не должен иметь большого значения, а последний о доступе к файловой системе связан с общими ограничениями.
Надеюсь, поможет.
Рекомендуемый способ создания потоков в среде Java EE - это API-интерфейс Concurrency Utils, который является частью спецификации EE7.
Используя этот API, ваш новый поток будет создаваться и управляться контейнером, гарантируя, что все службы EE будут доступны вашему потоку (например, безопасность, транзакции).
Примеры ниже взяты из моего собственного сайта здесь и здесь
Использование ManagedExecutorService
Чтобы создать новый поток с помощью ManagedExecutorService, сначала создайте объект задачи, который реализует Callable. В методе call() мы определим работу, которую мы хотим выполнить в отдельном потоке.
public class ReportTask implements Callable<Report> {
Logger logger = Logger.getLogger(getClass().getSimpleName());
public Report call() {
try {
Thread.sleep(3000);
catch (InterruptedException e) {
logger.log(Level.SEVERE, "Thread interrupted", e);
}
return new Report();
}
}
Затем нам нужно вызвать задачу, передав ее в метод submit() ManagedExecutorService.
@Stateless
public class ReportBean {
@Resource
private ManagedExecutorService executorService;
public void runReports() {
ReportTask reportTask = new ReportTask();
Future<Report> future = executorService.submit(reportTask);
}
}
Использование ManagedThreadFactory
Сначала создайте задачу Runnable, которая определит, какую работу нужно выполнять в фоновом режиме.
public class ReportTask implements Runnable {
Logger logger = Logger.getLogger(getClass().getSimpleName());
public void run() {
try {
//do your background task
Thread.sleep(10000);
} catch (InterruptedException e) {
logger.log(Level.SEVERE, "Thread interrupted", e);
}
}
}
Чтобы получить поток, управляемый контейнером, мы просто запрашиваем у ManagedThreadFactory новый поток и передаем ему наш экземпляр Runnable. Для запуска потока мы вызываем start().
@Stateless
public class ReportBean {
@Resource
private ManagedThreadFactory threadFactory;
public void runReports() {
ReportTask reportTask = new ReportTask();
Thread thread = threadFactory.newThread(reportTask);
thread.start();
}
}
Эти ограничения действуют в основном потому, что Java EE и EJB хотят поддерживать прозрачную кластеризацию. Например, один сервер кластера не должен изменять файлы, потому что эти изменения не могут быть легко отражены на другие серверы. Для потоков существует вопрос, должен ли быть один поток на кластер или на сервер. Эти потоки также не могут быть легко отслежены сервером приложений.
Тем не менее, должна быть возможность создавать потоки, соединения с сокетами или обращаться к файловой системе на сервере Java EE, как в обычном приложении.
Если вам нужно запустить несколько потоков, вот предложение (или альтернативный способ) с простым пулом управления:
1 - Передайте свой контекст (EJB) в качестве параметра вашему методу (конечная точка отдыха, планировщик, методы по умолчанию)
2 - Управляйте состоянием с помощью дополнительных флагов планировщика или объекта 3 - Будьте осторожны с объемом данных / обработки
4 - Рекомендации: настоятельно рекомендуется использовать показатели, журналы и тесты, тесты, тесты.
5 - Этот код находится на SpringBoot, но был протестирован в Jboss(с изменениями) в контексте EJB - тщательно протестируйте
6 - Используйте / изменяйте по своему усмотрению: (присылайте предложения / комментарии)
BaseControlExecutor.java
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class BaseControlExecutor {
private final ScheduledThreadPoolExecutor poolExec = new ScheduledThreadPoolExecutor(2);
public void execWithTimeout(final Runnable runnable, long timeout,
TimeUnit timeUnit) throws Exception {
execWithTimeout(new Callable<Object>() {
@Override
public Object call() throws Exception {
runnable.run();
return null;
}
}, timeout, timeUnit);
}
public <T> T execWithTimeout(Callable<T> callable, long timeout, TimeUnit timeUnit) throws Exception {
final Future<T> future = poolExec.submit(callable);
try {
return future.get(timeout, timeUnit);
} catch (TimeoutException e) {
future.cancel(true);
throw e;
} catch (ExecutionException e) {
Throwable t = e.getCause();
if (t instanceof Error) {
throw (Error) t;
} else if (t instanceof Exception) {
throw (Exception) t;
} else {
throw new IllegalStateException(t);
}
}
}
}
EndpointControlRest.java
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@RestController
@RequestMapping(value = "/report")
@Api(tags = "Endpoint of Future")
public class EndpointControlRest extends BaseControlExecutor {
Logger logger = LoggerFactory.getLogger(EndpointControlRest.class);
//single metric of execution
protected final AtomicLong counter = new AtomicLong();
@GetMapping(path = "/withThread", produces = { "application/json" })
@ApiOperation(value = "Return Hello count.")
public String greeting() {
Long countRunner = counter.incrementAndGet();
String json = ""; //or EJB context to use in Thread - becareful
new Thread(() -> {
try {
execWithTimeout(new Runnable() {
@Override
public void run() {
Instant start = Instant.now();
logger.info("Report init - " + countRunner);
//generating reports
generateBackgroundReport(json);
logger.info("Report End - " + countRunner);
Instant finish = Instant.now();
long timeElapsed = Duration.between(start, finish).toMillis();
logger.info("###DEBUG - " + countRunner + " - OK |Time exe: " + timeElapsed);
}
}, 120, TimeUnit.SECONDS);
} catch (TimeoutException e) {
logger.info("###DEBUG - " + countRunner + " - Timeout - " + e.getMessage());
} catch (Exception e) {
logger.info("###DEBUG - " + countRunner + " - Exception - " + e.getMessage());
}
}).start();
logger.info("####DEBUG - Rest call released");
return "Hello " + countRunner;
}
public String generateBackgroundReport(String json){
//simulating work
Long x = 0L;
for(Long i = 0L; i < 1000000000L; i ++){
x = i + 1;
}
logger.info("####DEBUG -report: " + x);
return "OK";
}
}