threads - Multithreading
Stability: 1 - Experiment
The threads module provides multithreading support and can start new threads to run code.
The script main thread waits for all child threads to finish before stopping. If a child thread contains an infinite loop, call exit() to stop the script, or threads.shutDownAll() to stop all child threads when appropriate.
All threads started by threads.start() are automatically stopped when the script is force-stopped.
Because JavaScript itself does not have native multithreading, you may encounter unexpected issues.
threads.start(action)
action{Function} Function to run in the new thread- Returns {Thread}
Start a new thread and execute action.
Example:
threads.start(function () {
// Code running in the new thread
while (true) {
log("Child thread");
}
});
while (true) {
log("Main thread");
}The returned Thread object can be used to check status and control execution. Example:
var thread = threads.start(function () {
while (true) {
log("Child thread");
}
});
// Stop the thread
thread.interrupt();See Thread for more.
threads.shutDownAll()
Stop all child threads started via threads.start().
threads.currentThread()
- Returns {Thread}
Return the current thread.
threads.disposable()
- Returns {Disposable}
Create a Disposable object used to wait for a one-time result from another thread. See Thread communication.
threads.atomic([initialValue])
initialValue{number} Initial integer value. Default 0- Returns {AtomicLong}
Create an integer atomic variable. See Thread safety and AtomicLong.
threads.lock()
- Returns {ReentrantLock}
Create a reentrant lock. See Thread safety and ReentrantLock.
threads.allThreads()
- Returns {Array<Thread>}
Return the list of threads managed by the threads module for the current script.
var list = threads.allThreads();
log("Thread count: " + list.length);threads.hasRunningThreads()
- Returns {boolean}
Return whether there are still threads running.
if (threads.hasRunningThreads()) {
log("Threads are still running");
}threads.getMainThread()
- Returns {Thread}
Return the main thread object of the current script.
var mainThread = threads.getMainThread();
log("Main thread: " + mainThread.getName());threads.exit()
- Returns {void}
Stop execution of the current thread. Typically used in a child thread to exit it proactively.
threads.start(function () {
log("Child thread started");
threads.exit();
log("This line usually won't run");
});Thread
Thread object returned by threads.start(). Used to query/control thread state and interact with other threads.
Thread provides APIs similar to the timers module (such as setTimeout() and setInterval()), allowing you to run timed callbacks on that thread, so threads can interact directly. Example:
var thread = threads.start(function () {
// Timer running on the child thread
setInterval(function () {
log("Child thread: " + threads.currentThread());
}, 1000);
});
log("Current thread is main thread: " + threads.currentThread());
// Wait for child thread to start
thread.waitFor();
// Timer executed on the child thread
thread.setTimeout(function () {
// This runs on the child thread
log("Current thread is child thread: " + threads.currentThread());
}, 2000);
sleep(30 * 1000);
thread.interrupt();Thread.interrupt()
Interrupt the thread.
Thread.join([timeout])
timeout{number} Wait time in milliseconds
Wait for the thread to finish. If timeout is 0, wait indefinitely; otherwise wait up to timeout milliseconds.
Example:
var sum = 0;
// Start a child thread to sum 1..10000
var thread = threads.start(function () {
for (var i = 0; i < 10000; i++) {
sum += i;
}
});
// Wait for it to finish
thread.join();
toast("sum = " + sum);isAlive()
- Returns {boolean}
Return whether the thread is alive. If the thread has not started or has already finished, returns false; if it has started or is running, returns true.
waitFor()
Wait for the thread to start execution. After calling threads.start(), the thread may take some time to start; calling this waits until it starts. If it is already running, it returns immediately.
var thread = threads.start(function () {
//do something
});
thread.waitFor();
thread.setTimeout(function () {
//do something
}, 1000);Thread.setTimeout(callback, delay[, ...args])
See timers.setTimeout().
The difference is that the timer callback runs on this thread. If the thread has not started yet or has already finished, it throws IllegalStateException.
log("Current thread (main): " + threads.currentThread());
log("Current thread (main): " + threads.currentThread());
var thread = threads.start(function(){
// Keep the thread alive with an empty timer
setInterval(function()\{\}, 1000);
});
sleep(1000);
thread.setTimeout(function(){
log("Current thread (child): " + threads.currentThread());
exit();
}, 1000);Thread.setInterval(callback, delay[, ...args])
See timers.setInterval().
The difference is that the timer callback runs on this thread. If the thread has not started yet or has already finished, it throws IllegalStateException.
Thread.setImmediate(callback[, ...args])
The difference is that the callback runs on this thread. If the thread has not started yet or has already finished, it throws IllegalStateException.
Thread.clearInterval(id)
The difference is that it clears a timer running on this thread. If the thread has not started yet or has already finished, it throws IllegalStateException.
Thread.clearTimeout(id)
The difference is that it clears a timer running on this thread. If the thread has not started yet or has already finished, it throws IllegalStateException.
Thread.clearImmediate(id)
The difference is that it clears an immediate callback on this thread. If the thread has not started yet or has already finished, it throws IllegalStateException.
activeCount()
- Returns {int}
Return an estimate of the number of active threads in the current thread group and its subgroups.
log("Active thread count: " + thread.activeCount());getStackTrace()
- Returns {java.lang.StackTraceElement[]}
Get the current stack trace of the thread. Useful for locating execution path and blocking points.
var arr = thread.getStackTrace();
for (var i = 0; i < arr.length; i++) {
log(String(arr[i]));
}getId()
- Returns {long}
Return the unique thread id.
log("Thread ID: " + thread.getId());getName()
- Returns {string}
Return thread name.
log("Thread name: " + thread.getName());getState()
- Returns {java.lang.Thread$State}
Return current thread state, e.g. RUNNABLE, TIMED_WAITING.
log("Thread state: " + thread.getState());getPriority()
- Returns {int}
Return thread priority, typically in the range 1 to 10.
log("Thread priority: " + thread.getPriority());setPriority(priority)
priority{number} Priority, typically1to10- Returns {void}
Set thread priority. Higher priority usually gets more scheduling opportunities.
thread.setPriority(7);
log("Priority after set: " + thread.getPriority());setName(name)
name{string} Thread name- Returns {void}
Set thread name, useful for locating it in logs.
thread.setName("worker-download");
log("New thread name: " + thread.getName());isDaemon()
- Returns {boolean}
Return whether the thread is a daemon thread.
log("Is daemon: " + thread.isDaemon());setDaemon(on)
on{boolean} Whether to set as daemon- Returns {void}
Set whether the thread is a daemon. Usually should be set before starting the thread, otherwise it may throw.
var t1 = threads.start(function () {
// do something
});
log("Is daemon (t1): " + t1.isDaemon());
var t2 = new java.lang.Thread(
new java.lang.Runnable({
run: function () {
for (var i = 0; i < 5; i++) {
log("Child thread " + i);
sleep(1000);
}
},
}),
);
t2.setName("worker-download");
t2.setDaemon(true); // Set before start
t2.start(); // Start last
log("Is daemon (t2): " + t2.isDaemon());getThreadGroup()
- Returns {java.lang.ThreadGroup}
Return the thread group object that the thread belongs to. Useful for debugging thread organization.
log("Thread group: " + thread.getThreadGroup());isInterrupted()
- Returns {boolean}
Return whether the thread has been interrupted.
log("Is interrupted: " + thread.isInterrupted());sleep(millis[, nanos])
millis{number} Milliseconds to sleepnanos{number} Optional additional nanoseconds- Returns {void}
Sleep the current thread for a period of time. Commonly used for throttling and waiting inside threads.
sleep(500);yield()
- Returns {void}
Hint the scheduler to yield execution so other threads of the same priority can run.
thread.yield();Thread safety
Thread safety is a relatively advanced topic. This section is only for users who need it.
From Wikipedia:
Thread safety is a programming concept describing the ability of code to function correctly when accessed by multiple threads concurrently, especially when dealing with shared state.
In Auto.js, variables can be shared across threads as long as JavaScript scoping rules allow it. For example, global variables can be accessed by all threads, and their updates are visible across threads. However, atomicity is not guaranteed. A classic example is i++, which is not atomic.
Rhino and Auto.js provide some basic tools for common thread-safety needs, such as locks threads.lock(), function synchronization sync(), and atomic integer variables threads.atomic().
For example, incrementing a shared integer can be problematic because i++ is effectively i = i + 1 (read i, add 1, write i). If two threads increment at the same time, the value may only increase by 1. You should use threads.atomic() to create an atomic integer, or use a lock threads.lock() to enforce atomicity, or wrap access with sync().
Thread-unsafe code:
var i = 0;
threads.start(function () {
while (true) {
log(i++);
}
});
while (true) {
log(i++);
}After running this code, you may see duplicate values in the logs.
Thread-safe version using threads.atomic():
// The AtomicLong ensures the increment is atomic
var i = threads.atomic();
threads.start(function () {
while (true) {
log(i.getAndIncrement());
}
});
while (true) {
log(i.getAndIncrement());
}Or:
// The lock ensures the operation is atomic
var lock = threads.lock();
var i = 0;
threads.start(function () {
while (true) {
lock.lock();
log(i++);
lock.unlock();
}
});
while (true) {
lock.lock();
log(i++);
lock.unlock();
}Or:
// sync() wraps the function with a synchronization lock so only one thread can run it at a time
var i = 0;
var getAndIncrement = sync(function () {
return i++;
});
threads.start(function () {
while (true) {
log(getAndIncrement());
}
});
while (true) {
log(getAndIncrement());
}Also, JavaScript Array is not thread-safe. If you need safe concurrent collections, use Android/Java APIs. For example, CopyOnWriteList and Vector are thread-safe alternatives for different scenarios. Example:
var nums = new java.util.Vector();
nums.add(123);
nums.add(456);
toast("Size: " + nums.size());
toast("First element: " + nums.get(0));Obviously, these classes are not as convenient as arrays and you can't use helpers like slice(). In the future a thread-safe array might be provided. You can also lock around each array operation:
var nums = [];
var numsLock = threads.lock();
threads.start(function () {
// Push 123
numsLock.lock();
nums.push(123);
log("Thread: %s, Array: %s", threads.currentThread(), nums);
numsLock.unlock();
});
threads.start(function () {
// Push 456
numsLock.lock();
nums.push(456);
log("Thread: %s, Array: %s", threads.currentThread(), nums);
numsLock.unlock();
});
// Pop last element
numsLock.lock();
nums.pop();
log("Thread: %s, Array: %s", threads.currentThread(), nums);
numsLock.unlock();sync(func)
func{Function} Function- Returns {Function}
Wrap func with a synchronization lock and return the new function.
var i = 0;
function add(x) {
i += x;
}
var syncAdd = sync(add);
syncAdd(10);
toast(i);Thread communication
Auto.js provides some basic facilities for simple thread communication. threads.disposable() lets one thread wait for a one-time result from another thread. Lock.newCondition() provides a Condition object for general thread coordination (await, signal). In addition, the events module can also be used for thread communication by controlling which thread executes EventEmitter callbacks.
With threads.disposable(), you can easily wait for and get a thread's result. For example, wait for a thread to compute 1 + ... + 10000:
var sum = threads.disposable();
// Start a child thread to compute
threads.start(function () {
var s = 0;
// Sum from 1 to 10000
for (var i = 1; i <= 10000; i++) {
s += i;
}
// Notify main thread
sum.setAndNotify(s);
});
// blockedGet() waits for the result
toast("sum = " + sum.blockedGet());Same example implemented with Condition:
// Create a lock
var lock = threads.lock();
// Create a condition: "complete"
var complete = lock.newCondition();
var sum = 0;
threads.start(function () {
// Sum from 1 to 10000
for (var i = 1; i <= 10000; i++) {
sum += i;
}
// Notify main thread
lock.lock();
complete.signal();
lock.unlock();
});
// Wait for completion
lock.lock();
complete.await();
lock.unlock();
// Print result
toast("sum = " + sum);Same example implemented with the events module:
// Create an emitter and make callbacks run on current thread
var sum = events.emitter(threads.currentThread());
threads.start(function () {
var s = 0;
// Sum from 1 to 10000
for (var i = 1; i <= 10000; i++) {
s += i;
}
// Emit result
sum.emit("result", s);
});
sum.on("result", function (s) {
toastLog("sum = " + s + ", current thread: " + threads.currentThread());
});For other threading problems such as producer-consumer, use Java approaches, e.g. java.util.concurrent.BlockingQueue.
Disposable
Disposable lets one thread wait for a one-time result from another thread. Create it via threads.disposable().
Disposable.setAndNotify(value)
value{any} Value to pass
Set the value and notify waiting threads. After calling this, all waiting threads (via blockedGet()) return immediately with the value.
This method can only be called once. Subsequent calls are ignored.
var result = threads.disposable();
threads.start(function () {
// Do some computation
var data = "result data";
// Set result and notify waiting thread
result.setAndNotify(data);
});
// Wait for result
var value = result.blockedGet();
log("Got result: " + value);Disposable.blockedGet()
- Returns {any} Disposable value
Block the current thread until setAndNotify() is called and a value is set. If the value is already set, returns immediately.
This blocks until a value is set. If setAndNotify() is never called, the thread waits forever.
var result = threads.disposable();
threads.start(function () {
sleep(2000); // Simulate long work
result.setAndNotify("Done");
});
// Main thread waits for result
log("Waiting for result...");
var value = result.blockedGet();
log("Got result: " + value);Note: If the value is never set (i.e. setAndNotify() is never called), blockedGet() will block forever. Make sure you call setAndNotify() when appropriate.
