We have explored quite a few features of Java with the first three
versions of the HelloJava
application.
But until now, our application has been rather passive; it has been
completely event-driven, waiting patiently for
events to come its way and responding to the whims of the user. Now our
application is going to take some initiative—HelloJava4
will blink![5] Here is the code for our latest version:
//file: HelloJava4.java
import
java.awt.*
;
import
java.awt.event.*
;
import
javax.swing.*
;
public
class
HelloJava4
{
public
static
void
main
(
String
[]
args
)
{
JFrame
frame
=
new
JFrame
(
"HelloJava4"
);
frame
.
add
(
new
HelloComponent4
(
"Hello, Java!"
)
);
frame
.
setDefaultCloseOperation
(
JFrame
.
EXIT_ON_CLOSE
);
frame
.
setSize
(
300
,
300
);
frame
.
setVisible
(
true
);
}
}
class
HelloComponent4
extends
JComponent
implements
MouseMotionListener
,
ActionListener
,
Runnable
{
String
theMessage
;
int
messageX
=
125
,
messageY
=
95
;
// Coordinates of the message
JButton
theButton
;
int
colorIndex
;
// Current index into someColors.
static
Color
[]
someColors
=
{
Color
.
black
,
Color
.
red
,
Color
.
green
,
Color
.
blue
,
Color
.
magenta
};
boolean
blinkState
;
public
HelloComponent4
(
String
message
)
{
theMessage
=
message
;
theButton
=
new
JButton
(
"Change Color"
);
setLayout
(
new
FlowLayout
()
);
add
(
theButton
);
theButton
.
addActionListener
(
this
);
addMouseMotionListener
(
this
);
Thread
t
=
new
Thread
(
this
);
t
.
start
();
}
public
void
paintComponent
(
Graphics
g
)
{
g
.
setColor
(
blinkState
?
getBackground
()
:
currentColor
());
g
.
drawString
(
theMessage
,
messageX
,
messageY
);
}
public
void
mouseDragged
(
MouseEvent
e
)
{
messageX
=
e
.
getX
();
messageY
=
e
.
getY
();
repaint
();
}
public
void
mouseMoved
(
MouseEvent
e
)
{
}
public
void
actionPerformed
(
ActionEvent
e
)
{
if
(
e
.
getSource
()
==
theButton
)
changeColor
();
}
synchronized
private
void
changeColor
()
{
if
(++
colorIndex
==
someColors
.
length
)
colorIndex
=
0
;
setForeground
(
currentColor
()
);
repaint
();
}
synchronized
private
Color
currentColor
()
{
return
someColors
[
colorIndex
];
}
public
void
run
()
{
try
{
while
(
true
)
{
blinkState
=
!
blinkState
;
// Toggle blinkState.
repaint
();
// Show the change.
Thread
.
sleep
(
300
);
}
}
catch
(
InterruptedException
ie
)
{
}
}
}
Compile and run this version of HelloJava
just like the others. You’ll see that
the text does, in fact, blink. Our apologies if you find this
annoying—it’s all in the name of education.
All the changes we’ve made in HelloJava4
have to do with setting up a
separate thread of execution to make the text blink. Java is a
multithreaded language, which means
there can be many paths of execution effectively running at the same
time. A thread is a separate flow of control
within a program. Conceptually, threads are similar to processes. Unlike
processes, multiple threads share the same program space, which means
that they can share variables and methods (but also have their own local
variables). Threads are also quite lightweight in comparison to
processes, so it’s conceivable for a single application to be running
many (perhaps hundreds or thousands) of threads concurrently.
Multithreading provides a way for an application to handle many different tasks at the same time. It’s easy to imagine multiple things going on at the same time in an application like a web browser. The user could be listening to an audio clip while scrolling an image; at the same time, the browser can be downloading another image. Multithreading is especially useful in GUI-based applications because it improves the interactive performance of these applications.
Unfortunately for us, programming with multiple threads can be quite a headache. The difficulty lies in making sure routines are implemented so they can be run concurrently by more than one thread at a time. If a routine changes the value of multiple state variables, for example, it may be important that those changes happen together, without overlapping changes affecting each other. Later in this section, we’ll examine briefly the issue of coordinating multiple threads’ access to shared data. In other languages, synchronization of threads can be extremely complex and error-prone. You’ll see that Java gives you powerful tools that help you deal with many of these problems. See Chapter 9 for a detailed discussion of threads.
The Java runtime system creates and manages a number of threads.
(Exactly how varies with the implementation.) We’ve already mentioned
the repaint thread, which manages repaint()
requests and event processing for
GUI components that belong to the java.awt
and javax.swing
packages. Our example applications
have done most of their work in one thread. Methods such as mouseDragged()
and actionPerformed()
are invoked by the windowing
thread and run by its thread, on its time. Similarly, our HelloComponent
constructor runs as part of the
main application thread (the main()
method). This means we are somewhat limited in the amount of processing
we do within these methods. If we were, for instance, to go into an
endless loop in our constructor, our application would never appear
because it would never finish initializing. If we want an application to
perform any extensive processing, such as animation, a lengthy
calculation, or communication, we should create separate threads for
these tasks.
As you might have guessed, threads are created and
controlled as Thread
objects. An
instance of the java.lang.Thread
class
corresponds to a single thread. It contains methods to start, control,
and interrupt the thread’s execution. Our plan here is to create a
Thread
object to handle our blinking
code. We call the Thread
’s start()
method to begin execution. Once the
thread starts, it continues to run until it completes its work, we
interrupt it, or we stop the application.
So, how do we tell the thread which method to run? Well, the
Thread
object is rather picky; it
always expects to execute a method called run()
to perform the
action of the thread. The run()
method can, however, with a little persuasion, be located in any class
we desire.
We specify the location of the run()
method in one of two ways. First, the
Thread
class itself has a method
called run()
. One way to execute some
Java code in a separate thread is to subclass Thread
and override its run()
method to do our bidding. Invoking the
start()
method of the subclass object
causes its run()
method to execute in
a separate thread.
It’s not usually desirable to create a subclass of Thread
to contain our run()
method. The Thread
class has a constructor that takes an
object as its argument. If we create a Thread
object using this constructor and call
its start()
method, the
Thread
executes the run()
method of the argument object rather
than its own. In order to accomplish this, Java needs a guarantee that
the object we are passing it does indeed contain a compatible run()
method. We already know how to make such
a guarantee: we use an interface. Java provides an interface named
Runnable
that must be implemented by
any class that wants to become a Thread
.
We’ve implemented the Runnable
interface in HelloComponent4
. To create a thread, the
HelloComponent4
object passes itself
(this
) to the Thread
constructor. This means that HelloComponent4
must implement the Runnable
interface by implementing the
run()
method. This method is called
automatically when the runtime system needs to start the thread.
We indicate that the class implements the interface in our class declaration:
public
class
HelloComponent4
extends
JComponent
implements
MouseMotionListener
,
ActionListener
,
Runnable
{...}
At compile time, the Java compiler checks to make sure we abide by
this statement. We have carried through by adding an appropriate
run()
method to HelloComponent4
. It takes no arguments and
returns no value. Our run()
method
accomplishes blinking by changing the color of our text a few times a
second. It’s a very short routine, but we’re going to delay looking at
it until we tie up some loose ends in dealing with the Thread
itself.
We want the blinking to begin when the application starts,
so we’ll start the thread in the initialization code in HelloComponent4
’s constructor. It takes only
two lines:
Thread
t
=
new
Thread
(
this
);
t
.
start
();
First, the constructor creates a new instance of Thread
, passing it the object that contains
the run()
method to the constructor.
Since HelloComponent4
itself contains
our run()
method, we pass the special
variable this
to the constructor.
this
always refers to our object.
After creating the new Thread
, we
call its start()
method to begin
execution. This, in turn, invokes HelloComponent4
’s run()
method in the new thread.
Our run()
method does
its job by setting the value of the variable blinkState
. We have added blinkState
, a Boolean variable that can have
the value true
or false
, to represent whether we are currently
blinking on or off:
boolean
blinkState
;
A setColor()
call has been
added to our paintComponent()
method
to handle blinking. When blinkState
is true
, the call to setColor()
draws the text in the background
color, making it disappear:
g
.
setColor
(
blinkState
?
getBackground
()
:
currentColor
());
Here we are being very terse, using the C language-style ternary
operator to return one of two alternative color values based on the
value of blinkState
. If blinkState
is true
, the value is the value returned by the
getBackground()
method. If it is
false
, the value is the value
returned by currentColor()
.
Finally, we come to the run()
method itself:
public
void
run
()
{
try
{
while
(
true
)
{
blinkState
=
!
blinkState
;
repaint
();
Thread
.
sleep
(
300
);
}
}
catch
(
InterruptedException
ie
)
{}
}
Basically, run()
is an infinite
while
loop, which means
the loop runs continuously until the thread is terminated by the
application exiting (not a good idea in general, but it works for this
simple example).
The body of the loop does three things on each pass:
sleep()
is a static
method of the Thread
class. The
method can be invoked from anywhere and has the effect of putting the
currently running thread to sleep for the specified number of
milliseconds. The effect here is to give us approximately three blinks
per second. The try/catch
construct,
described in the next section, traps any errors in the call to the
sleep()
method of the Thread
class and, in this case, ignores
them.
The try/catch
statement
in Java handles special conditions called exceptions. An exception is a message
that is sent, normally in response to an error, during the execution of
a statement or a method. When an exceptional condition arises, an object
is created that contains information about the particular problem or
condition. Exceptions act somewhat like events. Java stops execution at
the place where the exception occurred, and the exception object is said
to be thrown by that section of code. Like an
event, an exception must be delivered somewhere and handled. The section
of code that receives the exception object is said to catch the exception. An exception
causes the execution of the instigating section of code to stop abruptly
and transfers control to the code that receives the exception
object.
The try/catch
construct allows
you to catch exceptions for a section of code. If an exception is caused
by any statement inside a try
clause,
Java attempts to deliver the exception to the appropriate catch
clause. A catch
clause looks like a method declaration
with one argument and no return type.
try
{
...
}
catch
(
SomeExceptionType
e
)
{
...
}
If Java finds a catch
clause
with an argument type that matches the type of the exception, that
catch
clause is invoked. A try
clause can have multiple catch
clauses with different argument types;
Java chooses the appropriate one in a way that is analogous to the
selection of overloaded methods. You can catch multiple types of
exceptions from a block of code. Depending on the type of exception
thrown, the appropriate catch
clause
is executed.
If there is no try/catch
clause
surrounding the code, or a matching catch
clause is not found, the exception is
thrown up to the calling method. If the exception is not caught there,
it’s thrown up to another level, and so on until the exception is
handled or the Java VM prints an error and exits. This provides a very
flexible error-handling mechanism so that exceptions in deeply nested
calls can bubble up to the surface of the call stack for handling. As a
programmer, you need to know what exceptions a particular statement can
generate. For this reason, methods in Java are required to declare the
exceptions they can throw. If a method doesn’t handle an exception
itself, it must specify that it can throw that exception so that its
calling method knows that it may have to handle it. See Chapter 4 for a complete discussion of exceptions
and the try/catch
clause.
Why do we need a try/catch
clause in the run()
method? What kind
of exception can Thread
’s sleep()
method throw, and why do we care about
it when we don’t seem to check for exceptions anywhere else? Under some
circumstances, Thread
’s sleep()
method can throw an InterruptedException
, indicating that it was
interrupted by another thread. Since the run()
method specified in the Runnable
interface doesn’t declare that it can
throw an InterruptedException
, we
must catch it ourselves, or else the compiler will complain. The
try/catch
statement in our example
has an empty catch
clause, which
means that it handles the exception by ignoring it. In this case, our
thread’s functionality is so simple that it doesn’t matter if it’s
interrupted (and it won’t be anyway). All the other methods we have used
either handle their own exceptions or throw only general-purpose
exceptions called RuntimeException
s
that are assumed to be possible everywhere and don’t need to be
explicitly declared.
At any given time, we can have lots of threads running in an application. Unless we explicitly coordinate them, these threads will be executing methods without any regard for what the other threads are doing. Problems can arise when these methods share the same data. If one method is changing the value of some variables at the same time another method is reading these variables, it’s possible that the reading thread might catch things in the middle and get some variables with old values and some with new. Depending on the application, this situation could cause a critical error.
In our HelloJava
examples, both
our paintComponent()
and mouseDragged()
methods access the messageX
and messageY
variables. Without knowing more about
the implementation of the Java environment, we have to assume that these
methods could conceivably be called by different threads and run
concurrently. paintComponent()
could
be called while mouseDragged()
is in
the midst of updating messageX
and
messageY
. At that point, the data is
in an inconsistent state and if paintComponent()
gets lucky, it could get the
new x
value with the old y
value. Fortunately, Swing does not allow
this to happen in this case because all event activity is handled by a
single thread, and we probably would not even notice if it were to
happen in this application anyway. We did, however, see another case in
our changeColor()
and currentColor()
methods that is representative
of the potential for a more serious “out of bounds” error.
The synchronized
modifier tells
Java to acquire a lock for the object that contains the
method before executing that method. Only one method in the object can
have the lock at any given time, which means that only one synchronized
method in that object can be running at a time. This allows a method to
alter data and leave it in a consistent state before a concurrently
running method is allowed to access it. When the method is done, it
releases the lock on the class.
Unlike synchronization in other languages, the synchronized
keyword in Java provides locking
at the language level. This means there is no way that you can forget to
unlock a class. Even if the method throws an exception or the thread is
terminated, Java will release the lock. This feature makes programming
with threads in Java much easier than in other languages. See Chapter 9 for more details on coordinating threads
and shared data.
Whew! Well, it’s time to say goodbye to HelloJava
. We hope that you have developed a
feel for the major features of the Java language and that this will help
you as you explore the details of programming with Java. If you are a
bit bewildered by some of the material presented here, take heart. We’ll
be covering all the major topics presented here again in their own
chapters throughout the book. This tutorial was meant to be something of
a “trial by fire” to get the important concepts and terminology into
your brain so that the next time you hear them you’ll have a head
start.
[5] The title of this section, “Netscape’s Revenge,” refers to the
infamous <BLINK>
HTML
tag introduced with an early version of the Netscape web
browser.
Get Learning Java, 4th Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.