| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Source: Java.net Integrate your Cloud Computing Apps with Salesforce.com, Java 25 Mar 2010 20:02 GMT New integration patterns have emerged to enable many applications to migrate to the cloud while leveraging existing infrastructure. Source: DevX Flexible Swing Reporting Using JIDE Aggregate and Pivot Tables 22 Mar 2010 22:55 GMT
Learn about a Swing report alternative that provides 90% of the solution with 10% of the effort.
Flexible Swing Reporting Using JIDE Aggregate and Pivot Tables
Mon, 2010-03-22
Learn about a Swing report alternative that provides 90% of the solution with 10% of the effort.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ReportTableModel | CalculatedTableModel | ||
|---|---|---|---|
| Column 1 | Column 2 | Computed Column 1 | |
Sample data:
| ReportTableModel | CalculatedTableModel | ||
|---|---|---|---|
| Sales Price | Quantity | [Sales Price]*[Quantity] | |
| 5.00 | 22 | 110.00 | |
AggregateTable is created simply by passing in a TableModel. Additional functionality can be added to AggregateTable. AggregateView.getAggregateTable() in the sample code demonstrates the addition of column header features that allow for auto resizing, modifying the display state of columns, filtering and grouping.
The initial AggregateTable look and feel is similar to a JTable. The following are features of interest.

Figure 4. Filter
AggregateView.getAggregateTable()). Figure 5 illustrates this.

Figure 5. Group

Figure 6. Subtotals and grand totals
Figure 7 shows a partial view of the initial report.

Figure 7. Initial view
Figure 8 shows a partial view after the user right-clicked on the "Date Month" header and selected "Group this Column"; then did this again with "Item Type"; and then selected to create a "Date Month" subtotal.

Figure 8. Modified view
A toolbar was added to the demo application to allow the user to:
As of this writing, JIDE does not provide an interface for users to enter a formula; therefore you will need to provide your own GUI if you want to allow users to enter formulas dynamically. The example in the article only applies a simple expression.
To add a computed column, create a new ExpressionCalculatedColumn and add the column to the CalculatedTableModel. The following is the code from the sample:
final ExpressionCalculatedColumn commission =
new ExpressionCalculatedColumn(getAggregateView().getTableModel(),
"Commission", "([Amount])*.01");
getCalculatedModel().addColumn(commission);
getCalculatedModel().fireTableStructureChanged();
Note: There are numerous reasons to add computed columns. The SwingReportDemo.getCalculatedModel() decomposes each Date column into Year, Quarter, and Month columns. The additional columns allow the user more flexibility in grouping and subtotaling. The user can right-click on the header and hide columns; therefore you needn't be too concerned about making too much data available.
The SwingReportDemo.uiExport() function demonstrates the use of JIDE Excel export feature. Since Aggregate and Pivot tables have separate export functions, the export task is delegated to the appropriate View classes (AggregateView or PivotView).
Note: If your focus is true Excel functionality, products like JDataGrid from http://www.zfqjava.com/ do a great job of both importing and exporting Excel formatted files. JDataGrid tries to reproduce the Excel environment in Java without utilizing the OLE solution space.
Once the user has modified the report layout, the layout can be saved and reused. JIDE does the layout persistence to XML. The sample code uses a file chooser, but persistence to a data store is more practical.
Try: Play around with the report by grouping, subtotaling, and hiding columns, then save the report layout. Restart the application and load the saved report layout.
Important: The layout will not save filters. The reason filters are not saved is that filters are customizable and difficult to generalize for XML.
The Switch button allows the user to swap between Aggregate and Pivot views of the same data. The pivot view replicates much of the same behavior that exists in Excel pivot grid. Merely drag and drop columns to change the report layout, and right-click on the headers to see more Pivot column features. getPivotView() returns the Pivot view that is displayed:
PivotView getPivotView() {
if (m_pivotView == null) {
final String[] header = { "Date Month" };
final String[] row = { "Item Type", "Item Name" };
final String[] data = { "Amount" };
m_pivotView = PivotView.newUi(getCalculatedModel(),
header, row, data);
}
return m_pivotView;
}
Figure 9 displays the Pivot view on switch.

Figure 9. Pivot report on switch
JIDE is no panacea for reporting. The following are the things I like, and issues to be aware of when developing with JIDE.
SummaryCalculator, used in Pivot tables, does not follow the catalog and context design patterns, and hence has limited usage. JIDE's Pivot table ships with numerous built in summary calculations. However, SummaryCalculator limitations will become a problem if more than a single additional summary calculation is required.A more complete Swing based report solution could be developed by completing and generalizing the report code. Furthermore, as the JIDE components are improved, those new capabilities can be utilized to improve the example application. For now, the sample provides a great start on providing end users more flexibility inside a Swing application, while reducing headaches associated with incorporating a standard report writer.
| Attachment | Size |
|---|---|
| SwingJideReportCode.zip | 12.38 KB |
| aggtable.png | 125.72 KB |
| aggtable-modified.png | 94.76 KB |
| jide_flow.png | 3.15 KB |
| jtable_flow.png | 3.53 KB |
| pivot.png | 15.15 KB |
| revised_swing_report.png | 65.09 KB |
| swing-demo-filter.png | 9.18 KB |
| swing-demo-group.png | 17.8 KB |
| swing-demo-grouped.png | 19.19 KB |
| swing-demo-pivot.png | 15.15 KB |
The Blackberry OS is one of the most widely deployed Java ME implementations worldwide, with millions of business users that run mobile devices that use Java within the operating system. Released in 2009, the Blackberry OS 5.0 includes several new features that are of interest to today’s mobile Java developers, such as JSR-135 video recording and 3G graphics support with JSR-239 OpenGL ES API. Most notably for enterprise developers, the Blackberry OS 5.0 is the first mobile operating system that will include database support for Java ME developers who wish to store relational data.
The purpose of this article is to show developers how to get started in creating applications that utilize the SQLite database engine for Blackberry OS 5.0 applications. SQLite is an extremely popular database library that is used for wireless, embedded, and seamless-install application scenarios where a full scale database is not feasible or suitable. Since SQLite is an embedded database library, you (the developer) will have less visibility into the database itself. You won’t be able to use a db viewing tool to maintain the database from another computer, since there’s no way to connect you to SQLite db over a network. This article provides you with a simple tool, DBUtil.java, that will allow you to verify the existence of your db, and monitor its size.
If you’re wondering if the Blackberry SQLite Java API is JDBC compliant, let me clarify that it’s not. The JSR-169 API (JDBC API for Java ME) has been standardized for years, but unfortunately, there are no commercially available mobile phones that support database development with it. Please note, however, that although the Blackberry SQLite Java API doesn’t support the JSR-169 API, the basic concepts relating to programmatic access to the database are still the same:
So, with all the formalities out of the way, let’s look at the code needed to create your first SQLite database.
In order to run the example code provided in this article, you will need to download the Blackberry JDE 5.0 SDK.
Of course, since SQLite is an embedded database engine that’s optimized for mobile environments, the overhead to create a database is extremely simple. Listing 1, located below, shows the two lines of code necessary to physically create an instance of the SQLite database on Blackberry OS 5.0 devices.
Listing 1. The Code Required to Create a SQLite Database
URI uri = URI.create("file:///SDCard/Databases/database1.db");
sqliteDB = DatabaseFactory.create(uri);
As you can see, it’s extremely trivial to create a db instance with SQLite! Please note that although you may have the capability to create your database instance in various locations on the filesystem, it’s recommended that you create your database on external media, such as SD cards (especially if you intend to store large amounts of data). Listing 2, located below, is the source code for FirstSQLiteApp.java, which is a complete working example of a Java application that creates a database.
Listing 2. Full Source Code for FirstSQLiteApp.java.
import net.rim.device.api.ui.*;
import net.rim.device.api.ui.component.*;
import net.rim.device.api.ui.container.*;
import net.rim.device.api.database.*;
import net.rim.device.api.io.*;
public class FirstSQLiteApp extends UiApplication {
public FirstSQLiteApp() {
pushScreen(new InnerClassScreen());
}
public static void main(String[] args){
FirstSQLiteApp firstSQLiteApp = new FirstSQLiteApp();
firstSQLiteApp.enterEventDispatcher();
}
class InnerClassScreen extends MainScreen {
public Database sqliteDB;
public InnerClassScreen() {
LabelField title = new LabelField("Create DB Application",
LabelField.ELLIPSIS | LabelField.USE_ALL_WIDTH);
setTitle(title);
add(new RichTextField("Initializing db create process..."));
try{
URI uri = URI.create(
"file:///SDCard/Databases/database1.db");
sqliteDB = DatabaseFactory.create(uri);
add(new RichTextField(
"Status: Database was successfully created."));
} catch (Exception e){
System.out.println(e.getMessage());
add(new RichTextField(
"Status: Database was not created."));
add(new RichTextField(e.getMessage()));
e.printStackTrace();
}
}
}
}
Now, after we run the application within the Blackberry Simulator, we’ll get the following result as shown in Figure 1 below.

Figure 1. The Results of Executing FirstSQLiteApp.java
Please note that by default, the Blackberry OS 5.0 simulator doesn’t have a simulated SD loaded within the environment, so you’ll have to load one in order for the code in Listing 2 to work properly.
In order to load a simulated smart card into the Blackberry OS 5.0 simulator, you need to access the “Simulate > Change SD Card…” menu item while the simulator is running. Additionally, you need to be prepared to load a DMP file in order to finish configuration of the simulated smart card. You can find several sample DMP files in the Research In Motion\BlackBerry JDE 5.0.0\simulator folder. If the simulator provides an error message stating that the card needs to be formatted, then proceed with the formatting procedure.
Programmatically retrieving, storing and updating data within the SQLite database is also a very trivial task. The code in Listings 3-6 demonstrates how to create tables, insert, and select data from the database.
Listing 2. Creating Tables in the Database
try {
URI uri = URI.create("file:///SDCard/Databases/database1.db");
sqliteDB = DatabaseFactory.open(myURI);
Statement st = sqliteDB.createStatement( "CREATE TABLE 'Employee' ( " +
"'Name' TEXT, " +
"'Age' INTEGER )" );
st.prepare();
st.execute();
}
catch ( Exception e ) {
System.out.println( e.getMessage() );
e.printStackTrace();
}
Listing 3. Inserting Data into the Database
try {
URI uri = URI.create("file:///SDCard/Databases/database1.db");
sqliteDB = DatabaseFactory.open(myURI);
Statement st = sqliteDB.createStatement(
"INSERT INTO Employee(Name,Age) " +
"VALUES ('Ralph',47)");
st.prepare();
st.execute();
}
catch ( Exception e ) {
System.out.println( e.getMessage() );
e.printStackTrace();
}
Listing 4. Selecting Data from the Database
try {
URI uri = URI.create("file:///SDCard/Databases/database1.db");
sqliteDB = DatabaseFactory.open(myURI);
Statement st = sqliteDB.createStatement("SELECT * FROM Employee");
st.prepare();
Cursor c = st.getCursor();
Row r;
while(c.next()) {
r = c.getRow();
String name = r.getString(0) ;
Integer age = r.getInteger(1);
}
}
catch ( Exception e ) {
System.out.println( e.getMessage() );
e.printStackTrace();
}
One of the downsides of working with embedded databases is the lack of visibility. A traditional database runs on its own process on the CPU, binds to its own port on the network adapter, and can easily be remotely managed and configured. Conversely, embedded databases such as SQLite run in the process of your application, have no connection to the network, and must be managed and configured by applications that are resident on the host machine. Therefore, I created a simple JSR-75 (FileConnection API) application that allows developers to have more visibility into their embedded databases. When you run the DBUtility.java MIDlet, you can navigate the filesystem of your device to debug database creation issues, as well as to easily determine the file size of your embedded database. Figure 2, located below, shows how the DBUtility can browse your mobile device’s filesystem, and Figure 3 shows the file size of an individual file. Listings 5 and 6 contain all the source code necessary to build the DBUtility MIDlet.

Figure 2. Browsing the Filesystem Using DBUtility.java

Figure 3. Viewing the File Size of a File Using DBUtility.java
Listing 5. DButility.java
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import java.io.*;
import java.util.*;
import javax.microedition.io.*;
import javax.microedition.io.file.*;
import javax.microedition.lcdui.*;
public class DBUtility extends MIDlet implements CommandListener {
private Command exitCommand; // The exit command
private Display display; // The display for this MIDlet
private FileNavigator fileNavigator = null;
public DBUtility() {
display = Display.getDisplay(this);
exitCommand = new Command("Exit", Command.EXIT, 0);
fileNavigator = new FileNavigator(this);
}
public void startApp() {
Runnable r = new Runnable() {
public void run() {
display.setCurrent(fileNavigator.getListofFolder(
"",true)); //fileViewer.
}
};
new Thread(r).start();
}
public void pauseApp() {
}
public void destroyApp(boolean unconditional) {
}
public void commandAction(Command c, Displayable s) {
if (c == exitCommand) {
destroyApp(false);
notifyDestroyed();
}
}
}
Listing 6. FileNavigator.java
import javax.microedition.midlet.*;
import java.io.*;
import java.util.*;
import javax.microedition.io.*;
import javax.microedition.io.file.*;
import javax.microedition.lcdui.*;
/**
* This class traverses a filesystem and displays the folders
*and files within the filesystem in a List. This class creates the
* List, directoryList, to show its contents
*
* This class may also be renamed fileUtilty
*/
class FileNavigator implements CommandListener{
private Command backCommand1;
private Command selectCommand;
private Image folder_closed;
private Image folder_open;
private Image plain_file;
private MIDlet midlet;
//
// this is the main connection to the filesystem
// we'll use this object over and over
//
private FileConnection fileConn = null;
// These are vectors for the file/directory names,
// images, and URLs for the directory/files
// the boolean is used to determine if the item is
// a folder or file
private Vector fileNames = null;
private Vector images = null;
private Vector curr_dir_urls = null; // the urls for
// the files/folders in the current directory
private Vector fileTypes = null; // used to determine
//whether the element is a file or directory
// I'm assuming the fact that I'll need a class level variable
// that has the current "level" that we're at
// the folder with all the system roots is level '0'
int level = 0;
// I'm also assuming that I'm going to need a class level Vector
// where I'll store all my folder level urls
Vector level_urls = new Vector();
public FileNavigator(MIDlet _midlet){
midlet = _midlet;
selectCommand = new Command("Select", "Select", Command.OK, 1);
backCommand1 = new Command("Back", Command.BACK, 1);
try {
folder_closed = Image.createImage("/folder_closed.png");
plain_file = Image.createImage("/plain_file.png");
folder_open = Image.createImage("/folder_open.png");
} catch (Exception e) {
}
}
// this method returns a javax.lcd.List
// representing the files
// located in the folder found at the url
// passed in (if useRoots is false)
// if the useRoots variable is true, then the
//method will ignore the url string
// and return a List of the system roots
public List getListofFolder(String folder_url, boolean useRoots){
List directoryList = null; // this is the list that we'll return
Enumeration folder_contents = null; // the enumeration
//will contain all the contents of the current dir
// create the vectors, which will eventually be turned
// into arrays
fileNames = new Vector();
images = new Vector();
curr_dir_urls = new Vector();
fileTypes = new Vector();
// if useRoots is set to true, then obtain an enumeration of all
// the system roots (System roots may need to be explained)
if (useRoots == true){
folder_contents = FileSystemRegistry.listRoots();
} else {
// if we're in the else clause, then we need to use the
// passed in url to obtain a enumeration of the current dir
try{
fileConn = (FileConnection) Connector.open(folder_url);
// be sure to explain how to get hidden files
folder_contents = fileConn.list();
} catch (Exception e){
System.out.println(e);
new Alert(e.toString());
}
}
// we now need to provide a way to navigate
// out up of the folder (ie. cd ..)
// of course, we should provide this functionality for every
// folder EXCEPT the root
if (useRoots == true){
// set the level to "0"
level = 0;
} else {
fileNames.addElement("..");
images.addElement(folder_open);
curr_dir_urls.addElement(""); // this is an empty
// string null since that var isn't
// used for navigation
fileTypes.addElement(new Boolean(true));
// store the connection url in the level vector
// this will be used when we have to navigate ".."
level_urls.insertElementAt(folder_url, level);
// increment the level
level++;
}
// this is the rest of the old traverse method
while (folder_contents.hasMoreElements()) {
// 8-5, this looks like the items that will go in the vectors
// if so, then some additional comments may be needed
String item_name = (String)folder_contents.nextElement();
String item_url = null;
Image icon = null;
boolean isDirectory = false;
// since the enumeration only contains the filename,
// and not
// the full URL to the file, then we need set the full url
// in order to obtain the full information about
// each item in the folder
if (useRoots == true){
item_url = "file:///" + item_name;
} else {
item_url = fileConn.getURL() + item_name;
}
// here's the logic to set the proper image
// in the 'icon' variable
try {
FileConnection conn = (FileConnection)
Connector.open(item_url);
if(conn.isDirectory()){
icon = folder_closed;
isDirectory = true;
} else {
icon = plain_file;
isDirectory = false;
}
} catch (Exception e) {
System.out.println(e);
new Alert(e.toString());
}
// add the items to the vectors
fileNames.addElement(item_name);
images.addElement(icon);
curr_dir_urls.addElement(item_url);
fileTypes.addElement(new Boolean(isDirectory));
}
// initialize the arrays
String fileNameArray[] = new String[fileNames.size()];
Image imageArray[] = new Image[images.size()];
String urlArray[] = new String[curr_dir_urls.size()]; // this
// may not be needed since the vectors are global
Boolean fileTypeArray[] = new Boolean[fileTypes.size()]; // same
// for this one
// convert to arrays
fileNames.copyInto(fileNameArray);
images.copyInto(imageArray);
curr_dir_urls.copyInto(urlArray);
fileTypes.copyInto(fileTypeArray);
//create the List
directoryList = new List("Choose a folder or select a file",
Choice.IMPLICIT, fileNameArray, imageArray);
// add the navigation buttons
//here's the back button
directoryList.addCommand(backCommand1);
//here's the select button
directoryList.addCommand(selectCommand);
// add the inner class as the commandListener
directoryList.setCommandListener(this);
return directoryList;
}
public void commandAction(Command command, Displayable displayable) {
if(command == backCommand1){
// go to a previous screen
//Display.getDisplay(midlet).setCurrent(get_homeForm());
// test to see of the user hit the
// "select" button or the command action button
} else {
// get the selected index, which will be used shortly
int selectedIndex = ((List)displayable).getSelectedIndex();
if (((List)displayable).getString(selectedIndex).equals("..")){
// the user has selected the ".." directory
// so let's run some special logic to handle this
if(level > 1){
// this means that we should use the level_urls vector
// 8-6 perhaps we need to put the level-- right here
level = level - 2;
Display.getDisplay(midlet).setCurrent(
getListofFolder((String)
level_urls.elementAt(level), false));
} else{
// this means that we're at a low directory,
// so we need to show the roots
Display.getDisplay(midlet).setCurrent(
getListofFolder("", true));
level = 0;
}
// now decrement the level
//level--;
} else {
// otherwise, let's use the selected
// index to get the proper
// item in the List to navigate in the selected folder
// so, we've reached this area of code because
// we've come to
// the point where we will go further down a directory
// or select a file to see the size
// so obviously, we need to know if the user
// selected a file
// or a directory
boolean isFolderSelected = (
(Boolean)fileTypes.elementAt(selectedIndex)).
booleanValue();
if(isFolderSelected == true){
// the user selected a folder, so navigate down it
String folderUrl =
(String)curr_dir_urls.elementAt(selectedIndex);
Display.getDisplay(midlet).setCurrent(
getListofFolder(folderUrl, false));
} else {
// the user selected a file, so don't navigate
// start the process to discover Bluetooth devices
//**Display.getDisplay(midlet).
// setCurrent(get_fileSelectedAlert(), displayable);
// the user has obviously selected a file,
// so let's read it in
// this probably should be done in a new thread'
String file_url = (String)
curr_dir_urls.elementAt(selectedIndex);
//here
try{
fileConn = (FileConnection)
Connector.open(file_url);
long fileSize = fileConn.fileSize();
String stringFileSize = fileSize / 1024 + " kB";
Alert fileSelectedAlert = new Alert("",
"File Size: " + stringFileSize, null,
AlertType.CONFIRMATION);
fileSelectedAlert.setTimeout(Alert.FOREVER);
Display.getDisplay(midlet).
setCurrent(fileSelectedAlert);
System.out.println("File Size: " + stringFileSize);
} catch (Exception e){
}
}
}
}
}
}
For mobile Java developers, the Blackberry OS 5.0 platform provides a compelling development environment to enable developers to create exciting and full-featured applications. One of the most compelling features is the ability to create and manage a local relational database for persistent storage for your mobile application. This feature enables Java developers to bridge the gap between enterprise and mobile application development by incorporating a traditional paradigm for persistent data storage.
Thanks to the Blackberry OS 5.0 development team for the extensive documentation and example code on the Blackberry OS 5.0 and SQLite Java APIs.
| Attachment | Size |
|---|---|
| Figure1_sml.png | 66.61 KB |
| Figure2_sml.png | 65 KB |
| Figure3_sml.png | 59.13 KB |
For many years, we have been using Java Threads for developing numerous Client-Server based applications. While it's always desirable to have a highly responsive Client application, handling a high volume of client requests has always been a prime goal of any Server application. However, designing a highly-scalable server requires lots of analysis. Without careful design and adherence to a well thought out underlying policy, a thread based system can fail to produce the desired outcome.
In this article, we'll discuss certain design principles that should be followed when the objective is to build an efficient, thread-safe application.
As a design principle of thread safety, the vital point to look into is "Domain First." After all, the whole structure of an application lies on the Domain itself. If you get your domain model right, most of the problems are automatically taken care of. A model represents certain aspects of business logic consisting of various constraints and invariants. Understanding the pre and post conditions of a model are much needed in order to make the realized application thread-safe.
Let's imagine an travel agency that arranges budgeted cars for their traveler guests. They need an interactive car rental application that allows them to book multiple cars at different tourist locations on different dates within a specified price range. As they keep making multiple rental requests, at the same time if the system fails to find a car at particular location within price range, they want to be notified with suggested locations on the same screen.
We can think of this as a booking Requester taking the renal requests and putting them in a thread safe collection CarBookings that would be read concurrently by an actual CarFinder process to provide suggested locations. If CarFinder succeeds, it forwards this via the booking engine to the booking rental location. If CarFinder does not succeed, it tries to find the cars based on the price range and nearest location. Once found, it adds the info to another thread safe collection CarBookingAdvices.
There are many thread safe data structures available, but if you're sure about the Thread Safety policy that you want to adhere to, it's best to create a domain model on your own. For example:
class CarBookings{ private final Set bookings = new HashSet();
public synchronized void putCar(CarBooking booking){
bookings.add(booking);
}
public synchronized Set getBookings(){
return Collections.unmodifiableSet(bookings);
}
}
Since the methods putCar() and getBookings() provide a secure way of accessing the non-thread-safe instance variable bookings, the CarBookings can be used safely by both the BookingRequester and CarFinder process running in different threads. This meets our simple thread safety requirement.
//Defining BookingAdvice class BookingAdvice{ private final String bookingId=""; private final BigDecimal rate=new BigDecimal(""); private final String location=""; //Getter and Setter ommited } class BookingAdvices{ private final Set<BookingAdvice> advices = new HashSet<BookingAdvice>(); public synchronized void putAdvice(BookingAdvice advice){ advices.add(advice); } public synchronized Set<BookingAdvice> getAdvices(){ return Collections.unmodifiableSet(advices); } } //Defining Requester class BookingRequester{ private CarBookings bookings; private BookingAdvices advices; public void requestBooking(CarBooking booking){ bookings.putCar(booking); //Line 1 displayAdvice(advices.getAdvices()); //Line 2 } private void displayAdvices(Set<BookingAdvice> advices){ //Displays the advices on the same screen } } //Defining Finder class CarFinder{ private BookingAdvices advices; private CarBookings bookings; public void processAdvice(){ List<BookingAdvice> adviceList = getGeneratedAdvices(); for(BookingAdvice advice: adviceList) advices.putAdvice(advice); //Line 3 findAndProcessBookings(bookings.getBookings()); //Line 4 } private List<BookingAdvice> getGeneratedAdvices(){ //Generate advice based on requests } private void findAndProcessBookings(Set<CarBooking> bookings){ //Finds advice for the booking if required } }
But, there is a caveat: if the CarBooking class is mutable, then there is a possibility of modifying each CarBooking instance in the set returned by getBookings(). Does it make our model brittle? Well, as long as we can make sure that it won't be modified later on, we'll be fine with that. But, sometimes it's easier get thread safety by hiding the model state and blocking the access to it thereafter. Here, we make CarBooking immutable and add a copy constructor:
class CarBooking{ private final String bookingId; private final String location; private final BigDecimal rate; //Copy constructor public CarBooking(CarBooking booking){ this.bookingId= booking.getBookingId(); this.location= booking.getLocation(); this.rate= booking.getRate(); } //public Getter methods below } class CarBookings{ //Modified version public synchronized Set getBookings(){
Set newBookings = new HashSet();
for(CarBooking b : bookings){
CarBooking newBooking = new CarBooking(b);
newBookings.add(newBooking);
}
return Collections.unmodifiableSet(newBookings);
}
}
So, when getBookings() returns, it returns a copy of the CarBooking, thus not allowing modification of the original state--which makes it purely thread safe.
Sometimes you might find yourself being overburdened with handling the thread safety in your domain model; in that case you can simply delegate it to a range of Java built-in data structures that are already proven and tested to be thread safe. Like in our example, if we had to use bookings as HashMap<String, CarBooking>, where String could represent bookingId, we could have easily replaced that with ConcurrentHashMap--which is a thread safe HashMap. Likewise there are many other data structures available (under the java.util.concurrent package) like CopyOnWriteArrayList and ConcurrentLinkedQueue, to name a few.
To coordinate multiple threads in a lock-based application, the OS and JVM have to burn fair amount of CPU cycles for context switching and thread scheduling--making the system less responsive sometimes.
This can be avoided by allowing only one thread to gain access to work on a unit model (either CarBooking or BookingAdvice) at a time, and also making sure that same unit model should not be re-worked by the same thread. To make this happen, we can employ bounded BlockingQueues (BookingQ and AdviceQ) that would act as a mediator between Requester and CarFinder. So, for a rental request, the Requester would just create a CarBooking and put it in the BookingQ queue, and on the other hand, CarFinder would take that off of the queue. The same thing will be done by the CarFinder to return a BookingAdvice in the AdviceQ queue. This way, multiple Requesters and CarFinders can work asynchronously, making the system highly responsive.
While synchronized lock provides an easy way to work with multiple threads, improper usage of the lock may cause unpredictable results. This becomes obvious when multiple locks are acquired to perform an atomic operation where the first lock is held until last lock operation is performed. Let's consider our BookingRequester and CarFinder that are using simple java Sets instead of CarBookings and BookingAdvices. Here two operations: putting request and getting advices for Requester (vice versa for CarFinder, lines 5,6,7 and 8) become implicitly atomic under the same lock:
class BookingRequester{ private final Set bookings = new HashSet();
private final CarFinder finder;
public BookingRequester(CarFinder cf){
finder = cf;
}
public synchronized void requestBooking(CarBooking booking){
bookings.add(booking); //Line 5
displayAdvice(finder.getAdvices()); //Line 6
}
public synchronized Set getBookings(){
return bookings;
}
}
class CarFinder{
private final Set advices = new HashSet();
private BookingRequester bookings;
public CarFinder(BookingRequester br){
bookings = br;
}
public synchronized void processAdvice(){
List adviceList = getGeneratedAdvices();
for(Advice advice: adviceList
advices.add(advice); //Line 7
findAndProcessBookings(bookings.getBookings()); //Line 8
}
public synchronized Set getAdvices(){
return advices;
}
}
Now, a problem starts to creep in when two threads T1 and T2 try to call Requester requestBooking() and CarFinder processAdvice() respectively at the same time. During requestBooking, before T1 calls getAdvices() on CarFinder (Line 6), it has to wait for T2 to complete processAdvice. But to complete processAdvice, T2 needs to call getBookings() on Requester (Line 8), which won't happen until T1 completes requestBooking()--leading to a deadlock situation.
In our previous implementation of BookingRequester and CarFinder, we have already delegated the two operations to be handled independently under different locks (one for CarBookings and another for BookingAdvices, lines 1, 2, 3 and 4) to avoid any deadlock.
But, here we can simply resolve it by moving synchronized from the entire method to only the time required to put the booking (Line 5) and advice (line 7).
Another important point regarding deadlocks would be the order in which the resources are accessed in an atomic operation. If two threads work on an operation with two resource locks being held in a different order, it can lead to a deadlock situation too. So, make sure all threads calling an atomic operation get all resource locks in the same order.
Designing and improvising a thread based application is a challenge. But by following certain design principles and guidance, this can be easily overcome. At the same time, clear understanding of thread safety policy is also essential as it helps you simply the design. There are many other techniques available that we couldn't cover here. However, the principles presented here will always assist you in overcoming some of the thread safety related hurdles you might be facing as you develop thread-safe applications intended for operation on modern multicore and multiprocessor computers.
Sign In
Join
My Latest Posts My Monitored Threads My Blog My Photo Gallery My Profile My Homepage |