【从源码看Android】04 Android dalvik虚拟机Thread的底层实现

来源:互联网 发布:淘宝美工招聘网 编辑:程序博客网 时间:2024/05/22 02:00

这篇文章内容比较浅显,涉及到的源代码不多,更多是用来做一个读书笔记


我们都知道jvm的thread实现从jdk1.2之后就使用了原生线程实现方式,


对于Sun JDK来说,它的windows版和Linux版都是使用一对一的县城模型实现的

一条Java线程就映射到一条轻量级进程之中


因为android是基于linux,

按照这个推理,我们在java测试程序中跑

for (int ii = 0; ii < 10; ii++)        {            final int tmp = ii;            new Thread(new Runnable() {                @Override                public void run()                {                    try {                        Thread.sleep(10000);                        Log.e("ashqal","end of thread! -- " + tmp);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }            }).start();        }

并且在adb shell中ps出当前的运行的进程

事实告诉我们只有一个app进程在运行,

所以在dalvik vm中并不是一条Java线程就对应一个LWP(轻量级进程)


那么Android中是如何实现的呢?

从dalvik/vm/Thread.c中可以看到

当java程序调用thread.start()时


/* * Create a thread as a result of java.lang.Thread.start(). * * We do have to worry about some concurrency problems, e.g. programs * that try to call Thread.start() on the same object from multiple threads. * (This will fail for all but one, but we have to make sure that it succeeds * for exactly one.) * * Some of the complexity here arises from our desire to mimic the * Thread vs. VMThread class decomposition we inherited.  We've been given * a Thread, and now we need to create a VMThread and then populate both * objects.  We also need to create one of our internal Thread objects. * * Pass in a stack size of 0 to get the default. * * The "threadObj" reference must be pinned by the caller to prevent the GC * from moving it around (e.g. added to the tracked allocation list). */bool dvmCreateInterpThread(Object* threadObj, int reqStackSize){    pthread_attr_t threadAttr;    pthread_t threadHandle;    Thread* self;    Thread* newThread = NULL;    Object* vmThreadObj = NULL;    int stackSize;    assert(threadObj != NULL);    if(gDvm.zygote) {        // Allow the sampling profiler thread. We shut it down before forking.        StringObject* nameStr = (StringObject*) dvmGetFieldObject(threadObj,                    gDvm.offJavaLangThread_name);        char* threadName = dvmCreateCstrFromString(nameStr);        bool profilerThread = strcmp(threadName, "SamplingProfiler") == 0;        free(threadName);        if (!profilerThread) {            dvmThrowException("Ljava/lang/IllegalStateException;",                "No new threads in -Xzygote mode");            goto fail;        }    }    self = dvmThreadSelf();    if (reqStackSize == 0)        stackSize = gDvm.stackSize;    else if (reqStackSize < kMinStackSize)        stackSize = kMinStackSize;    else if (reqStackSize > kMaxStackSize)        stackSize = kMaxStackSize;    else        stackSize = reqStackSize;    pthread_attr_init(&threadAttr);    pthread_attr_setdetachstate(&threadAttr, PTHREAD_CREATE_DETACHED);    /*     * To minimize the time spent in the critical section, we allocate the     * vmThread object here.     */    vmThreadObj = dvmAllocObject(gDvm.classJavaLangVMThread, ALLOC_DEFAULT);    if (vmThreadObj == NULL)        goto fail;    newThread = allocThread(stackSize);    if (newThread == NULL)        goto fail;    newThread->threadObj = threadObj;    assert(newThread->status == THREAD_INITIALIZING);    /*     * We need to lock out other threads while we test and set the     * "vmThread" field in java.lang.Thread, because we use that to determine     * if this thread has been started before.  We use the thread list lock     * because it's handy and we're going to need to grab it again soon     * anyway.     */    dvmLockThreadList(self);    if (dvmGetFieldObject(threadObj, gDvm.offJavaLangThread_vmThread) != NULL) {        dvmUnlockThreadList();        dvmThrowException("Ljava/lang/IllegalThreadStateException;",            "thread has already been started");        goto fail;    }    /*     * There are actually three data structures: Thread (object), VMThread     * (object), and Thread (C struct).  All of them point to at least one     * other.     *     * As soon as "VMThread.vmData" is assigned, other threads can start     * making calls into us (e.g. setPriority).     */    dvmSetFieldInt(vmThreadObj, gDvm.offJavaLangVMThread_vmData, (u4)newThread);    dvmSetFieldObject(threadObj, gDvm.offJavaLangThread_vmThread, vmThreadObj);    /*     * Thread creation might take a while, so release the lock.     */    dvmUnlockThreadList();    ThreadStatus oldStatus = dvmChangeStatus(self, THREAD_VMWAIT);    int cc = pthread_create(&threadHandle, &threadAttr, interpThreadStart,            newThread);    oldStatus = dvmChangeStatus(self, oldStatus);    if (cc != 0) {        /*         * Failure generally indicates that we have exceeded system         * resource limits.  VirtualMachineError is probably too severe,         * so use OutOfMemoryError.         */        LOGE("Thread creation failed (err=%s)\n", strerror(errno));        dvmSetFieldObject(threadObj, gDvm.offJavaLangThread_vmThread, NULL);        dvmThrowException("Ljava/lang/OutOfMemoryError;",            "thread creation failed");        goto fail;    }    /*     * We need to wait for the thread to start.  Otherwise, depending on     * the whims of the OS scheduler, we could return and the code in our     * thread could try to do operations on the new thread before it had     * finished starting.     *     * The new thread will lock the thread list, change its state to     * THREAD_STARTING, broadcast to gDvm.threadStartCond, and then sleep     * on gDvm.threadStartCond (which uses the thread list lock).  This     * thread (the parent) will either see that the thread is already ready     * after we grab the thread list lock, or will be awakened from the     * condition variable on the broadcast.     *     * We don't want to stall the rest of the VM while the new thread     * starts, which can happen if the GC wakes up at the wrong moment.     * So, we change our own status to VMWAIT, and self-suspend if     * necessary after we finish adding the new thread.     *     *     * We have to deal with an odd race with the GC/debugger suspension     * mechanism when creating a new thread.  The information about whether     * or not a thread should be suspended is contained entirely within     * the Thread struct; this is usually cleaner to deal with than having     * one or more globally-visible suspension flags.  The trouble is that     * we could create the thread while the VM is trying to suspend all     * threads.  The suspend-count won't be nonzero for the new thread,     * so dvmChangeStatus(THREAD_RUNNING) won't cause a suspension.     *     * The easiest way to deal with this is to prevent the new thread from     * running until the parent says it's okay.  This results in the     * following (correct) sequence of events for a "badly timed" GC     * (where '-' is us, 'o' is the child, and '+' is some other thread):     *     *  - call pthread_create()     *  - lock thread list     *  - put self into THREAD_VMWAIT so GC doesn't wait for us     *  - sleep on condition var (mutex = thread list lock) until child starts     *  + GC triggered by another thread     *  + thread list locked; suspend counts updated; thread list unlocked     *  + loop waiting for all runnable threads to suspend     *  + success, start GC     *  o child thread wakes, signals condition var to wake parent     *  o child waits for parent ack on condition variable     *  - we wake up, locking thread list     *  - add child to thread list     *  - unlock thread list     *  - change our state back to THREAD_RUNNING; GC causes us to suspend     *  + GC finishes; all threads in thread list are resumed     *  - lock thread list     *  - set child to THREAD_VMWAIT, and signal it to start     *  - unlock thread list     *  o child resumes     *  o child changes state to THREAD_RUNNING     *     * The above shows the GC starting up during thread creation, but if     * it starts anywhere after VMThread.create() is called it will     * produce the same series of events.     *     * Once the child is in the thread list, it will be suspended and     * resumed like any other thread.  In the above scenario the resume-all     * code will try to resume the new thread, which was never actually     * suspended, and try to decrement the child's thread suspend count to -1.     * We can catch this in the resume-all code.     *     * Bouncing back and forth between threads like this adds a small amount     * of scheduler overhead to thread startup.     *     * One alternative to having the child wait for the parent would be     * to have the child inherit the parents' suspension count.  This     * would work for a GC, since we can safely assume that the parent     * thread didn't cause it, but we must only do so if the parent suspension     * was caused by a suspend-all.  If the parent was being asked to     * suspend singly by the debugger, the child should not inherit the value.     *     * We could also have a global "new thread suspend count" that gets     * picked up by new threads before changing state to THREAD_RUNNING.     * This would be protected by the thread list lock and set by a     * suspend-all.     */    dvmLockThreadList(self);    assert(self->status == THREAD_RUNNING);    self->status = THREAD_VMWAIT;    while (newThread->status != THREAD_STARTING)        pthread_cond_wait(&gDvm.threadStartCond, &gDvm.threadListLock);    LOG_THREAD("threadid=%d: adding to list\n", newThread->threadId);    newThread->next = gDvm.threadList->next;    if (newThread->next != NULL)        newThread->next->prev = newThread;    newThread->prev = gDvm.threadList;    gDvm.threadList->next = newThread;    if (!dvmGetFieldBoolean(threadObj, gDvm.offJavaLangThread_daemon))        gDvm.nonDaemonThreadCount++;        // guarded by thread list lock    dvmUnlockThreadList();    /* change status back to RUNNING, self-suspending if necessary */    dvmChangeStatus(self, THREAD_RUNNING);    /*     * Tell the new thread to start.     *     * We must hold the thread list lock before messing with another thread.     * In the general case we would also need to verify that newThread was     * still in the thread list, but in our case the thread has not started     * executing user code and therefore has not had a chance to exit.     *     * We move it to VMWAIT, and it then shifts itself to RUNNING, which     * comes with a suspend-pending check.     */    dvmLockThreadList(self);    assert(newThread->status == THREAD_STARTING);    newThread->status = THREAD_VMWAIT;    pthread_cond_broadcast(&gDvm.threadStartCond);    dvmUnlockThreadList();    dvmReleaseTrackedAlloc(vmThreadObj, NULL);    return true;fail:    freeThread(newThread);    dvmReleaseTrackedAlloc(vmThreadObj, NULL);    return false;}


android会通过jni调用Thread.c的dvmCreateInterpThread,并在内部调用pthread_create完成thread的实现

所以java thread和pthread是一对一的对应关系


我们知道android内核的实现是google开发的linux内核Bionic

所以我猜测bionic内核不是以轻量级进程的方式实现的进程

因为水平有限,我只做了验证

写了一个pthread测试程序,运行在android上,打印getpid后结果与主线程的getpid值一样,且与ps内显示的值相同

所以验证了上述猜测




0 0
原创粉丝点击