By default a new instance of the resource class is created for each new request.
We can change this default by overriding Application#getSingletons method in our Application implementation. In that case the same instances of the specified singleton classes will be used for all corresponding requests.
For singleton resources, we have to make sure of thread-safety, as multiple request coming in different threads can interfere each other when operating on shared data.
Let's understand that with examples.
The Application implementation
@ApplicationPath("/")
public class MyRestApp extends Application {
@Override
public Set<Class<?>> getClasses() {
Set<Class<?>> set = new HashSet<>();
set.add(MyResource.class);
return set;
}
@Override
public Set<Object> getSingletons() {
Set<Object> set = new HashSet<>();
set.add(new MySingletonResource());
return set;
}
}
MyResource.java
@Path("/")
public class MyResource {
private int counter;
@GET
@Path("count1")
public void count() {
counter++;
}
@GET
@Path("counter1")
public int getCounter() {
return counter;
}
@GET
@Path("reset1")
public void reset() {
counter = 0;
}
}
MySingletonResource.java
@Path("/")
public class MySingletonResource {
private int counter;
@GET
@Path("count2")
public void count() {
counter++;
}
@GET
@Path("counter2")
public int getCounter() {
return counter;
}
@GET
@Path("reset2")
public void reset() {
counter = 0;
}
}
To try examples, run embedded tomcat (configured in pom.xml of example project below):
mvn tomcat7:run
The resource client
Let's write a client using JAX-RS client API.
public class MyClient1 {
public static void main(String[] args) {
//default resource
getRequest("/reset1", Void.class);
countRequest("/count1");
Integer counter = getRequest("/counter1", Integer.class);
System.out.printf("counter1: %s%n", counter);
//singleton resource
getRequest("/reset2", Void.class);
countRequest("/count2");
Integer counter2 = getRequest("/counter2", Integer.class);
System.out.printf("counter2: %s%n", counter2);
}
public static void countRequest(String uri) {
for (int i = 0; i < 10; i++) {
getRequest(uri, Void.class);
}
}
public static <T> T getRequest(String uri, Class<T> responseType) {
Client client = ClientBuilder.newClient();
WebTarget target = client.target("http://localhost:8080" + uri);
return target.request().get(responseType);
}
}
Output
counter1: 0
counter2: 10
Above output shows a single instance of the singleton resource is used for multiple requests, hence increasing the counter value gradually.
Singleton Resources and thread safety
Let's write a client for our singleton resource which will make multiple requests in multiple threads simultaneously.
public class MyClient2 {
public static void main(String[] args) {
MyClient1.getRequest("/reset2", Void.class);
multiThreadedCountRequest("/count2");
Integer counter = MyClient1.getRequest("/counter2", Integer.class);
System.out.printf("counter2: %s%n", counter);
}
public static void multiThreadedCountRequest(String s) {
final ExecutorService es = Executors.newFixedThreadPool(1000);
for (int i = 0; i < 1000; i++) {
es.execute(() -> MyClient1.getRequest(s, Void.class));
}
es.shutdown();
try {
es.awaitTermination(5, TimeUnit.MINUTES);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
Output
counter2: 991
We were expecting counter value of 1000 but instead got 991. On multiple runs, we will get different results. That happens because of the thread interference.
Let's write a thread safe counter resource:
@Path("/")
public class MySingletonResource2 {
private AtomicInteger counter = new AtomicInteger(0);
@GET
@Path("count3")
public void count() {
counter.incrementAndGet();
}
@GET
@Path("counter3")
public int getCounter() {
return counter.get();
}
@GET
@Path("reset3")
public void reset() {
counter.set(0);
}
}
public class MyClient3 {
public static void main(String[] args) {
MyClient1.getRequest("/reset3", Void.class);
MyClient2.multiThreadedCountRequest("/count3");
Integer counter = MyClient1.getRequest("/counter3", Integer.class);
System.out.printf("counter3: %s%n", counter);
}
}
Output
counter3: 1000
Here we conclude that multiple overlapping requests for singleton resource may interfere each other while operating on share data. If we opt to use singleton resources, we have to make sure of thread safety ourselves.
Example ProjectDependencies and Technologies Used: - jersey-container-servlet 2.25.1: Jersey core Servlet 3.x implementation.
- JDK 1.8
- Maven 3.3.9
|