Compile and run the enclosed file as a Java application.
I have annotated the application trace output with lines marked ">> ".
The spec says that this "is an unusual action to take". If you consider
the example code wrong-headed, the threads implementation is still strange.
It unwinds the computation partway and lets the thread continue to run outside
the "catch" clause. Then it terminates without executing the "finally" clause,
which should run according to the language specification.
3 clam% java ActiviTest
Action thread ready for request.
>> Press the "compute" button.
Application issuing request.
Application request issued.
Application showing killer dialog.
Action thread got request ActiviTest@ee700210
Sleeping.
Application notified, end of ActiviTest@ee700210
Application popping down monitor window.
Action thread completed request.
Action thread ready for request.
>> Press the "compute" button again.
Application issuing request.
Application request issued.
Application showing killer dialog.
Action thread got request ActiviTest@ee7003e0
Sleeping.
>> Press the "Abort" button.
Killer dialog requesting abort of ActiviTest@ee7003e0
Stop returned to caller.
Application notified, end of ActiviTest@ee7003e0
Application popping down monitor window.
>> At this point in the output there is a delay as the sleep completes
>> its full specified time.
Application notified, end of ActiviTest@ee7003e0
>> Notice that the action thread continues running and waits
>> for the next request.
Action thread caught abort.
Action thread ready for request.
>> Press the "compute" button again.
Application issuing request.
>> The event handler acquires the lock on the action thread's queue
>> then checks to see if it is alive. I added this check because
>> the action thread was not responding.
Action thread is dead.
>> Note that the action thread never indicates that it is dying
>> even though there is a "finally" clause in its main loop and
>> the language specification states that "finally" clauses execute
>> when threads die.
Starting new action thread.
////////////////////////////////////////////////////////////////////////////
//
// %W% %G% %U%
//
// Copyright %G% Sun Microsystems, Inc. All Rights Reserved
//
////////////////////////////////////////////////////////////////////////////
// package COM.sun.share.ejava.test;
import java.util.*;
import java.awt.*;
import java.io.*;
/** A sample activity that takes some time.
*/
public class ActiviTest extends Activity {
public void act() {
System.err.println("Sleeping.");
try {
Thread.sleep(5000);
} catch(InterruptedException e) {
System.err.println("InterrunptedException.");
}
/*
System.err.print("Read> ");
try {
System.out.println("Read: " + new DataInputStream(System.in).readLine());
} catch(IOException e) {
System.err.println("I/O Exception.");
}
*/
}
public static void main(String[] args) {
TestFrame f = new TestFrame();
f.pack();
f.show();
}
}
////////////////////////////////////////////////////////////////////////////
class TestFrame extends Frame implements Observer {
private Activity act;
Dialog killer;
public TestFrame() {
setTitle("Java Killer App");
Panel panel = new Panel();
add("Center", panel);
panel.add(new Button("Press to compute."));
}
public boolean action(Event e, Object what) {
act = new ActiviTest();
act.setObserver(this);
killer = new Killer(this, act);
System.err.println("Application issuing request.");
act.request();
System.err.println("Application request issued.");
System.err.println("Application showing killer dialog.");
killer.show();
return true;
}
public void update(Observable obs, Object arg) {
System.err.println("Application notified, end of " + arg);
if (arg==act) {
act = null;
System.err.println("Application popping down monitor window.");
killer.hide();
}
}
}
////////////////////////////////////////////////////////////////////////////
class Killer extends Dialog {
private Activity act;
public Label message;
public Killer(Frame f, Activity a) {
super(f, "Java App Killer", false); // Should be "true".
act = a;
add("South", new Button("Abort"));
message = new Label("Press to Terminate");
add("Center", message);
pack();
}
public boolean action(Event e, Object what) {
if (!act.isActive()) {
System.err.println("Killer dialog: not active " + act);
// Hide this window right away; don't wait for
// the activity to get unstuck.
hide();
}
System.err.println("Killer dialog requesting abort of " + act);
act.abort();
return true;
}
}
////////////////////////////////////////////////////////////////////////////
/** This class provides a concept of an activity with a beginning,
a method that carries out the activity, and an end. The activity can be
aborted by request, and this class generates and handles the associated
exception. An Activity has active and inactive states. The class provides
synchronization to consistently manage the state transitions. If an Observer
is designated, this class notifies the Observer at the point where
the activity terminates normally or is aborted. Activities can be queued
for execution, and this base class provides a default background thread dedicated
to performing Activities.
*/
abstract class Activity {
private static BasicActionThread defaultThread = new BasicActionThread();
private boolean active;
private Thread thread;
private Observer observer;
public Activity() {}
/** Establishes an Observer to be notified of termination.
*/
public void setObserver(Observer o) {
observer = o;
}
/** Tells whether the Activity is still in progress, i.e. is not terminated.
*/
public boolean isActive() { return active; }
/** This method invokes the activity in the current thread, calling
begin(), then act(), then end() within a "finally" clause. This
method returns immediately in case of an abort, allowing only end()
and any onAbort code to run.
*/
public void perform() {
try {
// System.err.println("Beginning.");
begin();
// System.err.println("Doing act.");
act();
// System.err.println("Finished act.");
} finally {
// System.err.println("Doing end.");
end();
}
}
/** Override this method to do the work of the activity.
Typically, perform() gets called because the application code called
request(). In this case, expect act() to not run in the thread that
called request().
*/
protected abstract void act();
/** Any action desired in the action thread in case of an Abort.
Does nothing in the base class.
*/
protected void onAbort() {}
/** Called in the action thread to enter the active state. A subclass
can add actions to immediately precede entry to the active state.
*/
protected synchronized void begin() {
if (active) throw new RuntimeException("Activity is already active.");
thread = Thread.currentThread();
active = true;
}
/** Called exactly once as the Activity is ending, whether
normally or by being stopped (aborted). Calls any Observer's update
method, passing null as the Observable and this Activity as the
second argument. Possibly could be called twice if abort is called
in the activity thread.
*/
protected synchronized void end() {
boolean was = active;
active = false;
if (observer!=null && was) {
observer.update(null, this);
}
}
/** Call this to immediately terminate the activity. If the activity
has not yet terminated, stops it, causing end() to run in the action
thread.
*/
public synchronized void abort() {
if (active) {
thread.stop(new AbortActivity());
System.err.println("Stop returned to caller.");
if (observer!=null) observer.update(null, this);
if (thread instanceof BasicActionThread)
((BasicActionThread)thread).isStuck = true;
}
}
/** This method causes the perform() method to eventually be invoked
in an action thread. The base class provides a single action thread
and no queueing of requests. If the action thread is performing an
activity, calls to the base class request() method block until the
activity terminates.
*/
public void request() {
synchronized (defaultThread) {
if (!defaultThread.isAlive() || defaultThread.isStuck) {
if (defaultThread.isStuck) System.err.println("Action thread is stuck.");
else System.err.println("Action thread is dead.");
defaultThread = new BasicActionThread();
System.err.println("\nStarting new action thread.\n");
defaultThread.start();
}
defaultThread.putRequest(this);
}
}
static {
defaultThread.start();
}
}
////////////////////////////////////////////////////////////////////////////
/** A basic kind of thread that blocks requestors until it is ready
to begin the next Activity.
*/
class BasicActionThread extends Thread {
private Activity act;
// This should be true between the time the thread has
// been asked to abort and the time it is ready to process another request.
public boolean isStuck = false;
public void run() {
try {
while (!isStuck) {
try {
System.err.println("Action thread ready for request.");
Activity a = getRequest();
System.err.println("Action thread got request " + a);
a.perform();
System.err.println("Action thread completed request.");
// ?? isStuck = false;
} catch(AbortActivity e) {
System.err.println("Action thread caught abort.");
isStuck = false;
}
}
} finally { System.err.println("Action thread dying."); }
}
public synchronized Activity getRequest() {
while (act==null) { justWait(); }
Activity a = act;
act = null;
notify();
return a;
}
public synchronized void putRequest(Activity a) {
while (act!=null) { justWait(); }
act = a;
notify();
}
public void justWait() {
try {
wait();
} catch(InterruptedException e) {
System.err.println("Interrupted.");
}
}
}
////////////////////////////////////////////////////////////////////////////
/** Internal Exception class.
*/
class AbortActivity extends RuntimeException {}
I have annotated the application trace output with lines marked ">> ".
The spec says that this "is an unusual action to take". If you consider
the example code wrong-headed, the threads implementation is still strange.
It unwinds the computation partway and lets the thread continue to run outside
the "catch" clause. Then it terminates without executing the "finally" clause,
which should run according to the language specification.
3 clam% java ActiviTest
Action thread ready for request.
>> Press the "compute" button.
Application issuing request.
Application request issued.
Application showing killer dialog.
Action thread got request ActiviTest@ee700210
Sleeping.
Application notified, end of ActiviTest@ee700210
Application popping down monitor window.
Action thread completed request.
Action thread ready for request.
>> Press the "compute" button again.
Application issuing request.
Application request issued.
Application showing killer dialog.
Action thread got request ActiviTest@ee7003e0
Sleeping.
>> Press the "Abort" button.
Killer dialog requesting abort of ActiviTest@ee7003e0
Stop returned to caller.
Application notified, end of ActiviTest@ee7003e0
Application popping down monitor window.
>> At this point in the output there is a delay as the sleep completes
>> its full specified time.
Application notified, end of ActiviTest@ee7003e0
>> Notice that the action thread continues running and waits
>> for the next request.
Action thread caught abort.
Action thread ready for request.
>> Press the "compute" button again.
Application issuing request.
>> The event handler acquires the lock on the action thread's queue
>> then checks to see if it is alive. I added this check because
>> the action thread was not responding.
Action thread is dead.
>> Note that the action thread never indicates that it is dying
>> even though there is a "finally" clause in its main loop and
>> the language specification states that "finally" clauses execute
>> when threads die.
Starting new action thread.
////////////////////////////////////////////////////////////////////////////
//
// %W% %G% %U%
//
// Copyright %G% Sun Microsystems, Inc. All Rights Reserved
//
////////////////////////////////////////////////////////////////////////////
// package COM.sun.share.ejava.test;
import java.util.*;
import java.awt.*;
import java.io.*;
/** A sample activity that takes some time.
*/
public class ActiviTest extends Activity {
public void act() {
System.err.println("Sleeping.");
try {
Thread.sleep(5000);
} catch(InterruptedException e) {
System.err.println("InterrunptedException.");
}
/*
System.err.print("Read> ");
try {
System.out.println("Read: " + new DataInputStream(System.in).readLine());
} catch(IOException e) {
System.err.println("I/O Exception.");
}
*/
}
public static void main(String[] args) {
TestFrame f = new TestFrame();
f.pack();
f.show();
}
}
////////////////////////////////////////////////////////////////////////////
class TestFrame extends Frame implements Observer {
private Activity act;
Dialog killer;
public TestFrame() {
setTitle("Java Killer App");
Panel panel = new Panel();
add("Center", panel);
panel.add(new Button("Press to compute."));
}
public boolean action(Event e, Object what) {
act = new ActiviTest();
act.setObserver(this);
killer = new Killer(this, act);
System.err.println("Application issuing request.");
act.request();
System.err.println("Application request issued.");
System.err.println("Application showing killer dialog.");
killer.show();
return true;
}
public void update(Observable obs, Object arg) {
System.err.println("Application notified, end of " + arg);
if (arg==act) {
act = null;
System.err.println("Application popping down monitor window.");
killer.hide();
}
}
}
////////////////////////////////////////////////////////////////////////////
class Killer extends Dialog {
private Activity act;
public Label message;
public Killer(Frame f, Activity a) {
super(f, "Java App Killer", false); // Should be "true".
act = a;
add("South", new Button("Abort"));
message = new Label("Press to Terminate");
add("Center", message);
pack();
}
public boolean action(Event e, Object what) {
if (!act.isActive()) {
System.err.println("Killer dialog: not active " + act);
// Hide this window right away; don't wait for
// the activity to get unstuck.
hide();
}
System.err.println("Killer dialog requesting abort of " + act);
act.abort();
return true;
}
}
////////////////////////////////////////////////////////////////////////////
/** This class provides a concept of an activity with a beginning,
a method that carries out the activity, and an end. The activity can be
aborted by request, and this class generates and handles the associated
exception. An Activity has active and inactive states. The class provides
synchronization to consistently manage the state transitions. If an Observer
is designated, this class notifies the Observer at the point where
the activity terminates normally or is aborted. Activities can be queued
for execution, and this base class provides a default background thread dedicated
to performing Activities.
*/
abstract class Activity {
private static BasicActionThread defaultThread = new BasicActionThread();
private boolean active;
private Thread thread;
private Observer observer;
public Activity() {}
/** Establishes an Observer to be notified of termination.
*/
public void setObserver(Observer o) {
observer = o;
}
/** Tells whether the Activity is still in progress, i.e. is not terminated.
*/
public boolean isActive() { return active; }
/** This method invokes the activity in the current thread, calling
begin(), then act(), then end() within a "finally" clause. This
method returns immediately in case of an abort, allowing only end()
and any onAbort code to run.
*/
public void perform() {
try {
// System.err.println("Beginning.");
begin();
// System.err.println("Doing act.");
act();
// System.err.println("Finished act.");
} finally {
// System.err.println("Doing end.");
end();
}
}
/** Override this method to do the work of the activity.
Typically, perform() gets called because the application code called
request(). In this case, expect act() to not run in the thread that
called request().
*/
protected abstract void act();
/** Any action desired in the action thread in case of an Abort.
Does nothing in the base class.
*/
protected void onAbort() {}
/** Called in the action thread to enter the active state. A subclass
can add actions to immediately precede entry to the active state.
*/
protected synchronized void begin() {
if (active) throw new RuntimeException("Activity is already active.");
thread = Thread.currentThread();
active = true;
}
/** Called exactly once as the Activity is ending, whether
normally or by being stopped (aborted). Calls any Observer's update
method, passing null as the Observable and this Activity as the
second argument. Possibly could be called twice if abort is called
in the activity thread.
*/
protected synchronized void end() {
boolean was = active;
active = false;
if (observer!=null && was) {
observer.update(null, this);
}
}
/** Call this to immediately terminate the activity. If the activity
has not yet terminated, stops it, causing end() to run in the action
thread.
*/
public synchronized void abort() {
if (active) {
thread.stop(new AbortActivity());
System.err.println("Stop returned to caller.");
if (observer!=null) observer.update(null, this);
if (thread instanceof BasicActionThread)
((BasicActionThread)thread).isStuck = true;
}
}
/** This method causes the perform() method to eventually be invoked
in an action thread. The base class provides a single action thread
and no queueing of requests. If the action thread is performing an
activity, calls to the base class request() method block until the
activity terminates.
*/
public void request() {
synchronized (defaultThread) {
if (!defaultThread.isAlive() || defaultThread.isStuck) {
if (defaultThread.isStuck) System.err.println("Action thread is stuck.");
else System.err.println("Action thread is dead.");
defaultThread = new BasicActionThread();
System.err.println("\nStarting new action thread.\n");
defaultThread.start();
}
defaultThread.putRequest(this);
}
}
static {
defaultThread.start();
}
}
////////////////////////////////////////////////////////////////////////////
/** A basic kind of thread that blocks requestors until it is ready
to begin the next Activity.
*/
class BasicActionThread extends Thread {
private Activity act;
// This should be true between the time the thread has
// been asked to abort and the time it is ready to process another request.
public boolean isStuck = false;
public void run() {
try {
while (!isStuck) {
try {
System.err.println("Action thread ready for request.");
Activity a = getRequest();
System.err.println("Action thread got request " + a);
a.perform();
System.err.println("Action thread completed request.");
// ?? isStuck = false;
} catch(AbortActivity e) {
System.err.println("Action thread caught abort.");
isStuck = false;
}
}
} finally { System.err.println("Action thread dying."); }
}
public synchronized Activity getRequest() {
while (act==null) { justWait(); }
Activity a = act;
act = null;
notify();
return a;
}
public synchronized void putRequest(Activity a) {
while (act!=null) { justWait(); }
act = a;
notify();
}
public void justWait() {
try {
wait();
} catch(InterruptedException e) {
System.err.println("Interrupted.");
}
}
}
////////////////////////////////////////////////////////////////////////////
/** Internal Exception class.
*/
class AbortActivity extends RuntimeException {}
- relates to
-
JDK-4168013 JVM_StopThread should not set the stillborn bit for running threads
- Closed