Parallel programming is an important part of Java, this article through the RunOrder example combined with the source code to introduce the Thread class and partial lock related knowledge.
RunOrder and RunOrder2
RunOrder
RunOrder simulates the process in which multiple customers place orders and the warehouse management system updates the inventory. In code implementation, it is shown that multiple threads each cycle several times to decrement the count variable. RunOrder does not use any synchronization and results in inconsistent results, while RunOrder2 uses an AtomicInteger count variable and its corresponding decrement method to synchronize threads. AtomicInteger implements locking based on CAS(Compare And Swap).
public class RunOrder {
private static final int cCount = 6000000;
public int count = cCount;
private void order(a) {
count--;
}
public static void main(String[] args) throws IOException, InterruptedException {}public void orderWithMultithread(a) {
ThreadGroup tg = Thread.currentThread().getThreadGroup();
for (int i=0; i<6; i++) {new Thread(tg,
()-> {
for (int j=0; j<cCount/6; j++) {this.order(); } } } ).start(); }}}Copy the code
Threadgroups in the Thread constructor
If the first ThreadGroup argument is omitted during a new Thread, the ThreadGroup is specified as NULL in the Thread constructor that takes only the Runable Target argument. In this case, the newly created Thread is assigned to the main ThreadGroup.
The main ThreadGroup is also not the root ThreadGroup. System threadgroups include main threadgroups. The main thread group can be obtained by getParent().
- Source Code
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(ThreadGroup group, Runnable target) {
init(group, target, "Thread-" + nextThreadNum(), 0);
}
Copy the code
When a thread calls exit(), the thread group it belongs to is responsible for removing it from the thread group and terminating.
private void exit(a) {
if(group ! =null) {
group.threadTerminated(this);
group = null;
}
/* Aggressively null out all reference fields: see bug 4006245 */
target = null;
/* Speed the release of some of these resources */
threadLocals = null;
inheritableThreadLocals = null;
inheritedAccessControlContext = null;
blocker = null;
uncaughtExceptionHandler = null;
}
Copy the code
void threadTerminated(Thread t) {
synchronized (this) {
remove(t);
if (nthreads == 0) {
notifyAll();
}
if (daemon && (nthreads == 0) &&
(nUnstartedThreads == 0) && (ngroups == 0)) { destroy(); }}}Copy the code
Difference between start() and run()
Calling the start() method causes the new thread to start executing the method in the Runable Target. Note: If the run() method is called here, the effect is that the main thread executes the methods in the Runable Target one by one instead of creating a new thread.
- Source Code
public synchronized void start(a) {
/** * This method is not invoked for the main method thread or "system" * group threads created/set up by the VM. Any new functionality added * to this method in the future may have to also be added to the VM. * * A zero status value corresponds to state "NEW". */
if(threadStatus ! =0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started * so that it can be added to the group's list of threads * and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if(! started) { group.threadStartFailed(this); }}catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then it will be passed up the call stack */}}}Copy the code
The start() method adds a new thread to the ThreadGroup and calls start0() to do the main job of starting the thread. So what does start0() do?
- Source Code
private native void start0(a);
/**
* If this thread was constructed using a separate
* <code>Runnable</code> run object, then that
* <code>Runnable</code> object's <code>run</code> method is called;
* otherwise, this method does nothing and returns.
* <p>
* Subclasses of <code>Thread</code> should override this method.
*
* @see #start()
* @see #stop()
* @see #Thread(ThreadGroup, Runnable, String)
*/
Copy the code
You can see that start0() is a native method implemented by other languages. Based on comments, start0() initializes and starts a new thread at the operating system (kernel) level.
- Source Code
@Override
public void run(a) {
if(target ! =null) { target.run(); }}Copy the code
The run() side is simple, calling the run() method of the Runable Target. So by calling the run() method of the new thread, no new threads are created at either the operating system or JVM level. Subsequent experiments in RunOrder2 confirmed this.
java.lang.ThreadGroup[name=main,maxpri=10]
Thread[main,5,main]
Thread[Monitor Ctrl-Break,5,main]
Thread.activeCount(): 2
RunOrder2 count:0
Copy the code
You can see that the main Thread group contains only the main Thread itself and a Monitor Thread, with no newly created threads (the default name for user-created threads is thread-n). This means that even “single-threaded” Java programs with no new threads will always execute with two threads, a main thread and a Monitor Ctrl-break thread.
RunOrder2
public class RunOrder2 {
private static final int cCount = 6000000;
private AtomicInteger count = new AtomicInteger(cCount);
private void order(a) {
count.decrementAndGet();
}
public static void main(String[] args) throws InterruptedException {
test3();
}
/ /...
}
Copy the code
AtomicInteger with CAS
Obviously, using the AtomicInteger data type alone is not enough, and decrementAndGet() is key to ensuring that multiple threads update atomicity on the same variable.
- Source Code
public final int decrementAndGet(a) {
return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
}
Copy the code
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
Copy the code
CompareAndSwapInt () is a native method. DecrementAndGet (), as you can see in the source code, is decrementAndGet() to ensure that the operation is atomic via CAS.
RunOrder2 Experimental results
You can see that there are six newly created threads. The contents of ‘[]’ are name, priority, and ThreadGroup respectively. At the same time, it can be seen that the activeCount is 8, which means that the 6 newly created threads are active. This is related to the thread synchronization method CAS used in this experiment. CAS is a lightweight lock, and the threads that failed to acquire the lock (in this case, 5) are in a spin state, rather than blocking the thread that failed to compete for the lock like a heavyweight lock. Since these threads are not blocked, they are still marked as active.
Java.lang.ThreadGroup[name=main,maxpri=10] Thread[main,5,main] Thread[Monitor Ctrl-break,5,main] Thread[thread-0,5,main] Thread[thread-1,5,main] Thread[thread-2,5,main] Thread[thread-3,5,main] Thread[thread-4,5,main] Thread[thread-5,5,main] Thread.activeCount(): 8 RunOrder2 count:0Copy the code