Java EE specification and multi threading
I am writing a Java EE application using Struts and Spr开发者_如何学Pythoning. In one of the operations there is heavy database processing, and hence performance issues. What I want to know is can I use multithreading here? I think the Java EE specification does not allow custom threads to be created apart from those created by Server (I use Weblogic). Please guide me through this.
The recommended way to create threads in a Java EE environment, is with the Concurrency Utils API, which is part of the EE7 specification.
By using this API your new thread will be created, and managed by the container, guaranteeing that all EE services are available to your thread (eg security, transactions).
The examples below are taken from my own site here and here
Using a ManagedExecutorService
To create a new thread using a ManagedExecutorService, first create a task object that implements Callable. Within the call() method we will define the work that we want carried out in a separate thread.
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();
}
}
Then we need to invoke the task by passing it though to the submit() method of the ManagedExecutorService.
@Stateless
public class ReportBean {
@Resource
private ManagedExecutorService executorService;
public void runReports() {
ReportTask reportTask = new ReportTask();
Future<Report> future = executorService.submit(reportTask);
}
}
Using a ManagedThreadFactory
First create a Runnable task which will define what work is to be done in the background.
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);
}
}
}
To get a container managed thread, we simply ask the ManagedThreadFactory for a new thread, and pass it our Runnable instance. To start the thread we call start().
@Stateless
public class ReportBean {
@Resource
private ManagedThreadFactory threadFactory;
public void runReports() {
ReportTask reportTask = new ReportTask();
Thread thread = threadFactory.newThread(reportTask);
thread.start();
}
}
This question pops up once in a while.
As per the spec it's not authorized. The best page to look at is this one: Q/A: J2EE Restrictions
That said, there are ways to spawn threads, especiall in Weblogic with the WorkManager
.
See these questions:
- How can an EJB parallelize a long, CPU intensive process?
- Why spawning threads in J2EE container is discouraged?
- J2EE programmers do not write to files
The fact that the first one targets EJB shouldn't matter that much, and the last one about access to file system is about general restrictions.
Hope it helps.
These restrictions are in place mostly because Java EE and EJB want to support transparent clustering. For example one server of a cluster should not modify files because these changes can not be easily mirrored to other servers. For threads there is the question if there should be one thread per cluster or per server. These threads also can not be easily monitored by the application server.
That said, it should be possible to create threads, socket connections or access the filesystem in a Java EE server just like in a normal application.
If you need to run several Threads here is a suggestion(or alternative way) with simple control's pool:
1 - Pass your context(EJB) as a parameter to your method(rest endpoint, scheduler, default's methods)
2 - Control the state with complementary scheduler or entity flags 3 - Be careful with the volume of data/processing
4 - Recommendations: Metrics, logs, and tests, tests, tests are strongly recommended
5 - This code is on SpringBoot but was tested in Jboss(with modifications) under EJB Context - Test carefully
6 - Use/modify as you wish: (send suggestions/comments)
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";
}
}
精彩评论