Sun Microsystems Logo

 

 

The Tiger Roars: Experience the New Functionality in J2SETM 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

Software needed for the lab:


Notations used in this documentation

Things to check before you start the lab:  

Once you installed J2SE 5.0, please make sure you do the following:

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 )
  1. AutoBoxing
  2. Generics
  3. Type-safe Enumerations
  4. Enhanced For Loops
  5. Static Imports
  6. Variable Arguments
Part 2 : Metadata ( 30 minutes )  
  1. Override annotation
  2. Single-member annotation
  3. Multi-member annotation
  4. Complex annotation
  5. Meta-annotation
  6. Read annotations at runtime

Part 3 : Concurrency ( 30 minutes )
    1. Executor and ThreadPool
    2. Callable and Future
    3. Semaphore
    4. Blocking queue

Resources:


Where to send questions or feedbacks on this lab and public discussion forums:



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.
3.  Observe the output like following.  This verifies the above code is a working code. 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.

6. Observe the following result.  You have successfully modified the code so that it uses the AutoBoxing.

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  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.

6. Observe that the application runs successfully as following.
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.

3.  Observe the output like following.  This verifies the above code is a working code. 

4. Modify this code so that it uses Enums functionality. Here are some ideas:

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.

7.  Observe the following result.

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.

3.  Observe the output like following.  This verifies the above code is a working code. 

4. Modify this code so that it uses the J2SE 5.0 style for loops.

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.

7. Observe the application runs successfully as following.

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.

3.  Observe the output like following.  This verifies the above code is a working code. 
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.
6. Observe that the application runs successfully as following.

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.

3.  Observe the output like following.  This verifies the above code is a working code.
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.
6. Observe that the application runs successfully. 

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:
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.
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. 
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.
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.

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.
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.
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.
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.
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.
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. 
The Target meta-annotation is used to specify where the annotation is to be applied.  The possible choices include the following:
The Retention meta-annotation is used to specify how long an annotation is retained.  The possible choices include the following:
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. 

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.
6. If you want to have an annotation applicable to multiple places you can use the array mechanism as following:

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. 
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. 
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
5. Run the AnnotationReader application.

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
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.
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.)
8. Observe the message that was sent in the previous step being displayed in the server's terminal window (terminal window #1).
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. 
10. Observe the message that was sent in the previous step being displayed in the server's terminal window.
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.)
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. 
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:

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
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.

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.
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
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.

4. Run the ResourceUser application in a terminal window.
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
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.
5. Run the MsgSender application in a terminal window. 
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:
Solution can be provided in the <lab_root>/tiger/solutions/concurrency/MsgSender.java.


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.



 
Company Info   |   Contact   |   Copyright 2004 Sun Microsystems