并行编程是Java的重要部分,本文通过RunOrder的例子结合源码介绍Thread类及部分锁相关的知识。
RunOrder与RunOrder2
RunOrder
RunOrder模拟了多个客户下单,仓库管理系统更新存货量的过程。在代码实现上表现为多个线程各自循环多次对count
变量进行自减操作。其中RunOrder
未使用任何同步手段,会得到与预期不一致的效果,RunOrder2
使用AtomicInteger
类型的count
变量及其对应的自减方法,完成线程同步。AtomicInteger
是基于CAS(Compare And Swap)实现锁的。
public class RunOrder {
private static final int cCount = 6000000;
public int count = cCount;
private void order() {
count--;
}
public static void main(String[] args) throws IOException, InterruptedException {
}
public void orderWithMultithread() {
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();
}
}
}
Thread构造函数中的ThreadGroup
在new Thread的过程中,如果省略第一个ThreadGroup参数,在仅以Runable Target为参数的Thread构造函数中ThreadGroup会被指定为null。在这种情况下新创建的Thread会被归于main ThreadGroup。
main ThreadGroup也不是根线程组,system ThreadGroup包括了main ThreadGroup。main线程组可以通过getParent()
获得system线程组。
- Source Code
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(ThreadGroup group, Runnable target) {
init(group, target, "Thread-" + nextThreadNum(), 0);
}
线程调用终止方法exit()的时候,线程所在的线程组会负责将其从线程组中移除并终止。
private void exit() {
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;
}
void threadTerminated(Thread t) {
synchronized (this) {
remove(t);
if (nthreads == 0) {
notifyAll();
}
if (daemon && (nthreads == 0) &&
(nUnstartedThreads == 0) && (ngroups == 0))
{
destroy();
}
}
}
start()与run()的区别
调用start()方法使新线程开始执行Runable Target中的方法。注意:如果在此处调用的run()方法,其实际效果相当于不新创建线程,由主线程逐一执行Runable Target中的方法。
- Source Code
public synchronized void start() {
/**
* 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 */
}
}
}
start()方法将新线程加入ThreadGroup,并调用了start0()完成线程启动的主要工作。那么start0()做了什么?
- Source Code
private native void start0();
/**
* 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)
*/
可以看到start0()是一个native方法,由其他语言实现。基于注释推测,start0()在操作系统(内核)层面初始化新线程并启动新线程。
- Source Code
@Override
public void run() {
if (target != null) {
target.run();
}
}
run()这边就很简单,调用了Runable Target的run()方法。所以通过调用新线程的run()方法,无论在操作系统层面还是JVM层面都没有新建线程。后续在RunOrder2中的实验结果也证明了这一点。
java.lang.ThreadGroup[name=main,maxpri=10]
Thread[main,5,main]
Thread[Monitor Ctrl-Break,5,main]
Thread.activeCount(): 2
RunOrder2 count:0
可以看到main线程组中只有main线程自己和一个Monitor线程,没有新创建的线程(用户新创建线程的默认命名方式是Thread-N)。这也说明即使是没有新建线程的”单线程“Java程序,在执行时也总是有2个线程,一个main线程,一个Monitor Ctrl-Break线程。
RunOrder2
public class RunOrder2 {
private static final int cCount = 6000000;
private AtomicInteger count = new AtomicInteger(cCount);
private void order() {
count.decrementAndGet();
}
public static void main(String[] args) throws InterruptedException {
test3();
}
//......
}
AtomicInteger与CAS
很明显,光使用AtomicInteger
数据类型是不够的,自减操作方法decrementAndGet()
是确保多线程对同一变量更新操作原子性的关键。
- Source Code
public final int decrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
}
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;
}
其中compareAndSwapInt()
是一个native方法。从源码中可以看到,decrementAndGet()
是通过CAS来保证操作原子性的。
RunOrder2实验结果
可以看到6个新创建的线程,'[]'中的内容分别是name, priority, ThreadGroup。同时可以看到此时activeCount为8,意味着6个新创建的线程都处于活动状态。这与本实验使用的线程同步手段CAS有关,CAS是轻量级锁,未能获取锁的线程(本例:5个)处于自旋状态,并非像重量级锁一样将竞争锁失败的线程阻塞。由于这些线程没有被阻塞,它们仍被标记为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:0