As a hobbyist programmer, I gain knowledge where I can find it from google searches. I tend to write code that utilizes threads that have infinite loops using while loops, where the while condition slaves onto a BooleanProperty
that when set false, causes the thread to interrupt the loop. I use a single class to keep track of the threads that are still looping and when they have all stopped, it can then exit the program.
A specific example of how I use a thread like this is an app I wrote that keeps my One Time Passwords for two-factor authentication on various web sites. Those passwords change every 30 seconds at second 0 of a minute change and at second 30 within a minute. Within each 30-second time frame, I do things to the label that is showing the password for an account such as fade the color to red during the last 10 seconds, then fade opacity in the last second and the first second, and there is simply no possible way to make those changes event-driven, so I use a looping thread to determine when those events need to happen.
However, it was brought to my attention that the way I am doing this might not be the best way, so I would like to share how I am doing this and see if anyone can suggest a better method.
I took a few Java classes when I got my CIS degree, but nothing covering advanced topics in Java. So I admit my knowledge is far from where I want it to be where wielding the Java language is concerned. I'd consider myself a hack programmer, but I would like my knowledge to be more complete than hack-level, but I don't know what I don't know so It's difficult to determine what to study given what I do know... you know?
Anyway, the way I have been typically handling this issue, is that I first start with a ThreadRegister
class that is publicly static and it looks like this:
package ClassHelpers;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class ThreadRegister {
public static BooleanProperty keepRunning = new SimpleBooleanProperty(true);
private static List<Long> threadIDList = new ArrayList<>();
private static long threadID = 0;
public static long addThread() {
threadID++;
threadIDList.add(threadID);
return threadID;
}
public static void removeThread(Long threadID) {
threadIDList.remove(threadID);
}
public static void exitApp() {
new Thread(() -> {
keepRunning.setValue(false);
while (threadIDList.size() > 0) {
try {TimeUnit.MILLISECONDS.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}
}
System.exit(0);
}).start();
}
}
The publicly static BooleanProperty
is then bound to the same object type with the same name in any other class that has looping threads, and such a class would look like this:
package Tests;
import ClassHelpers.ThreadRegister;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import java.util.concurrent.TimeUnit;
public class MyClass {
public MyClass() {
this.keepRunning.bind(ThreadRegister.keepRunning);
}
private BooleanProperty keepRunning = new SimpleBooleanProperty();
private void myThread() {
new Thread(()->{
long threadID = ThreadRegister.addThread();
while (keepRunning.getValue().equals(true)) {
//Do necessary processing here then
//Sleep for a short time to keep CPU consumption low
try {TimeUnit.MILLISECONDS.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}
}
ThreadRegister.removeThread(threadID);
}).start();
}
}
I always make sure that any loops within a threads' main while
loop also depend on that BooleanProperty
with sleep values in every loop sitting right around 50 milliseconds to make sure that when the app is closed, the threads stop quickly. And last, I set the JavaFX Stage onCloseRequest
value to the ThreadRegisters
exitApp()
method like this:
this.stage.setOnCloseRequest(e->ThreadRegister.exitApp());
Now, no matter how many of these threads I have running, when the stage is close requested, the ThreadRegister
sets the BooleanProperty
to false, then it waits for all of the threads to stop running before finally exiting the app.
3 Answers 3
Hello and welcome to code review! My main question is: How often do you write programs that have several threads running throughout the application lifecycle with identical exit condition? It is possible that this might negatively affect the way you design your applications and make you use practises that aren't quite optimal? For example, instead of starting and stopping a thread when needed, do you now have threads running forever and waiting for a specific start signal?
Java already has quite powerful tools for managing concurrency, such as executors and parallel streams. I think you might benefit from studying those before rolling your own.
Code
The ThreadRegister
has one glaring bug: even though it deals with threads and concurrency, it is not thread safe itself. Access to threadIDList
and threadID
should be synchronized.
You should follow Java naming conventions. Mainly, packages should be in lower case.
By using property-classes from the javafx package, you have created a dependency from a fairly generic looking utility to a full blown desktop application framework. This limits the usability of your utility a lot.
-
\$\begingroup\$ I was not aware that packages should be lower case, I always treated them like class names... what do yo mean by the threadIDList and threadID should be synchronized? I like the way properties from JavaFX work because you can easily communicate between class instances without having to pass the class itself onto another class ... and as far as the looping threads go, Often, they check for some event that has happened ... but sometimes they just continually "do stuff" like the thread I mentioned in my second paragraph... \$\endgroup\$Michael Sims– Michael Sims2020年07月06日 07:06:53 +00:00Commented Jul 6, 2020 at 7:06
As for a real review, see Torben's answer.
Apart from that: have a look at the method Thread.setDaemon()
. Setting your worker threads to daemon would probably solve your problem without any further code. (See this question in SO https://stackoverflow.com/questions/2213340/what-is-a-daemon-thread-in-java)
If you want to perform additional parallel tasks in JavaFX specifically, have a look at Task
s and Worker
s. For things like transitioning a color over time, have a look at JavaFX's "animations" and "timeline animations".
-
\$\begingroup\$ I've worked with animations - specifically to make a slide-out menu drawer, but I've not seen how one can fade color using animations... but in this particular case, there is still the problem of determining when to start the fade and a thread is the only way I can think of to calculate when the number of seconds within a minute hits the 20 second mark and then again when it hits the 50 second mark as that would be when the color fade needs to start and it fades over 10 seconds each time it runs. \$\endgroup\$Michael Sims– Michael Sims2020年07月06日 07:10:19 +00:00Commented Jul 6, 2020 at 7:10
-
\$\begingroup\$ Concerning the setDaemon method... after reading what you posted, would it be reasonable to say that if my thread is not performing any I/O operations, then I can just set it up as Daemon and not even bother with making sure it's shut down before a System.exit() is called? \$\endgroup\$Michael Sims– Michael Sims2020年07月06日 09:00:25 +00:00Commented Jul 6, 2020 at 9:00
-
\$\begingroup\$ Probably yes. But as usual, trying it out is better than discussing the theory. \$\endgroup\$mtj– mtj2020年07月06日 10:01:46 +00:00Commented Jul 6, 2020 at 10:01
-
\$\begingroup\$ @MichaelSims, if you are using
JavaFx
and need to do any animations, you should use something from theAnimation
API. It will probably make what you are trying to do very easy. Also, @mtj is correct. UsingThread.setDeamon(true)
should solve your problem. However, this problem should go away if you use something from theAnimation
API. \$\endgroup\$SedJ601– SedJ6012022年09月01日 16:37:28 +00:00Commented Sep 1, 2022 at 16:37 -
\$\begingroup\$ @SedJ601 - This is of course two years later. These days, I primarily just use simple threads or I'll utilize TimerTask with Timers which has proven to be a great way to implement threads that need to repeat on a schedule. I don't ever do animations in Java ... or anywhere for that matter. The other advantage of TimerTasks is that when you kill the app, the timer itself just stops doing what it's doing and no need to worry about threads that aren't done - when I write them, or course, so that they don't infinitely loop - which is substituted with the Timer object and is easily terminated. \$\endgroup\$Michael Sims– Michael Sims2022年09月25日 08:37:48 +00:00Commented Sep 25, 2022 at 8:37
@TorbenPutkonen's answer includes all the important facts about your code, I want just to add some minor thoughts. When you works with java threads and you have some shared variables check if there is a thread safe alternative already implemented in the java library. So instead of long
and boolean
use the thread safe alternatives AtomicLong
and AtomicBoolean
, same approach for the list that must be synchronized.
-
\$\begingroup\$ I've seen atomic variables before in example code, but I've never looked into what they are or why they exist ... I will read up on them, thank you. \$\endgroup\$Michael Sims– Michael Sims2020年07月06日 07:11:34 +00:00Commented Jul 6, 2020 at 7:11
-
\$\begingroup\$ @MichaelSims You are welcome. \$\endgroup\$dariosicily– dariosicily2020年07月06日 07:18:29 +00:00Commented Jul 6, 2020 at 7:18
JavaFX
? \$\endgroup\$Properties
andStage
. I guess a lot of theJavaFX
code is missing. Anyway, if you are writing this inJavaFX
, useTask
orService
over pureThreads
. \$\endgroup\$