从一个简单例子了解Java的Thread类

174 阅读4分钟

并行编程是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