The Tiger Roars: Experience the New Functionality in J2SE
TM 5.0
Expected duration: 120
minutes
Today JavaTM
technology is
everywhere
-- in large enterprise systems, desktops, hand-held devices and smart
cards. Consequently Java technology is the platform of choice for
developers all over the world. All this has happened in the short time
since the technology was first introduced in 1995. While there have
been updates and enhancements since the first version, release 5.0 of
the core Java platform brings to the table more language-level updates
and other enhancements through the incorporation of a number of Java
Specification Requests (JSRs) than at any other time. In this
hands-on session, developers will learn about all these changes and how
to build desktop
applications using version 5.0 of the Java platform.
Prerequisites
- 6 months of Java programming experience
Software needed for the lab:
- Download and install J2SE
5.0
- For Windows, install it under root of a drive, for example,
c:\jdk1.5.0
- For Solaris/Linux, install it under user's home directory, for
example, /home/username/jdk1.5.0
- Download and unzip lab zip
file (1105_tiger.zip) under a directory of your
choice
- Read <lab_root>/tiger/index.html (this document) to
proceed
Notations
used in
this documentation
- <JAVA_HOME> (or %JAVA_HOME%)
- It is installation directory of J2SE 5.0 SDK
- For Windows, for example, C:\jdk1.5.0
- For Solaris/Linux, for example, /home/username/jdk1.5.0
- <lab_root>
- This is the directory under which
the lab zip file is
unzipped.
- This document uses <lab_root> to denote the directory
under which you have
unzipped the lab zip file of this hands-on lab. The name of the lab zip
file of this hands-on lab is 1105_tiger.zip.
- Once you
unzipped the lab zip file under <lab_root>, it will create
a subdirectory called tiger.
For example, under Windows, if you
have unzipped the lab zip file under c:\
directory, it will create c:\tiger.
Under Linux/Solaris, if you have unzipped the
lab zip file under /home/username
directory, it will create /home/username/tiger
directory
Things
to check
before you start the
lab:
Once you installed J2SE
5.0, please make sure you do the following:
- Set %JAVA_HOME% environment variable to the installation
directory of J2SE 5.0
- Type "echo %JAVA_HOME%" in a terminal window. You
should see
the following:
- c:\jdk1.5.0 (if you installed it in c:\jdk1.5.0 on Windows)
- Place %JAVA_HOME%\bin in the path variable
- Type "java -version" in a terminal window. You should
see the following:
- java version "1.5.0"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0-b64)
Java HotSpot(TM) Client VM (build 1.5.0-b64, mixed mode, sharing)
Lab
exercises:
This lab is comprised of 3 major parts. Each part contains
several lab exercises.
Please proceed the lab by doing the exercises in sequence.
Part 1: J2SE 5.0
Language Enhancements ( 60 minutes )
- AutoBoxing
- Generics
- Type-safe Enumerations
- Enhanced For Loops
- Static Imports
- Variable Arguments
Part 2 : Metadata ( 30
minutes )
Part 3 : Concurrency
( 30 minutes )
- Executor and
ThreadPool
- Callable and Future
- Semaphore
- Blocking queue
Resources:
Where to send questions
or feedbacks
on this lab
and public discussion forums:
- If you have any questions or feedback's on this lab, please send
them to the following email alias - please be advised that this alias
is set up for addressing issues/questions directly relevant to this lab
not for answering generic J2SE 5.0 questions.
- You can also send your questions to the public J2SE SDK forum -
this
forum can be used to ask any J2SE 5.0 related questions.
Part
1: J2SE 5.0 Language Enhancements ( 60 minutes )
Exercise
1:
Autoboxing
Introduction:
The Autoboxing and Unboxing
feature of J2SE 5.0 eliminates the drudgery of manual
conversion between primitive types (such as int) and wrapper types
(such
as Integer).
If you are new to
AutoBox
functionality, please take a look at the following resources:
Steps
to follow:
1. Open
<lab_root>/tiger/exercise/language/AutoBoxLab.java
(c:\tiger\exercise\language\AutoBoxLab.java if you have unzipped
the lab file under c:\ of Windows) using a text
editor of your choice. This file contains Java code that does not
use AutoBoxing/UnBoxing functionality yet. (Code-11 below) You
will change
this Java
code so that it uses Autoboxing/UnBoxing in the subsequent steps. The
code
fragment that is
highlighted with bold font is candidate for change.
public class AutoBoxLab
{
Integer iObj;
Float fObj;
Long lObj;
Double dObj;
public AutoBoxLab() {
}
public static void main( String[] args ) {
AutoBoxLab a = new AutoBoxLab();
a.iObj = new
Integer( 22 );
a.fObj = new Float( 22.0 );
a.lObj = new Long ( 22L );
a.dObj = new Double( 22 );
System.out.println( " int Value of iObj is: " +
a.iObj.intValue() );
System.out.println( " float Value of iObj is: " +
a.fObj.floatValue() );
System.out.println( " long Value of iObj is: " +
a.lObj.longValue() );
System.out.println( " double Value of iObj is: " +
a.dObj.doubleValue() );
}
}
|
Code-11:Code that does not use AutoBoxing/UnBoxing
2. Compile and run the code by typing the following in a
terminal window.
- cd <lab_root>/tiger/exercise/language
(cd c:\tiger\exercise\language,
if
you are not already in this directory)
- javac
AutoBoxLab.java
- java AutoBoxLab
3. Observe the output like following. This verifies the
above code is a working code.
- int Value of iObj is: 22
float Value of iObj is: 22.0
long Value of iObj is: 22
double Value of iObj is: 22.0
4. Modify the code so that it uses AutoBoxing and UnBoxing as
shown in Code-12 below. The code that is
highlighted with
bold font and
blue color represents the changed code.
public class AutoBoxLab
{
Integer iObj;
Float fObj;
Long lObj;
Double dObj;
public AutoBoxLab() {
}
public static void main( String[] args ) {
AutoBoxLab a = new AutoBoxLab();
// a.iObj = new
Integer( 22 );
a.iObj =
22;
// Using AutoBoxing
// a.fObj = new Float( 22.0 );
a.fObj = 22.0f ;
// Using AutoBoxing
// a.lObj = new Long ( 22L );
a.lObj = 22L;
// Using AutoBoxing
// a.dObj = new Double( 22 );
a.dObj = 22d;
// Using AutoBoxing
// System.out.println( " int Value of iObj is: " +
a.iObj.intValue() );
System.out.println( " int Value of iObj is: " +
a.iObj ); // Using UnBoxing
// System.out.println( " float Value of iObj is: " +
a.fObj.floatValue() );
System.out.println( " float Value of iObj is: " +
a.fObj ); // Using UnBoxing
// System.out.println( " long Value of iObj is: " +
a.lObj.longValue() );
System.out.println( " long Value of iObj is: " +
a.lObj ); // Using UnBoxing
// System.out.println( " double Value of iObj is: "
+ a.dObj.doubleValue() );
System.out.println( " double Value of iObj is: " + a.dObj ); // Using UnBoxing
}
}
|
Code-12: Code that uses AutoBoxing/UnBoxing
5. Compile and run
the code by typing the following in a terminal window.
- cd <lab_root>/tiger/exercise/language
(cd
c:\tiger\exercise\language, if
you are not already in this directory)
- javac
AutoBoxLab.java
- If you encounter following compiler errors, it is likely that
you are not
using J2SE 5.0. Please make sure the <j2se5.0_install>/bin
directory of the J2SE
5.0 is in your path.
- javac AutoBoxLab.java
AutoBoxLab.java:15: incompatible types
found : int
required: java.lang.Integer
a.iObj = 22 ;
^
AutoBoxLab.java:18: incompatible types
found : float
required: java.lang.Float
a.fObj = 22.0f ;
^
AutoBoxLab.java:21: incompatible types
found : long
required: java.lang.Long
a.lObj = 22L ;
^
AutoBoxLab.java:24: incompatible types
found : double
required: java.lang.Double
a.dObj = 22d;
^
4 errors
- java AutoBoxLab
6. Observe the following result. You have successfully modified
the code so that it uses the AutoBoxing.
- int Value of iObj is: 22
float Value of iObj is: 22.0
long Value of iObj is: 22
double Value of iObj is: 22.0
Summary:
In this lab exercise, you learned how to
use the AutoBoxing functionality in J2SE 5.0. You
Exercise 2:
Generics
Introduction:
This long-awaited enhancement to the type system allows a
type or method to operate on objects of various types while providing
compile-time type safety. It adds compile-time type safety to the
Collections Framework and eliminates the drudgery of type casting.
If you are new to
Generics functionality, please take a look at the following resources:
Steps to follow:
1. Open <lab_root>/tiger/exercise/language/GenericsLab.java.
(Code-13)
This
code is written using the
pre-J2SE 5.0 style, i.e. without using Generics.
import java.util.*;
public class GenericsLab {
public static void main( String[] args ) {
List sList = new
LinkedList();
List iList = new LinkedList();
// In the next line create a
generic List iterator - from the iList
iList.add(new Integer(1));
iList.add(new Integer(2));
sList.add(new String("Hello"));
sList.add(new String("World"));
// Add an Integer to list
that contains only String
sList.add(new Integer(1));
// Create a generic List
iterator for the iList
Iterator genericIterator =
iList.iterator();
while(genericIterator.hasNext()) {
// Illegal
cast caught at runtime.
String item =
(String)genericIterator.next();
}
genericIterator =
sList.iterator();
// No guarantee of homogeneous
containers.
while (genericIterator.hasNext())
{
// fail at
runtime due to heterogeneous set
String item =
(String)genericIterator.next();
}
}
}
|
Code-13:
Code that does not use Genetics
2. Compile
and run
the code by typing the following in a terminal window
- cd <lab_root>/tiger/exercise/language
(c:\tiger\exercise\language,
if
you are not already in this directory)
- javac GenericsLab.java
- java GenericsLab
3. Observe
that the compilation ("javac GenericsLab.java") succeeds but the
running the application ("java GenericsLab") above fails
with
java.lang.ClassCastException.
This is not the desired
behavior. In other words, you want to detect "type mismatch"
failure during compile
time rather than runtime.
So you will fix these
problem using Generics functionality in subsequent
steps.
4. Modify the
code so that it uses Generics functionality so that it does the
following. The modified code can be seen in Code-14 below.
(The code is provided as <lab_root>/tiger/solutions/language/GenericsLab.java.)
a. Create a List of String's
and a List of Integer's
b. Create two Iterators, one
for each list (we could use new for loop
c. In two separate
loops, print the contents pointed to by the two iterators you
created in Step b.
import java.util.*;
public class GenericsLab {
public static void main( String[] args ) {
// Create a List
of String's and a List of Integer's
LinkedList<String>
sList = new
LinkedList<String>();
LinkedList<Integer> iList = new
LinkedList<Integer>();
// Add Integers to the List of Integer's
iList.add(new
Integer(1));
iList.add(new Integer(2));
// Add Strings to the List of String's
sList.add(new String("Hello"));
sList.add(new String("World"));
// Create Iterators - we could have used new "for loop"
Iterator <String> strIterator =
sList.iterator();
Iterator <Integer> intIterator =
iList.iterator();
// Print out the contents of
the List through the Iterator
while( intIterator.hasNext()) {
System.out.println( " Contents of
the iList are:" +
intIterator.next() );
}
while ( strIterator.hasNext() ) {
System.out.println( " Contents of
the sList are: " +
strIterator.next() );
}
}
}
|
|
Code-14: Code
that uses Generics
5. Compile and run
the code by typing the following in a terminal window.
- cd <lab_root>/tiger/exercise/language
(c:\tiger\exercise\language,
if
you are not already in this directory)
- javac GenericsLab.java
- java GenericsLab
6. Observe that the
application runs successfully as following.
- Contents of the iList are:1
Contents of the iList are:2
Contents of the sList are: Hello
Contents of the sList are: World
Summary:
In this lab exercise,
you learned how to
use the Generics functionality in J2SE 5.0.
Exercise
3: Type-safe Enumerations
Introduction:
This flexible object-oriented enumerated type facility
allows you to create enumerated types with arbitrary methods and
fields. It provides all the benefits of the Typesafe Enum pattern
(
"Effective Java",
Item 21) without the verbosity and the
error-proneness.
If you
are new to
Type-safe Enumerations functionality, please take a look at the
following resources:
Steps
to follow:
1. Open <lab_root>/tiger/exercise/language/EnumLab.java.
(Code-15) This code is created using the
pre-J2SE 5.0 style, i.e. without
Enumerations.
class FootballScore {
public static final int EXTRAPOINT = 1;
public static final int TWOPOINTS = 2;
public static final int
SAFETY = 2;
public static final int FIELDGOAL = 3;
public static final int TOUCHDOWN = 6;
private int score;
public FootballScore( int score ) {
this.score = score;
}
public FootballScore() {
this.score = 0;
}
public int getScore() {
return this.score;
}
}
public class EnumLab {
public static void main( String[] args ) {
FootballScore[] myScores = {
new
FootballScore( FootballScore.TOUCHDOWN ),
new
FootballScore( FootballScore.EXTRAPOINT ),
new
FootballScore( FootballScore.FIELDGOAL ),
new
FootballScore( FootballScore.TOUCHDOWN ),
new
FootballScore( FootballScore.SAFETY ),
new
FootballScore( FootballScore.TOUCHDOWN ),
new
FootballScore( FootballScore.TWOPOINTS )
};
FootballScore[] yourScores = {
new
FootballScore( FootballScore.FIELDGOAL ),
new
FootballScore( FootballScore.TOUCHDOWN ),
new
FootballScore( FootballScore.FIELDGOAL )
};
int mytotal = calcTotal( myScores
);
int yourtotal = calcTotal(
yourScores );
System.out.println( " My football
team scored " + mytotal );
System.out.println( " Your
football team scored " + yourtotal );
if ( mytotal > yourtotal ) {
System.out.println( " My Team Won! " );
}
else if ( mytotal < yourtotal
) {
System.out.println( " Your Team Won! " );
}
else
System.out.println( "What do you know? It is a Tie !! " );
}
public static int calcTotal( FootballScore[] f ) {
int total = 0;
for ( int i = 0; i < f.length; ++i ) {
total += f[i].getScore();
}
return total;
}
}
|
Code-15: Code that does not use Enums
2. Compile
and run the code by typing the following in a
terminal window.
- cd <lab_root>/tiger/exercise/language
(cd
c:\tiger\exercise\language, if
you are not already in this directory)
- javac EnumLab.java
- java EnumLab
3. Observe the output like following. This verifies the
above code is a working code.
- My football team scored 26
Your football team scored 12
My Team Won!
4. Modify this code
so that it uses Enums
functionality. Here are some ideas:
- Replace the helper class FootballScore
with an Enum.
- Place the FootballScore Enum
type inside the EnumLab class.
- In the main method,
initialize the FootballScore[]
, with the
appropriate Enum element.
- You should be able to make use of the same calcTotal() method
5. One possible
solution can be found in Code-16 below.
public class EnumLab {
public enum
FootballScore {
TOUCHDOWN( 6 ), FIELDGOAL(
3 ), TWOPOINTS( 2 ), SAFETY( 2 ), EXTRAPOINT( 1 );
FootballScore( int value )
{
this.score =
value;
}
private final int score;
public int score() {
return score;
}
}
public static void main( String[] args ) {
FootballScore[] myScores = {
FootballScore.TOUCHDOWN,
FootballScore.EXTRAPOINT,
FootballScore.FIELDGOAL,
FootballScore.TOUCHDOWN,
FootballScore.SAFETY,
FootballScore.TOUCHDOWN,
FootballScore.TWOPOINTS
};
FootballScore[] yourScores = {
FootballScore.FIELDGOAL,
FootballScore.TOUCHDOWN,
FootballScore.FIELDGOAL
};
int mytotal = calcTotal( myScores );
int yourtotal = calcTotal( yourScores );
System.out.println( " My football team scored
" + mytotal );
System.out.println( " Your football team
scored " + yourtotal );
if ( mytotal > yourtotal ) {
System.out.println( " My
Team Won! " );
}
else if ( mytotal < yourtotal ) {
System.out.println( " Your
Team Won! " );
}
else
System.out.println( "What
do you know? It is a Tie !! " );
}
public static int calcTotal( FootballScore[] f ) {
int total = 0;
for ( int i = 0; i < f.length;
++i ) {
total +=
f[i].score();
}
return total;
}
} |
Code-16: Code that
uses Enum
6. Compile and run
the modified code by typing the following in a terminal window and
observe the
application runs successfully.
- cd <lab_root>/tiger/exercise/language
(c:\tiger\exercise\language,
if
you are not already in this directory)
- javac EnumLab.java
- java EnumLab
7. Observe the following
result.
- My football team scored 26
Your football team scored 12
My Team Won!
Summary:
In this lab exercise, you learned how to
use the Enum type in J2SE 5.0.
Exercise
4:
Enhanced For Loops
Introduction:
This new language construct eliminates the drudgery
and error-proneness of iterators and index variables when iterating
over
collections and arrays.
If you are new to
"Enhanced For Loops" functionality, please take a look at the following
resources:
Steps to follow:
1. Open <lab_root>/tiger/exercise/language/EnhancedForLab.java.
(Code-17
below)
This code
does not use Enhanced For Loops feature of J2SE 5.0 yet. You are
going to change the code in subsequent steps.
import java.util.*;
public class EnhancedForLab {
public static void main( String[] args ) {
Vector<Object> v = new
Vector<Object>();
v.add( new String( "Hello World"
) );
v.add( new Integer( 10 ) );
v.add( new Double( 11.0 ) );
v.add( new Long( 12 ) );
for ( Iterator i = v.iterator();
i.hasNext(); ) {
System.out.println( " Vector element is: " + i.next() );
}
String [] s = {
"Java 2",
"Platform",
"Standard",
"Edition",
"1.5",
"is",
"the",
"latest",
"release",
"of",
"the",
"Java",
"Platform" };
for ( int i = 0; i < s.length;
++i ) {
System.out.println( "String array element is: " + s[i] );
}
}
}
|
Code-17: Code that does not use enhanced for loop
2. Compile and run the code by typing the following in a
terminal window.
- cd <lab_root>/tiger/exercise/language
(if you are not already in this directory)
- javac EnhancedForLab .java
- java EnhancedForLab
3. Observe the output like following. This verifies the
above code is a working code.
- Vector element is: Hello World
Vector element is: 10
Vector element is: 11.0
Vector element is: 12
String array element is: Java 2
String array element is: Platform
String array element is: Standard
String array element is: Edition
String array element is: 1.5
String array element is: is
String array element is: the
String array element is: latest
String array element is: release
String array element is: of
String array element is: the
String array element is: Java
String array element is: Platform
4. Modify this code
so that it uses the J2SE 5.0 style for
loops.
- Please note that there are two "for loops" codes in this
code. The first "for loops"
code iterates on a Vector.
The second one iterates on a String
array.
You need to modify both.
5. One possible solution can be found in Code-18 below.
(The code is provided
as <lab_root>/tiger/solutions/language/EnhancedForLab.java.)
import java.util.*;
public class EnhancedForLab {
public static void main( String[] args ) {
Vector<Object> v = new
Vector<Object>();
v.add( new String( "Hello World"
) );
v.add( new Integer( 10 ) );
v.add( new Double( 11.0 ) );
v.add( new Long( 12 ) );
for ( Object o : v ) {
System.out.println( " Vector element is: " + o );
}
String [] s = {
"Java 2",
"Platform",
"Standard",
"Edition",
"1.5",
"is",
"the",
"latest",
"release",
"of",
"the",
"Java",
"Platform" };
for ( String i : s ) {
System.out.println( " String Array element is: " + i );
}
}
}
|
Code-18: Code that uses enhanced for loop
6. Compile and run
the code by typing the following in a terminal window.
- cd <lab_root>/tiger/exercise/language
- javac EnhancedForLab.java
- java EnhancedForLab
7. Observe the
application runs successfully as following.
- Vector element is: Hello World
Vector element is: 10
Vector element is: 11.0
Vector element is: 12
String Array element is: Java 2
String Array element is: Platform
String Array element is: Standard
String Array element is: Edition
String Array element is: 1.5
String Array element is: is
String Array element is: the
String Array element is: latest
String Array element is: release
String Array element is: of
String Array element is: the
String Array element is: Java
String Array element is: Platform
Summary:
In this lab exercise,
you learned how to
use the Enhanced For loops in J2SE 5.0.
Exercise 5: Static Import
Introduction:
This facility lets you avoid qualifying static members
with class names without the shortcomings of the "Constant Interface
antipattern."
If you are new to
"Static Import" functionality, please take a look at the following
resources:
Steps
to follow:
1. Open
<lab_root>/tiger/exercise/language/StaticImportLab.java
.
(Code-19 below) This code does not use Static Imports featuer
yet. You are going to modify this code in subsequent steps.
The code <lab_root>/tiger/exercise/language/emp/EmpAttribs.java
is shown in Code-20
below.
import emp.EmpAttribs;
public class StaticImportLab
{
public StaticImportLab() { }
public static void main( String[] args ) {
System.out.println( " Here are
the attributes of a employee who will be hired: " );
System.out.println( "Minimum
Salary is: " + EmpAttribs.MINSALARY );
System.out.println( "Maximum
Salary is: " + EmpAttribs.MAXSALARY );
System.out.println( "Max Vacation
Days: " + EmpAttribs.MAXVACATION );
System.out.println( "Max Raise
Percentage: " + EmpAttribs.MAXANNUALRAISEPERCENTAGE );
}
}
|
Code-19: Code
that does not use Static import
package emp;
public class EmpAttribs {
public static final int MINSALARY = 50000;
public static final int MAXSALARY = 70000;
public static final int MAXVACATION = 15;
public static final int MAXANNUALRAISEPERCENTAGE =
10;
}
|
Code-20:
Imported code
2.
Compile
and run the code by typing the following in a
terminal window.
- cd <lab_root>/tiger/exercise/language
(if you are not already in this directory)
- javac StaticImportLab .java
- java StaticImportLab
3. Observe the output like
following. This verifies the
above code is a working code.
- Here are the attributes of a employee who will be hired:
Minimum Salary is: 50000
Maximum Salary is: 70000
Max Vacation Days: 15
Max Raise Percentage: 10
4. Modify this
code so that it uses Static
Imports as
Code-21 below. (The code is provided
as <lab_root>/tiger/solutions/language/StaticImportLab.java.)
import static emp.EmpAttribs.*;
public class StaticImportLab
{
public StaticImportLab() { }
public static void main( String[] args ) {
System.out.println( " Here are
the attributes of a employee who will be hired: " );
System.out.println( "Minimum
Salary is: " + MINSALARY );
System.out.println( "Maximum
Salary is: " + MAXSALARY );
System.out.println( "Max Vacation
Days: " + MAXVACATION );
System.out.println( "Max Raise
Percentage: " + MAXANNUALRAISEPERCENTAGE );
}
}
|
Code-21: Code
that uses Static imports
5. Compile
and run
the code by typing the following in a terminal window.
- cd <lab_root>/tiger/exercise/language
- javac StaticImportLab.java
- java StaticImportLab
6. Observe that the
application runs successfully as following.
- Here are the attributes of a employee who will be hired:
Minimum Salary is: 50000
Maximum Salary is: 70000
Max Vacation Days: 15
Max Raise Percentage: 10
Summary:
In this lab exercise,
you learned how to
use the Static Imports in J2SE 5.0.
Exercice
6: Variable Arguments
Introduction:
This Variable Arguments feature of J2SE 5.0 eliminates the need for
manually boxing up
argument lists into an array when invoking methods that accept
variable-length argument lists.
Background
information:
If you used C or C++, you might be already familiar with varargs.
The issue at hand is how to have a method take a variable number of
parameters. This is possible in Java already as you can pass an
array of Objects as a single parameter. The method then uses that
array as the set of parameters. The problem with this is that the
caller is responsible for creating the array of Objects before calling
the method which is overly cumbersome.
The solution is to let the compiler do the work for you again. To
do this a new piece of syntax has been included in Java. When
defining a method you can now define the last argument in the methods
parameters to be what is referred to as a variable arity
parameter. This parameter is a type followed by an elipsis and a
variable name. In the example the format method can be called
with a String parameter and then any number of Object parameters.
If you are new to
"Variable Arguments" functionality, please take a look at the following
resources:
Steps
to follow:
1. Open <lab_root>/tiger/exercise/language/VarArgsLab.java.
(Code-23)
This code uses an array of Objects as a way to pass variable number of
parameters.
import java.util.Date;
import java.text.MessageFormat;
import java.util.*;
public class VarArgsLab
{
public VarArgsLab() { }
public String formatThis() {
Object[] args = {
"Hurricane",
new Integer( 99 ),
new
GregorianCalendar( 1999, 0, 1).getTime(),
new Double( 10E7
) };
return MessageFormat.format( "On {2}, a {0}
destroyed {1} houses and caused {3} of damage",
args );
}
public static void main( String[] args ) {
VarArgsLab v = new VarArgsLab();
System.out.println(
v.formatThis());
}
}
|
Code-23: Code
that does not use Variable Arguments
2. Compile and run the code by typing the following in a
terminal window.
- cd <lab_root>/tiger/exercise/language
(if you are not already in this directory)
- javac VarArgsLab.java
- java VarArgsLab
3. Observe the output like following. This verifies the
above code is a working code.
- On 1/1/99 12:00 AM, a Hurricane destroyed 99 houses and caused
100,000,000 of damage
4. Modify <lab_root>/tiger/exercise/language/VarArgsLab.java as shown in Code-24
below. The code fragment that needs to be modified is highlighted
in bold and blue-colored font. This code now uses variable
arguments.
import java.util.Date;
import java.text.MessageFormat;
import java.util.*;
public class VarArgsLab
{
public VarArgsLab() { }
public String formatThis() {
return( MessageFormat.format( "On
{2}, a {0} destroyed {1} houses and caused {3} of damage",
"Hurricane",
new Integer( 99 ),
new
GregorianCalendar( 1999, 0, 1).getTime(),
new Double( 10E7 ) ) );
}
public static void main( String[] args ) {
VarArgsLab v = new VarArgsLab();
System.out.println(
v.formatThis());
}
}
|
Code-24: Code
that does use Variable Arguments
5. Compile
and run
the code by typing the following in a terminal window.
- cd <lab_root>/tiger/exercise/language
(if
you are not already in this directory)
- javac VarArgsLab.java
- java VarArgsLab
6. Observe that the
application runs successfully.
- On 1/1/99 12:00 AM, a Hurricane destroyed 99 houses and caused
100,000,000 of damage
Summary:
In this lab exercise,
you learned
how to work with the VarArgs functionality in J2SE 5.0
Part
II : Metadata (Annotations)
Metadata can be roughly divided into
three types:
- Information that is
only available at compile time (source code level)
- Information
retained in the class file, but not available at runtime
- Information that can still be accessed at runtime.
If you are new
to
"Metadata" functionality, please take a look at the following resources
Exercise 1: Override
annotation
Introduction:
Programmers
sometimes overload a method when they mean to override it;
a classic example of this is the equals()
method which is defined in the Object
class.
AnnotatedClass.java
contains a simple example of this:
public boolean equals(String otherName) {
int comparisson = name.compareTo(otherName);
return (comparisson == 0);
}
The programmer
thinks that they are overriding the equals()
method from the Object
class, but since that takes an Object as a
parameter the result (which is perfectly correct Java) is an overloaded
method instead. This could, potentially cause some very subtle
and therefore hard to debug runtime errors. Adding a standard
annotation type to this can identify these problems at compile
time.
Steps to
follow:
1. Open <lab_root>/tiger/exercise/metadata/AnnotatedClass.java
(c:\tiger\exercise\metadata\AnnotatedClass.java) in a source code
editor of your choice as shown in Code-29 below. Note that the equals() method at the end does not
have
@Override annotation method yet.
/**
*
**/
public class AnnotatedClass {
private String name;
private int id;
/**
* Constructor
**/
public AnnotatedClass() {
name = "JavaONE";
}
/**
* Accessor for name
*
* @return The name value
**/
public String getName() {
return name;
}
/**
* Mutator for name
*
* @param name The new value for name
**/
public void setName(String name) {
this.name = name;
}
/**
* Accessor for id
*
* @return id
**/
public int getId() {
return id;
}
/**
* Mutator for id
*
* @param id The new id
**/
public void setId(int id) {
this.id = id;
}
/**
* Test of
equals method overriding v. overloading
* For
exercise 1 change this method
**/
public boolean
equals(String otherName) {
int
comparisson = name.compareTo(otherName);
return
(comparisson == 0);
}
}
|
Code-29:
equals() method without
@Override annotation
2. Compile the code as
following. Note that there is no compile
error. This is not desirable. What we want is compile time
detection of a problem.
- cd <lab_root>/tiger/exercise/metadata
(cd \tiger\exercise\metadata)
- javac AnnotatedClass.java
3. Add the standard
annotation @Override
before the equals() method
definition as shown in Code-30 below. (The line that needs to be
added is highlighted in blue color and in bold font.)
/**
*
**/
public class AnnotatedClass {
private String name;
private int id;
/**
* Constructor
**/
public AnnotatedClass() {
name = "JavaONE";
}
/**
* Accessor for name
*
* @return The name value
**/
public String getName() {
return name;
}
/**
* Mutator for name
*
* @param name The new value for name
**/
public void setName(String name) {
this.name = name;
}
/**
* Accessor for id
*
* @return id
**/
public int getId() {
return id;
}
/**
* Mutator for id
*
* @param id The new id
**/
public void setId(int id) {
this.id = id;
}
/**
* Test of
equals method overriding v. overloading
* For
exercise 1 change this method
**/
@Override
public boolean
equals(String otherName) {
int
comparisson = name.compareTo(otherName);
return
(comparisson == 0);
}
}
|
Code-30: equals() method with @Override
annotation
4. Compile the application.
- javac AnnotatedClass.java
5. Observe the compile time
error. This is what we want. In
other words, the
compiler has correctly detected that this is an overloaded
method and not overriden method.
- AnnotatedClass.java:55: method does not override a method from
its superclass
@Override
^
1 error
6. Change the code so that the
equals() method is truely a
overriden method as intended as shown in Code-31 below.
/**
*
**/
public class AnnotatedClass {
private String name;
private int id;
/**
* Constructor
**/
public AnnotatedClass() {
name = "JavaONE";
}
/**
* Accessor for name
*
* @return The name value
**/
public String getName() {
return name;
}
/**
* Mutator for name
*
* @param name The new value for name
**/
public void setName(String name) {
this.name = name;
}
/**
* Accessor for id
*
* @return id
**/
public int getId() {
return id;
}
/**
* Mutator for id
*
* @param id The new id
**/
public void setId(int id) {
this.id = id;
}
/**
* Test of equals method overriding v. overloading
* For exercise 1 change this method
**/
@Override
public boolean
equals(Object otherName) {
String
newName = (String) otherName;
int
comparison = name.compareTo(newName);
return
(comparison == 0);
}
}
|
Code-31: Make it overriden method
4. Compile the application.
The compilation should succeed.
- javac AnnotatedClass.java
Summary:
This type of annotation is known as a marker
annotation since it simply marks a particular piece of code and
has no parameters. This
annotation can
only be used in a class definition, not an interface. The reason
for this is that the compiler will already catch problems in
overloading rather than overriding methods in an interface.
Exercise
2:
Single-member annotation
Introduction:
Many annotations
will only require a single value to be associated with
them. Examine the Mutator.java
file which contains the definition of a single membered
annotation. The goal of this exercise is to edit the AnnotatedClass.java
file and add a Mutator
annotation to the setName
method.
There are two
approaches you can do this. The first approach is to modify the
code as following:
@Mutator(variable = "name")
public void setName(String name)
The second
approach is as following. This is possible since this is a single
member annotation we do not need to specify the
name of the member to assign the value.
However, if you try to compile this code, you will get a compiler
error. Why? The answer is
that for single member annotations the identifier used
for the member must be called value. You will
modify Mutator.java to correct this.
@Mutator("name")
public void setName(String name)
Steps to
follow:
1. Modify
<lab_root>/tiger/exercise/metadata/AnnotatedClass.java
add a Mutator
annotation to the setName
method as shown in Code-32 below. we are using the first
approach mentioned above. (The line that needs to be added is
highlighted in blue color and in bold font.)
/**
*
**/
public class AnnotatedClass {
private String name;
private int id;
/**
* Constructor
**/
public AnnotatedClass() {
name = "JavaONE";
}
/**
* Accessor for name
*
* @return The name value
**/
public String getName() {
return name;
}
/**
* Mutator for name
*
* @param name The new value for name
**/
@Mutator(variable
= "name")
public void setName(String name) {
this.name = name;
}
/**
* Accessor for id
*
* @return id
**/
public int getId() {
return id;
}
/**
* Mutator for id
*
* @param id The new id
**/
public void setId(int id) {
this.id = id;
}
/**
* Test of equals method overriding v. overloading
* For exercise 1 change this method
**/
@Override
public boolean equals(Object otherName) {
String newName = (String) otherName;
int comparison = name.compareTo(newName);
return (comparison == 0);
}
}
|
Code-32: Add
Mutator annotation
2. Compile the
application. No compile error should be generated.
- cd
<lab_root>/tiger/exercise/metadata (if you are
not in that directory already)
- javac Mutator.java AnnotatedClass.java
3. Modify the <lab_root>/tiger/exercise/metadata/AnnotatedClass.java
using the
second approach mentioned above. (Code-33)
/**
*
**/
public class AnnotatedClass {
private String name;
private int id;
/**
* Constructor
**/
public AnnotatedClass() {
name = "JavaONE";
}
/**
* Accessor for name
*
* @return The name value
**/
public String getName() {
return name;
}
/**
* Mutator for name
*
* @param name The new value for name
**/
@Mutator("name")
public void setName(String name) {
this.name = name;
}
/**
* Accessor for id
*
* @return id
**/
public int getId() {
return id;
}
/**
* Mutator for id
*
* @param id The new id
**/
public void setId(int id) {
this.id = id;
}
/**
* Test of equals method overriding v. overloading
* For exercise 1 change this method
**/
@Override
public boolean equals(Object otherName) {
String newName = (String) otherName;
int comparison = name.compareTo(newName);
return (comparison == 0);
}
}
|
Code-33: Add
Mutator annotation
4. Compile the
application. Now you will experience a compile error. As
mentioned earlier, for single member
annotations the identifier used
for the member must be called value. You will
modify Mutator.java to correct this.
- javac Exposed.java Mutator.java AnnotatedClass.java
AnnotatedClass.java:34: cannot find symbol
symbol : method value()
location: @interface j1lab.Mutator
@Mutator("name")
^
1 error
5.
Modify <lab_root>/tiger/exercise/metadata/Mutator.java as following. (Code-34) (The
line that needs to be modified is highlighted in blue color and in bold
font.)
public @interface Mutator {
String
value();
}
|
Code-34:
Mutator.java
6. Compile the
application. No compile error should be generated.
- javac Mutator.java AnnotatedClass.java
Summary:
In this exercise, you learned how to add a single-member annotation
using two diffferent approaches.
Exercise
3:
Multiple-member annotation
Introduction:
Annotations
can be defined so that they have multiple values and, where
required, default values can be provided. In this exercise, you
will learn how to define an annotation for accessor methods. This
annotation has both the name of the
variable and the type of the variable defined
as members of the annotation. You will also learn how to specify a
default value - In order to specify a
default value, add default
"value" after the member
name
and before the semi-colon. . You will also learn
how to modify the Accessor annotation so that
the variableType has a default value of "String".
Steps to
follow:
1. Examine
<lab_root>/tiger/exercise/metadata/Accessor.java.
(Code-35) This file defines an annotation for
accessor methods. (Accessor methods are the methods that accesses
value of fields and takes the form of getXXX().) This
has both the name and the type of the variable defined
as members of the annotation. It also shows how to set a default
value to one of its members.
/**
* Annotation definition for an accessor method. This
shows the use of
* multiple members.
**/
public @interface Accessor {
String variableName();
String variableType() default "String";
}
|
Code-35:
Accessor.java
2. Modify <lab_root>/tiger/exercise/metadata/AnnotatedClass.java
to add the Accessor annotation to both the getName and getId
accessor methods as
shown in Code-36 below. For getName method, do
not specify the variableType since
it is the default. (The
lines that need to be added are highlighted in blue color and in bold
font.)
/**
*
**/
public class AnnotatedClass {
private String name;
private int id;
/**
* Constructor
**/
public AnnotatedClass() {
name = "JavaONE";
}
/**
* Accessor for name
*
* @return The name value
**/
@Accessor(variableName = "name")
public String getName() {
return name;
}
/**
* Mutator for name
*
* @param name The new value for name
**/
@Mutator(variable = "name")
public void setName(String name) {
this.name = name;
}
/**
* Accessor for id
*
* @return id
**/
@Accessor(variableName =
"name", variableType = "int")
public int getId() {
return id;
}
/**
* Mutator for id
*
* @param id The new id
**/
public void setId(int id) {
this.id = id;
}
/**
* Test of equals method overriding v. overloading
* For exercise 1 change this method
**/
@Override
public boolean equals(Object otherName) {
String newName = (String) otherName;
int comparison = name.compareTo(newName);
return (comparison == 0);
}
}
|
Code-36: Add
Accessor annotations
3.
Compile the application. No
compile error should be generated.
- cd
<lab_root>/tiger/exercise/metadata (if you are
not in that directory)
- javac Mutator.java Accessor.java AnnotatedClass.java
Summary:
In this exercise, you learned how to define an annotation that contains
multiple members.
Exercise 4:
Complex Annotation Types
Introduction:
Since an
annotation is defined in the same way as an interface, it is
possible to include types in an annotation definition that are
themselves annotations. In this exercise, you will learn how to
define a complex annotation type called Name that contains a
particular
person's first and last name and another annotation that uses Name for the type of
the value.
Steps to
follow:
1.
Examine <lab_root>/tiger/exercise/metadata/Name.java
that defines that contains a
particular
person's first and last name. (Code-37)
import java.lang.annotation.*;
public @interface Name {
String first();
String last();
}
|
Code-37:
Name.java
2. Examine
<lab_root>/tiger/exercise/metadata/Reviewer.java
that defines an
annotation type that uses Name for the type of
the value. (Code-38)
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
public @interface Reviewer {
Name value();
}
|
Code-38:
Reviewer.java
3. Add a
Reviewer annotation to the <lab_root>/tiger/exercise/metadata/AnnotatedClass.java
file using your own name as shown in Code-39 below. Since the type
used for the Reviewer annotation is itself an annotation you will need
to nest the definition correctly, i.e.
@AnnotationName(@AnnotationType(...)).
(The line that needs to be added
are highlighted in blue color and in bold font.)
/**
*
**/
@Reviewer(@Name(first
= "James",
last = "Gosling"))
public class AnnotatedClass {
private String name;
private int id;
/**
* Constructor
**/
public AnnotatedClass() {
name = "JavaONE";
}
/**
* Accessor for name
*
* @return The name value
**/
@Accessor(variableName = "name")
public String getName() {
return name;
}
/**
* Mutator for name
*
* @param name The new value for name
**/
@Mutator(variable = "name")
public void setName(String name) {
this.name = name;
}
/**
* Accessor for id
*
* @return id
**/
@Accessor(variableName = "name", variableType = "int")
public int getId() {
return id;
}
/**
* Mutator for id
*
* @param id The new id
**/
public void setId(int id) {
this.id = id;
}
/**
* Test of equals method overriding v. overloading
* For exercise 1 change this method
**/
@Override
public boolean equals(Object otherName) {
String newName = (String) otherName;
int comparison = name.compareTo(newName);
return (comparison == 0);
}
}
|
Code-39: Add
Reviewer annotation
4. Compile the
application. No compile error should be generated.
- cd
<lab_root>/tiger/exercise/metadata (if you are
not in that directory)
- javac Mutator.java Accessor.java
Name.java Reviewer.java
AnnotatedClass.java
Summary:
In
this
exercise, you will learn how to define an annotation that uses another
annotation.
Exercise 5:
Meta-Annotations
Introduction:
Annotation types
designed for annotating annotation type declarations
are called meta-annotation
types.
The package java.lang.annotation
provides several of these. The meta-annotation types
can be used to restrict the annotation types they annotate.
- @Target(ElementType.FIELD)
- @Retention(RetentionPolicy.RUNTIME)
The Target
meta-annotation is used to specify where the annotation is to be
applied. The possible choices include the following:
- FIELD
- TYPE
(Class, interface or enum definition)
- METHOD
- PARAMETER
- CONSTRUCTOR
- LOCAL_VARIABLE
- ANNOTATION_TYPE
- PACKAGE.
The
Retention meta-annotation is used to specify how long an annotation is
retained. The possible choices include the following:
- SOURCE: This
annotation information is only retained in the
source code and is not recorded in the generated class file.
- CLASS: This annotation is recorded in the class file by the
compiler, but need not be retained by the virtual machine at
runtime. This is the default if @Retention is not specified.
- RUNTIME: Annotations are recorded in the class file by the
compiler and retained by the virtual machine at runtime and can be read
reflectively.
In this exercise, you learn how to use two
meta-annotation types.
Steps to
follow:
1. Examine the
<lab_root>/tiger/exercise/metadata/Exposed.java. (Code-40) The
code shows the use of two meta-annotation types.
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Exposed {
String value();
}
|
Code-40:
Exposed.java
2. Modify <lab_root>/tiger/exercise/metadata/AnnotatedClass.java and add an Exposed annotation to setName method as shown in
Code-41.
/**
*
**/
@Reviewer(@Name(first = "James", last = "Gosling"))
public class AnnotatedClass {
private String name;
private int id;
/**
* Constructor
**/
public AnnotatedClass() {
name = "JavaONE";
}
/**
* Accessor for name
*
* @return The name value
**/
@Accessor(variableName = "name")
public String getName() {
return name;
}
/**
* Mutator for name
*
* @param name The new value for name
**/
@Exposed("name")
@Mutator("name")
public void setName(String name) {
this.name = name;
}
/**
* Accessor for id
*
* @return id
**/
@Accessor(variableName = "name", variableType = "int")
public int getId() {
return id;
}
/**
* Mutator for id
*
* @param id The new id
**/
public void setId(int id) {
this.id = id;
}
/**
* Test of equals method overriding v. overloading
* For exercise 1 change this method
**/
@Override
public boolean equals(Object otherName) {
String newName = (String) otherName;
int comparison = name.compareTo(newName);
return (comparison == 0);
}
}
|
Code-41: Exposed
annotation to a method
3. Compile the
application. The compilation should fail as following.
- javac Mutator.java
Accessor.java Name.java Reviewer.java
Exposed.java AnnotatedClass.java
AnnotatedClass.java:33:
annotation type not applicable to this kind of
declaration
@Exposed("name")
^
1 error
4.
Modify <lab_root>/tiger/exercise/metadata/AnnotatedClass.java as shown in
Code-42. (Remove @Exposed("name")
annotation
from the
setName method and
add it to
name field.)
/**
*
**/
@Reviewer(@Name(first = "James", last = "Gosling"))
public class AnnotatedClass {
@Exposed("name") private
String name;
private int id;
/**
* Constructor
**/
public AnnotatedClass() {
name = "JavaONE";
}
/**
* Accessor for name
*
* @return The name value
**/
@Accessor(variableName = "name")
public String getName() {
return name;
}
/**
* Mutator for name
*
* @param name The new value for name
**/
@Mutator(variable = "name")
public void setName(String name) {
this.name = name;
}
/**
* Accessor for id
*
* @return id
**/
@Accessor(variableName = "name", variableType = "int")
public int getId() {
return id;
}
/**
* Mutator for id
*
* @param id The new id
**/
public void setId(int id) {
this.id = id;
}
/**
* Test of equals method overriding v. overloading
* For exercise 1 change this method
**/
@Override
public boolean equals(Object otherName) {
String newName = (String) otherName;
int comparison = name.compareTo(newName);
return (comparison == 0);
}
}
|
Code-42: Exposed
annotation to a field
5. Compile the application. This time,
it should succeed.
- javac Mutator.java Accessor.java Name.java Reviewer.java
Exposed.java AnnotatedClass.java
6.
If you want
to have an annotation
applicable to multiple places you can use the array mechanism as
following:
- @Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
Summary:
In this exercise, you learned how to use Target and Retention
meta-annotation types.
Exercise 6:
Reading Annotations At Runtime
Introduction:
As explained in
exercise 5, it is possible to specify that annotations
are maintained in the classfile and made available via the runtime
environment of the JVM. To access the runtime information you
need
to use the reflection APIs which have been modified in J2SE 5.0 to
include support for Metadata.
- The Class
class now has two additional methods:
- getAnnotations()
which returns an array of all annotations for the class
- getAnnotation(Class
c) which
returns the information about the annotation of type c
passed as a parameter.
- The Method, Constructor
and Field
classes also have two new methods:
- getAnnotation(Class
c) which
is the same as for Class
- getDeclaredAnnotations()
which returns an array of annotations declared for the Method, Constructor
or Field.
Steps to
follow:
1. Add the Runtime Retention
to Accessor and Mutator
annotations. (Code -43 and Code-44). Exposed and Reviewer
annotations have already Runtime Retention. (The line that
needs to be added are highlighted in blue color and in bold font.)
import
java.lang.annotation.*;
/**
* Annotation definition for an accessor method. This
shows the use of
* multiple members.
**/
@Retention(RetentionPolicy.RUNTIME)
public @interface Accessor {
String variableName();
String variableType() default "String";
}
|
Code-43:
Accessor annotation with Runtime Retention
import
java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
public @interface Mutator {
String variable();
}
|
Code-44: Mutator
annotation with Runtime Retention
2. Compile the
application.
- javac Mutator.java Accessor.java Name.java Reviewer.java
Exposed.java AnnotatedClass.java
3.
Examine the code of AnnotationReader.java, which shows some
sample code for reading the annotations on an AnnotatedClass
object. (Code-45 below)
import java.lang.annotation.*;
/**
* Read annotation information from a class at runtime using
the new
* JDK1.5 reflection features
**/
public class AnnotationReader {
AnnotatedClass ac;
/**
* Constructor
**/
public AnnotationReader() {
ac = new AnnotatedClass();
}
/**
* Print out runtime annotation information
**/
public void printAnnotations() {
Class c = ac.getClass();
Annotation[] annotations = c.getAnnotations();
int numberOfAnnotations = annotations.length;
System.out.println("Class " + c.getName() + " has " +
numberOfAnnotations + " annotations");
for (int i = 0 ; i < numberOfAnnotations; i++) {
System.out.println("Annotation " + i +
": " + annotations[i] +
", type" +
annotations[i].annotationType().getName());
}
}
/**
* Main entry point
*
* @param args Command line arguments
**/
public static void main(String[] args) {
AnnotationReader ar = new AnnotationReader();
ar.printAnnotations();
}
}
|
Code-45:
AnnotationReader.java
4. Compile
AnnotationReader.java
- javac Mutator.java Accessor.java Name.java Reviewer.java
Exposed.java AnnotatedClass.java AnnotationReader.java
5. Run the AnnotationReader
application.
- java AnnotationReader
Class AnnotatedClass has 1 annotations
Annotation 0: @Reviewer(value=@Name(first=James, last=Gosling)),
typeReviewer
Summary:
In this exercise, you learned
how to retrieve annotation information
during runtime.
Part III: Concurrency Utilities (JSR-166)
The
java.util.concurrent
,
java.util.concurrent.atomic
,
and
java.util.concurrent.locks
packages provide a
powerful,
extensible framework of high-performance, scalable, thread-safe
building
blocks for developing concurrent classes and applications, including
thread
pools, thread-safe collections, semaphores, a task scheduling
framework,
task synchronization utilities, atomic variables, and locks.
The
addition of
these packages to the core class library frees the programmer from the
need
to craft these utilities by hand, in much the same manner that the
Collections Framework did for data structures. Additionally, these
packages
provide low-level primitives for advanced concurrent programming which
take
advantage of concurrency support provided by the processor, enabling
programmers to implement high-performance, highly scalable concurrent
algorithms in the Java language to a degree not previously possible
without
resorting to native code.
Exercise 1:
Executor & ThreadPool
Introduction:
In this
exercise, you will learn how to use create a pool of threads using the
ThreadPoolExecutor class. You also will learn how these pool of threads
are used to execute tasks. You will also learn
how to use Executors utility class for creating a pool of threads.
Steps to
follow:
1. cd <lab_root>/tiger/exercise/concurrency/threadpool
- Windows: cd \tiger\exercise\concurrency\threadpool
- Solaris/Linux: cd
/home/username/tiger/exercise/concurrency/threadpool
2. Examine ConcurrencyExample.java.
(Code-50 below)
This class will act as a listener on an IP port and echo characters
sent to it when a connection is made. This is similar in
functionality to a web server type application. Since we want to
be able to handle lots of short lived connections simultaneously, we
would create a separate thread of execution for each connection.
Since thread creation is a costly operation to the JVM, the best way to
achieve this is to create and use a pool of threads, which can be
re-used by new
connections when a previous connection has finished with it.
For
this example, we use an implementation of the Executor
interface, ThreadPoolExecutor.
To create a new instance of this, we must also first create a Queue to be
used for the pool, which in this example is an ArrayBlockingQueue
which provides a fixed sized, queue which is protected to ensure
multiple threads can add items without contention problems.
/**
* Concurrency utilities (JSR-166) example
**/
package threadpool;
import java.net.*;
import java.io.*;
import java.util.concurrent.*;
public class ConcurrencyExample {
private final static int MAX_THREADS = 2;
private final ServerSocket serverSocket;
private final
ThreadPoolExecutor pool;
private final
ArrayBlockingQueue<Runnable>
workQueue;
/**
* Constructor
**/
public ConcurrencyExample(int port, int poolSize)
throws IOException {
/* Create a new
ServerSocket to listen for incoming connections */
serverSocket = new
ServerSocket(port);
/* In order to create a pool
of threads, we must first have a queue
* that will be used to
hold the work tasks before they are executed
* For this example we
use a ArrayBlockingQueue that can hold the
* same number of tasks
as we have maximum threads
*/
workQueue = new
ArrayBlockingQueue<Runnable>(MAX_THREADS);
/* Now create a
ThreadPool. The initial and maximum number of
* threads are the same
in this example
*/
pool = new
ThreadPoolExecutor(MAX_THREADS, MAX_THREADS,
60, TimeUnit.SECONDS, workQueue);
}
/**
* Service requests
**/
public void serviceRequests() {
int count = 1;
int qLength = 0;
try {
for
(;;) {
if ((qLength = workQueue.size()) > 0)
System.out.println("Queue length is " + qLength);
pool.execute(new
ConnectionHandler(serverSocket.accept(), count++));
}
} catch (IOException ioe) {
System.out.println("IO Error in ConnectionHandler: " +
ioe.getMessage());
pool.shutdown();
}
}
/**
* Main entry point
*
* @param args Command line arguments
**/
public static void main(String[] args) {
System.out.println("Listening for connections...");
ConcurrencyExample ce = null;
try {
ce =
new ConcurrencyExample(8100, 4);
/*
* Serve incoming connection request until interrupted.
*/
ce.serviceRequests();
} catch (IOException ioe) {
System.out.println("IO Error creating listener: " + ioe.getMessage());
}
}
}
|
Code-50: ConcurrencyExample.java
3. Examine
ConnectionHandler.java.
(Code-51 below)
/**
* Concurrency utilities (JSR-166) example
**/
package threadpool;
import java.io.*;
import java.net.*;
import java.util.concurrent.*;
public class ConnectionHandler implements Runnable {
private final Socket socket;
private final int connectionID;
/**
* Constructor
*
* @param socket The socket on which incoming
data will arrive
**/
public ConnectionHandler(Socket socket, int
connectionID) {
this.socket = socket;
this.connectionID =
connectionID;
}
/**
* run method to do the work of the
handler
**/
public void run() {
System.out.println("Connection " + connectionID + ", started");
try {
InputStream is = socket.getInputStream();
// Loop to do something with the socket here
while (true) {
byte[] inData = new byte[100];
/* If the number of bytes read is less than zero then the
connection
* has been terminated so we end the thread
*/
if (is.read(inData) < 0)
break;
System.out.print("[" + connectionID + "]: " + new String(inData));
}
} catch (IOException ioe) {
//
Ignore
}
System.out.println("Connection " + connectionID + ", ended");
}
}
|
Code-51: ConnectionHandler.java
4. Compile the
code.
5. Run the
ConcurrencyExample application in a terminal window. It will say
it is listening for connections as shown below.
- cd <lab_root>/tiger/exercise/concurrency
- Windows:
cd \tiger\exercise\concurrency
- Solaris/Linux:
cd /home/username/tiger/exercise/concurrency
- java -cp .
threadpool.ConcurrencyExample (Don't forget the dot right after -cp.)
Listening for connections...
6. Examine
<lab_root>/tiger/exercise/concurrency/threadpool/Echo.java. (Code-52
below) This code plays the role of a client making a socket
connection to the ConcurrencyExample server and then sends some data.
/**
* Concurrency utilities (JSR-166) example
**/
package threadpool;
import java.net.*;
import java.io.*;
import java.util.concurrent.*;
/**
* Echo characters from the keyboard to a socket.
Simplified version of
* telnet
**/
public class Echo {
/**
* Main entry point
*
* @param args Command line arguments
**/
public static void main(String[] args) {
String host = null;
int port = 0;
if (args.length < 2) {
host = "127.0.0.1";
port = 8100;
}
OutputStream os = null;
try {
Socket s = new Socket(host, port);
os = s.getOutputStream();
System.out.println("Connection
established to server. Type characters and press <ENTER> to
send");
System.out.println("Type EXIT and press
<RETURN> to exit");
/* Read data from the standard
input and send it to the remote socket */
while (true) {
byte[] inData = new
byte[100];
System.in.read(inData);
String inString = new
String(inData);
if (inString.substring(0,
4).compareTo("EXIT") == 0)
System.exit(1);
os.write(inData);
}
} catch (Exception e) {
System.out.println("Failed to connect to
remote host: " +
e.getMessage());
}
}
}
|
Code-52: Echo.java
7. Open
another terminal window - we will call this terminal window #2.
And
establish a connection to the
ConcurrencyExample server you started in the previous step. And
send a message as shown below. (This is
the first client.)
- cd <lab_root>/tiger/exercise/concurrency
(cd \tiger\exercise\concurrency)
- Windows:
cd \tiger\exercise\concurrency
- Solaris/Linux:
cd /home/username/tiger/exercise/concurrency
- java -cp .
threadpool.Echo (then type in something at the cursor as shown below as
bold charactors and the press RETURN key)
Connection established to server. Type characters and press
<ENTER> to send
Type EXIT and press <RETURN> to exit
Hello, username. Life is Good.
8. Observe the message that was sent in
the previous step being
displayed in the server's terminal window (terminal window #1).
- Connection 1, started
[1]: Hello, username. Life is
Good.
9. Open another terminal window - we
will call this terminal window #3. And establish the 2nd
connection to the
ConcurrencyExample server and send a message
as shown below. (This is the 2nd client.) Everything will work
the same since the thread pool has a
size of two.
- cd <lab_root>/tiger/exercise/concurrency
(cd \tiger\exercise\concurrency)
- Windows:
cd \tiger\exercise\concurrency
- Solaris/Linux:
cd /home/username/tiger/exercise/concurrency
- java -cp .
threadpool.Echo (then type in
something at the cursor as shown below as bold charactors and the press
RETURN key)
Connection
established to server. Type characters and press
<ENTER> to send
Type EXIT and press <RETURN> to exit
This is a message from the 2nd
client.
10. Observe
the
message that was sent in the previous step being displayed in the
server's terminal window.
- Connection 2, started
[2]: This is a message from the 2nd client
11. Open another terminal window
- we will call this terminal window #4. And
establish a connection to the
ConcurrencyExample server you started in the step 2 and send a message
as shown below. (This is the 3rd client.)
- cd <lab_root>/tiger/exercise/concurrency
- Windows:
cd \tiger\exercise\concurrency
- Solaris/Linux:
cd /home/username/tiger/exercise/concurrency
- java -cp .
threadpool.Echo (then type in
something at the cursor as shown below as bold charactors and the press
RETURN key)
Connection
established to server. Type characters and press
<ENTER> to send
Type EXIT and press <RETURN> to exit
This is a message from the 3rd
client.
This time,
although you will see the
message saying that you are connected to the server any charaters you
type will not be shown in the server's terminal window (terminal window
#1). Why?
12. In one of
the
terminal windows with an open connection, let's say the 2nd client,
type EXIT and
press
<RETURN> to terminate the connection. Look at the server
window and you will see that the last connection you tried to make is
now working because a thread is available.
- Connection 2, ended
Connection 3, started
[3]: This is a message from the 3rd client
13. Terminate all
clients and the server by pressing CTRL/C in each terminal window.
14. There is a
utility method in the Executors
class that can be used to get a ThreadPoolExecutor
more easily than by instantiating your own. Look at the
documentation for J2SE 5.0,
specifically the Executors
class. Re-write the ConcurrencyExample
code to use the Executors
utility method as shown below in Code-53. Also increase the
MAX_THREADS to 3. The code fragments that need to be modified are
highlighted in bold font.
/**
*
* Concurrency utilities (JSR-166) example
**/
package j1lab.threadpool;
import java.net.*;
import java.io.*;
import java.util.concurrent.*;
public class ConcurrencyExample {
private final
static int MAX_THREADS = 3;
private final ServerSocket serverSocket;
private final ThreadPoolExecutor pool;
private final ArrayBlockingQueue<Runnable>
workQueue;
/**
* Constructor
**/
public ConcurrencyExample(int port, int poolSize)
throws IOException {
/* Create a new
ServerSocket to listen for incoming connections */
serverSocket = new
ServerSocket(port);
/* Use the Exectors factory method to get a ThreadPool */
pool = Executors.newFixedThreadPool(MAX_THREADS);
}
/**
* Service requests
**/
public void serviceRequests() {
int count = 1;
int qLength = 0;
try {
for
(;;) {
if ((qLength = workQueue.size()) > 0)
System.out.println("Queue length is " + qLength);
pool.execute(new ConnectionHandler(serverSocket.accept(), count++));
}
} catch (IOException ioe) {
System.out.println("IO Error in ConnectionHandler: " +
ioe.getMessage());
pool.shutdown();
}
}
/**
* Main entry point
*
* @param args Command line arguments
**/
public static void main(String[] args) {
System.out.println("Listening for connections...");
ConcurrencyExample ce = null;
try {
ce =
new ConcurrencyExample(8100, 4);
ce.serviceRequests();
} catch (IOException ioe) {
System.out.println("IO Error creating listener: " + ioe.getMessage());
}
}
}
|
Code-53: Revised ConcurrencyExample.java
15. Now repeat the same steps you've done in this exercise like
following:
- Start the server in a terminal window
- Start 4 clients in different terminal window
- Observe the 4th client does not trigger the server window to
display the data
- Terminate one of the first three clients and then observe the
data that was sent by the 4th client now gets displayed in the server's
terminal window
Summary:
In this
exercise, you will learn how to use create a pool of threads using the
ThreadPoolExecutor class. You also will learn how these pool of threads
are used to execute tasks. You also learned how to use Executors
utility class for creating a pool of threads.
Exercise 2:
Callable & Future
Introduction:
In
this
exercise, we will look at the use of the Callable
interface and a Future
object to allow a child thread to return a result to a parent
thread.
Steps to
follow:
1. cd <lab_root>/tiger/exercise/concurrency/future
- Windows: cd \tiger\exercise\concurrency\future
- Solaris/Linux: cd
/home/username/tiger/exercise/concurrency/future
2. Examine CallableExample.java.
(Code-55 below)
The
CallableExample class implements the Callable
interface. It implements call()
method that returns a String object.
Notice the use of generics <String> to specify the type of the
object
returned.
/**
*
* Concurrency utilities (JSR-166) example
**/
package future;
import java.net.*;
import java.io.*;
import java.util.concurrent.*;
/**
* A simple implementation of the Callable interface that
can return a value
* to the thread that started it
**/
public class CallableExample implements Callable<String> {
/**
* The entry point called when this object is invoked
as a new thread
* of execution
*
* @returns A String as a simple result
**/
public String call() {
System.out.println("Starting call() method in second
thread");
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
// Ignore
}
System.out.println("Completed call() method in
second thread");
return "Finished";
}
}
|
Code-55: CallableExample.java
3. Examine
the FutureExample.java
class in the same directory.
The FutureExample class uses
the CallableExample
class to do some work in a separate thread while completing some work
itself (in this case there is no real work, we use a sleep() call
to simulate this).
/**
*
* Concurrency utilities (JSR-166) example
**/
package future;
import java.net.*;
import java.io.*;
import java.util.concurrent.*;
/**
* Demonstration of the use of a Future to return the
results from a
* Callable. This shows the simplicity of
synchronising two concurrent
* threads
**/
public class FutureExample {
/**
* Constructor
**/
public FutureExample() {
}
/**
* Run the test
**/
public void runTest() {
/* Use the Executors utility method to get an
ExecutorService for a
* separate thread of execution
*/
ExecutorService threadExecutor =
Executors.newSingleThreadExecutor();
System.out.println("Starting test in first thread");
/* Now attempt to run a task in the second
thread */
try {
Future<String> future =
threadExecutor.submit(new CallableExample());
/* Set this sleep to either 100 0r
1000 to see the synchronisation
* effects
*/
Thread.sleep(1000);
System.out.println("First thread work
complete. Asking future for result");
String result = future.get();
System.out.println("Result from Future
is " + result);
} catch (Exception e) {
System.out.println("Got an exception
executing the test");
System.out.println(e.getMessage());
}
/* Shutdown the second thread so the program
terminates gracefully
*/
threadExecutor.shutdown();
}
/**
* Main entry point
*
* @param args The command line arguments
**/
public static void main(String[] args) {
FutureExample fe = new FutureExample();
fe.runTest();
}
}
|
Code-55:
FutureExample.java
4. Compile the
code.
- cd <lab_root>/tiger/exercise/concurrency/future
- Windows:
cd \tiger\exercise\concurrency\future
- Solaris/Linux:
cd /home/username/tiger/exercise/concurrency/future
- javac
*.java
5. Run the
FutureExample application in a terminal window. It will print messages
to show what is happening
with the two threads in respect of starting and completing the
work as shown below.
- cd <lab_root>/tiger/exercise/concurrency
- Windows:
cd \tiger\exercise\concurrency
- Solaris/Linux:
cd /home/username/tiger/exercise/concurrency
- java -cp .
future.FutureExample
Starting test in first thread
Starting call() method in second thread
Completed call() method in second thread
First thread work complete. Asking future for result
Result from Future is Finished
6. Modify the sleep
parameters in the two threads to make the
main thread complete more quickly than the CallableExample
thread. Notice the change in the order of the messages.
Notice that the two threads still remain correctly synchronized.
Solution
is provided in the <lab_root>/tiger/solutions/concurrency/ResourceUser.java
.
summary:
In
this
exercise, you looked at the use of the Callable
interface and a Future
object so that a child thread can return a result to a parent
thread.
Exercise 3:
Semaphore
Introduction:
For
this
exercise we will look at the use of a semaphore to restrict
access to a fixed size pool of resources. Semaphores can be used
to provide a fixed number of simultaneous accesses to a particular
resource.
Steps to
follow:
1. cd <lab_root>/tiger/exercise/concurrency/semaphore
- Windows: cd \tiger\exercise\concurrency\semaphore
- Solaris/Linux: cd
/home/username/tiger/exercise/concurrency/semaphore
2. Examine ResourcePool.java.
(Code-57 below)
This provides a pool
of resources (they could be
things like JDBC connections, but for this example they are simple Integer
objects). In the constructor method of this class, a Semaphore
is created with the same count as the number of resources available (in
this case two). When someone wants a resource, the getResource
method calls the aquire method
of the Semaphore class. If the
count for the semaphore is less than the initialised value the method
continues. If all reources are currently in use the method will
block until a resource becomes available. One of the most useful
features of the semaphore is that the resource get and return methods
can be in the same class and can be accessed concurrently by different
threads (unlike if a simple count had been used and the methods were
synchronized).
/**
*
* Concurrency utilities (JSR-166) example
**/
package semaphore;
import java.net.*;
import java.io.*;
import java.util.concurrent.*;
/**
* Use a Semaphore to control the usage of a pool of
resources. If a
* consumer requests a resource when all are being used the
consumer
* will block until a resource is returned and made
available.
**/
public class ResourcePool {
private int poolSize;
private Semaphore available;
private Integer[] resources;
private boolean[] used;
/**
* Constructor
*
* @param poolSize Size of fixed pool of resources
**/
public ResourcePool(int poolSize) {
this.poolSize = poolSize;
/* Create a pool of resources (for this
example just a set of Integer
* objects. Create a new semaphore
to control access to the resources
*/
available = new Semaphore(poolSize);
used = new boolean[poolSize];
resources = new Integer[poolSize];
for (int i = 0; i < poolSize; i++)
resources[i] = new Integer(i);
}
/**
* Get a resource. If all are currently being
used this will block
* until one is returned to the pool. This is a
synchronised method
* to make the code fully thread safe.
*
* @return The resource to use
**/
public Integer getResource() {
try {
available.acquire();
} catch (InterruptedException ie) {
// Ignore
}
for (int i = 0; i < poolSize; i++) {
if (used[i] == false) {
used[i] = true;
return resources[i];
}
}
return null;
}
/**
* Return a resource to the pool
*
* @param resource The resource being returned to the pool
**/
public void putResource(Integer resource) {
/* Note use of auto-unboxing */
used[resource] = false;
available.release();
}
}
|
Code-57: ResourcePool.java
3. Compile the
code.
- cd <lab_root>/tiger/exercise/concurrency/semaphore
- Windows:
cd \tiger\exercise\concurrency\semaphore
- Solaris/Linux:
cd /home/username/exercise/concurrency/semaphore
- javac
*.java
4. Run the ResourceUser
application in a terminal window.
- cd <lab_root>/tiger/exercise/concurrency
(cd \tiger\exercise\concurrency)
- java -cp .
semaphore.ResourceUser
[1] trying to get resource
[1] aquired resource
[2] trying to get resource
[2] aquired resource
[3] trying to get resource
[1] releasing resource
[3] aquired resource
[2] releasing resource
[1] trying to get resource
[1] aquired resource
[2] trying to get resource
[1] releasing resource
[2] aquired resource
- To
terminate the application press <CTRL> + C.
This class
creates three threads that try
to access the reources in the pool. By using a semaphore to
restrict access to the resources this demonstrates that one of the
three threads will always block when trying to aquire a resource.
The threads hold a resource for different lengths of time to illustrate
this more clearly.
5. Change the
values used when creating the ResourceUser
objects to change the behaviour of the application. For example
make each ResourceUser
hold the resource for the same time.
NOTE:
Strictly speaking this code is not thread safe. Do
you know why? If not, look in the <lab_root>/tiger/solutions/concurrency/ResourcePool.java
to see
how the class should be coded to make it fully thread
safe.
Summary:
In this
exercise, you learned how to use a semaphore to restrict
access to a fixed size pool of resources.
Exercise 4:
Blocking Queue
Introduction:
For this
exercise we will look at the use of a BlockingQueue
to synchronize the recording of logging messages from a number of
separate threads.
Steps
to follow:
1. cd <lab_root>/tiger/exercise/concurrency/queue
- Windows: cd \tiger\exercise\concurrency\queue
- Solaris/Linux: cd
/home/username/tiger/exercise/concurrency/queue
2. Examine Logger.java.
(Code-58 below)
The Logger class
takes a BlockingQueue
as an argument to the constructor to use as a queue to hold messages
that need to be logged. The BlockingQueue
will ensure thread safe operations when multiple threads are trying to
add elements to the list.
/**
*
* Concurrency utilities (JSR-166) example
**/
package queue;
import java.net.*;
import java.io.*;
import java.util.concurrent.*;
/**
* Runnable that consumes information placed in a blocking
queue. This
* demonstrates the simple synchonisation that can be
achieved with the
* new BlockingQueue class.
**/
public class Logger implements Runnable {
private BlockingQueue<String> messageQueue;
/**
* Constructor
*
* @param messageQueue The queue that will be used to pass
messages
* between the two threads
**/
public Logger(BlockingQueue<String> messageQueue) {
this.messageQueue = messageQueue;
}
/**
* Run method simply takes messages from the queue
when they're there
* and pushes them to the stdout
**/
public void run() {
try {
while (true) {
System.out.println("LOG MSG:
" + messageQueue.take());
}
} catch (InterruptedException ie) {
// Ignore
}
}
}
|
Code-58:
Logger.java
3. Examine
the MsgSender.java.
(Code-59 below)
The MsgSender class creates an ArrayBlockingQueue,
which is the simplest concrete implementation of the BlockingQueue
interface. It uses this to send messages to the Logger
object.
/**
*
* Concurrency utilities (JSR-166) example
**/
package queue;
import java.net.*;
import java.io.*;
import java.util.concurrent.*;
/**
* Send messages to a Logger via a BlockingQueue
**/
public class MsgSender implements Runnable {
private BlockingQueue<String> messageQueue;
private int id;
private int count;
private long pause;
/**
* Constructor
*
* @param messageQueue The quese to send messages to
* @param id The ID number of this sender
* @param count The number of messages to send
* @param pause The pause between sending each message
**/
public MsgSender(BlockingQueue<String> messageQueue, int
id,
int count, long pause) {
this.messageQueue = messageQueue;
this.id = id;
this.count = count;
this.pause = pause;
}
/**
* Run method to send the messages
**/
public void run() {
try {
for (int i = 0; i < count; i++) {
messageQueue.put("ID " + id
+ ": log message number " + i);
Thread.sleep(pause);
}
} catch (InterruptedException ie) {
// Ignore
}
}
/**
* Main entry point for running test scenario
*
* @param args The command line arguments
**/
public static void main(String[] args) {
/* For the test we will need a BlockingQueue
to be used by both threads
* Initially use an ArrayBlockingQueue
which is the simplest concrete
* implementation of the BlockingQueue
interface. The constructor takes
* the size of the queue as a parameter
*/
ArrayBlockingQueue<String> queue = new
ArrayBlockingQueue<String>(10);
/* Use the utility method from the Executors
class to get an
* ExcutorService reference that will
allow us to execute a single
* thread
*/
ExecutorService loggerExecutor =
Executors.newSingleThreadExecutor();
loggerExecutor.execute(new Logger(queue));
/* Again use the utility Executors class to
get a new ExecutorService
* for a second new thread and pass a
MsgSender instance to it to run
*/
ExecutorService senderExecutor =
Executors.newSingleThreadExecutor();
senderExecutor.execute(new MsgSender(queue, 1, 10,
500));
}
}
|
Code-59: MsgSender.java
4.
Compile the code.
- cd <lab_root>/tiger/exercise/concurrency/queue
- Windows:
cd \tiger\exercise\concurrency\queue
- Solaris/Linux:
cd /home/username/tiger/exercise/concurrency/queue
- javac
*.java
5. Run the MsgSender
application in a terminal window.
- cd <lab_root>/tiger/exercise/concurrency
- Windows:
cd \tiger\exercise\concurrency
- Solaris/Linux:
cd /home/username/tiger/exercise/concurrency
- java
-cp . queue.MsgSender
LOG MSG: ID 1: log message number 0
LOG MSG: ID 1: log message number 1
LOG MSG: ID 1: log message number 2
LOG MSG: ID 1: log message number 3
LOG MSG: ID 1: log message number 4
LOG MSG: ID 1: log message number 5
LOG MSG: ID 1: log message number 6
LOG MSG: ID 1: log message number 7
LOG MSG: ID 1: log message number 8
LOG MSG: ID 1: log message number 9
- To
terminate the application press <CTRL> + C.
6. Since this is
a single thread sending
messages to the logger the output is not very interesting. Modify the main
method in the MsgSender.java
class to use multiple message senders. You will need to make the
following changes:
- Create multiple instances of the MsgSender class with different
identifiers and pause times
- Change the Executor to
a ThreadPoolExecutor
to support the right number of threads.
- Have the threads use different pauses between sending messages so
you can see the interleaving of the different threads of
execution.
Congratulations!
You have
successfully completed the "Experience
New Functionaly in J2SE 5.0" Hands-on lab.
Please
send your feedback to handsonlab-tiger@yahoogroups.com.