I am just wondering how to control console inputs in separate threads?
I have thread A and thread B and thread C; B and C they both control user input... the thing is I am not pretty sure how to switch between B and C threads the scanIn.nextLine();
because B seems to loop two unnecessary iterations before thread C can interrupt B :(
Main thread:
public class Main
{
private volatile ThreadGroup threadGroup=new ThreadGroup();//contains concurrent hash map...
private volatile TaskManager taskManager=new TaskManager(threadGroup);
private A a=new A(threadGroup);
private B b=new B(threadGroup,taskManager);
private C c=new C(threadGroup);
Main()
{
b.start();
threadGroup.add(a,"A");
threadGroup.add(b,"B");
threadGroup.add(c,"C");
}
public static void main(String args[]){new Main();}
}
TaskManager method snippet:
...
public synchronized void threadCMaybeCanBeStartedLater()
{
this.getThreadGroup().get("A").start();
}
...
thread A code like a (overridden run method invokes):
public void loopIt()
{
Random generator = new Random();
A: while(!this.interrupted())
{
Thread.sleep(1000);
int i=generator.nextInt(100)+1;
int j=generator.nextInt(100)+1;
if(i==j){this.invokeC(); System.out.println("event : i==j");}
}
}
private void invokeC()
{
if(!this.getThreadGroup().get("C").isAlive())this.getThreadGroup().get("C").start();
}
thread B code like a:
public void loopIt() throws InterruptedException
{
Scanner scanIn = new Scanner(System.in);
B: while(!this.isInterrupted())
{
Thread.sleep(1000);
String command= scanIn.nextLine();
...
if(command.equals("a"))
{
System.out.println("a was entered");
this.getTaskManager().threadCMaybeCanBeStartedLater();//
continue;
}
if(command.equals("b"))
{
System.out.println("b was entered");
continue;
}
if(command.equals("c"))
{
System.out.println("c was entered");
continue;
}
else{System.out.println("no such command");}
}
}
thread C (the run method invokes)
public void loopIt() throws InterruptedException
{
getThreadGroup().get("B").interrupt();
Scanner scanIn = new Scanner(System.in);
C: while(!this.isInterrupted())
{
Thread.sleep(1000);
String command= scanIn.nextLine();
...
if(command.equals("d"))
{
System.out.println("d was entered");
continue;
}
if(command.equals("e"))
{
System.out.println("e was entered");
this.interrupt();
break C;
}
if(command.equals("f"))
{
System.out.println("f was entered");
continue;
}
else{System.out.println("no such command");}
}
getThreadGroup().get("B").start();
}
...as you can see, the major code conception (see A thread snippet) is "you don't know when thread C can be started but when it started you need to give it console"; that's all; if it was GUI there was no problem but console-like app makes it quite problematic...
So the question is ... how to interrupt/re-start thread B immediately from thread C in this case?
Thanks
Synchronising Threads Using Thread Class
Thread.interrupt() on its own does not synchronise logic & timing between two threads.
Thread.interrupt() signals that the caller would like the thread to interrupt at a time in the near future. The interrupt() method sets an interrupt flag. The isInterrupted() method checks whether this flag is set (& also clears the flag again). The methods Thread.sleep(), Thread.join(), Object.wait() and a number of I/O methods also check & clear this flag, when throwing InterruptedException.
The thread doesn't immediately pause but continues running code. The internal thread logic is designed & implemented by the developer: continue to run thread code considered atomic/urgent until it gets to an "interruptable point", then check the interrupted flag / catch InterruptedException & then do a clean pause - usually via Thread.sleep(), Thread.join() or Object.wait(), and sometimes by exiting Thread.run() altogether thus stopping the thread permanently.
While all of this is happening the calling thread is still running and will execute an indeterminate amount of code before the interrupt takes effect... hence the lack of synchronisation. There is a lack of guaranteed happens-before condition between the code in one thread and code in the other thread.
Some approaches that do synchronise logic & timing between two threads (creating a happens-before condition):
thread1 calls Thread2.join()
thread1 calls SomeObject.wait() and thread2 calls SomeObject.notify()
Synchronise on a method or block
Quick Review of Your Code:
- Thread B runs in an infinite loop - there is no call to interrupt it from any thread and no call for it's thread to wait(). It will, however, temporily block until System.in has more input, and then continue.
- Thread A only interrupts itself - cleaner and easier to analyse logic if you don't call
this.interrupt()
and while(!this.isInterrupted())
: just change the while loop into: do { .... } while (i != j)
- Thread A only interrupts itself - cleaner and much easier to analyse logic if you don't call
this.interrupt()
and while(!this.isInterrupted())
: just change the while loop into: do { .... } while (!"e".equals(command))
Thread C must make the following calls at the top of it's while loop:
threadB.interrupt();
synchronized(this) {
try {
this.wait();
} catch (InterruptedException ie) {
}
Thread B must make the following call as the last line of code:
synchronized(threadC) {
threadC.notify();
}
Reading from I/O (nextLine()
) is a blocking & interruptable operation. Right next to it you introduce Thread.sleep()
which is also a blocking & interruptable operation that introduces an artificial delay in your code - it is not necessary; remove.
- The only Scanner method you call is
nextLine()
. You're using it as if it were an InputStreamReader & not doing any scanning. Also, you're not buffering input. If code stays like this, replace 'Scanner scanIn = Scanner(System.in)
' with: 'BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))
'.
- The only
ThreadGroup
method you call are add()
and get()
. You're using it as if it were a HashMap
& not doing any thread group management. If code stays like this, you may replace 'ThreadGroup
' with 'HashMap
'. However, even the HashMap seems excessive - could simply pass Threads references to other Threads using constructors/setters and avoid HashMap altogether.
- Avoid excessive use of
continue
inside loops - try to avoid altogether. Best to do this by chaining successive 'if
' statements together using '} else if {
'...
- Potential race condition between main thread and thread B. When thread B is started (from
Main()
) it may execute many lines of code before the main thread executes any more code - B may call ThreadGroup.get() before main thread has called ThreadGroup.add() x 3. Solution: in Main(), put b.start() after ThreadGroup.add() x 3
- In general,
"a".equals(command)
is better practice than command.equals("a")
- it handles nulls, giving correct result without NPE (you seem lucky here - probably won't have nulls).
Suggested Changes:
public class ThreadA extends Thread {
ThreadC threadC;
public void setThreadC(ThreadC threadC) {
this.threadC = threadC;
}
@Override
public void run() {
this.loopIt();
}
public void loopIt() {
Random generator = new Random();
int i, j;
do {
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {
}
i=generator.nextInt(100)+1;
j=generator.nextInt(100)+1;
} while (i != j);
threadC.start();
}
}
public class ThreadB extends Thread {
ThreadA threadA;
ThreadC threadC;
public void setThreadA(ThreadA threadA) {
this.threadA = threadA;
}
public void setThreadC(ThreadC threadC) {
this.threadC = threadC;
}
@Override
public void run() {
this.loopIt();
}
public void loopIt() {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String command = null;
// loop until interrupted
try {
while (!this.isInterrupted()) {
command = reader.readLine();
if ("a".equals(command)) {
System.out.println("a was entered");
if (threadA.getState() == Thread.State.NEW) {
threadA.start();
}
} else if ("b".equals(command)) {
System.out.println("b was entered");
} else if ("c".equals(command)) {
System.out.println("c was entered");
} else if ("z".equals(command)) {
System.out.println("z was entered");
throw new InterruptedException("Command z interruption");
} else {
System.out.println("no such command");
}
}
} catch (IOException ioe) {
ioe.printStackTrace();
} catch (InterruptedException ie) {
}
// Now notify ThreadC - it will wait() until this code is run
synchronized(threadC) {
threadC.notify();
}
}
}
public class ThreadC extends Thread {
ThreadB threadB;
public void setThreadB(ThreadB threadB) {
this.threadB = threadB;
}
@Override
public void run() {
this.loopIt();
}
public void loopIt() {
// Block until the lock can be obtained
// We want thread B to run first, so the lock should be passed into Thread C constructor in an already locked state
threadB.interrupt();
synchronized(this) {
try {
// Put this thread to sleep until threadB calls threadC.notify().
//
// Note: could replace this line with threadB.join() - and remove
// from threadB the call to threadC.notify()
this.wait();
} catch (InterruptedException ie) {
}
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String command = null;
while (!"e".equals(command)) {
try {
command= reader.readLine();
if ("d".equals(command)) {
System.out.println("d was entered");
} else if ("e".equals(command)) {
System.out.println("e was entered");
} else if ("f".equals(command)) {
System.out.println("f was entered");
} else if ("z".equals("command")) {
System.out.println("z was entered");
} else {
System.out.println("no such command");
};
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
}
}
nextLine()
does not respond to interruption. You want to do something like
String command;
if (scanIn.hasNextLine())
command = scanIn.nextLine();
else
Thread.sleep(1000);
You can use flag variables (as global variables) to control the while loop
in each thread...
suppose that Thread A has an infinite loop like this
while(true)
while(x == 1){
your code ...
}
Thread.sleep(2000);
}
when Thread b is started you can change x to 0 (suppose x is a global variable) then when Thread b finishes executing change x to 1 at the end of Thread b code...
or you can interrupt the thread from thread itself based of flag value x