Android 다 중 스 레 드 분석의 2: Thread 의 실현

18400 단어
Android 다 중 스 레 드 분석의 2: Thread 의 실현
나 조 휘 (罗 朝 光)http://www.cnblogs.com/kesalin/)
CC 허가, 전재 출처 밝 혀 주세요.
 
앞에서 'Android 다 중 스 레 드 분석 중 하나: Thread 비동기 로 그림 을 다운로드 합 니 다' 에서 Thread 를 사용 하여 비동기 사 무 를 처리 하 는 방법 을 보 여 주 었 습 니 다.예제 에서 이 자바 Thread 류 는 모두 Framework 층 에 있 는 클래스 로 JNI 를 통 해 dalvik 안의 Thread 와 관련 된 방법 으로 이 루어 집 니 다.따라서 Androd 중의 스 레 드 를 분석 하려 면 이 두 층 의 스 레 드 와 관련 된 코드 를 분석 해 야 한다. 이것 이 바로 본 고 에서 연구 하고 자 하 는 주제 이다.본 고 는 Framework 계층 의 자바 Thread 를 Android 스 레 드 / thread 라 고 부 르 고 dalvik 의 Thread 는 dalvik 스 레 드 / Thread 가 됩 니 다. 
본 논문 과 관련 된 안 드 로 이 드 소스 경로: android / libcore / luni / src / main / java / lang / Runnable. java android / libcore / luni / src / main / java / lang / thread. java android / libcore / luni / main / java / java / lang / threadGroup. javaandroid / libcore / luni / sarc / main / java / lang / VMThread. java android / dalvik / vm / native / javalang_VMThread.cppandroid/dalvik/vm/Thread.cpp
먼저 안 드 로 이 드 Thread 를 분석 합 니 다. 이 종류의 소스 코드 는 안 드 로 이 드 / libcore / luni / src / main / java / lang / Thread. java 에 있 습 니 다. 이것 은 Runnable 인 터 페 이 스 를 실현 합 니 다.Runnable 은 되 돌아 오지 않 는 값 이 하나 밖 에 없어 요. void run () 의 인터페이스:
/**
 * Represents a command that can be executed. Often used to run code in a
 * different {@link Thread}.
 */
public interface Runnable {
    /**
     * Starts executing the active part of the class' code. This method is
     * called when a thread is started that has been created with a class which
     * implements {@code Runnable}.
     */
    public void run();
}

Android Thread 에는 여섯 가지 상태 가 존재 합 니 다. 이 상태 들 은 매 거 진 State 에 정의 되 어 있 습 니 다. 원본 주석 이 선명 하 게 쓰 여 있 습 니 다. 여기 서 는 잔소리 하지 않 습 니 다. 
    /**
     * A representation of a thread's state. A given thread may only be in one
     * state at a time.
     */
    public enum State {
        /**
         * The thread has been created, but has never been started.
         */
        NEW,
        /**
         * The thread may be run.
         */
        RUNNABLE,
        /**
         * The thread is blocked and waiting for a lock.
         */
        BLOCKED,
        /**
         * The thread is waiting.
         */
        WAITING,
        /**
         * The thread is waiting for a specified amount of time.
         */
        TIMED_WAITING,
        /**
         * The thread has been terminated.
         */
        TERMINATED
    }

Android Thread 클래스 의 일부 핵심 구성원 변 수 는 다음 과 같 습 니 다.
    volatile VMThread vmThread;
    volatile ThreadGroup group;
    volatile boolean daemon;    
    volatile String name;
    volatile int priority;
    volatile long stackSize;
    Runnable target;
    private static int count = 0;
    private long id;
    ThreadLocal.Values localValues;

vmThread: dalvik thread 에 대한 간단 한 패키지 로 볼 수 있 습 니 다. Thread 류 는 VMThread 에 있 는 JNI 방법 으로 dalvik 에서 스 레 드 를 조작 하 는 방법 을 호출 합 니 다. 구성원 변수 thread 와 vmata 를 통 해 Android Thread 와 dalvik Thread 를 연결 할 수 있 습 니 다.
group: 모든 스 레 드 는 하나의 group 에 속 합 니 다. 스 레 드 가 생 성 될 때 특정한 group 을 추가 합 니 다. 스 레 드 가 끝 날 때 이 group 에서 제거 합 니 다.
daemon: 현재 스 레 드 는 데 몬 스 레 드 가 아 닙 니 다. 데 몬 스 레 드 는 데 몬 스 레 드 가 아 닌 상태 에서 만 실 행 됩 니 다.
priority: 스 레 드 우선 순위, 자바 Thread 류 의 스 레 드 우선 순위 추출 범 위 는 [1, 10] 이 고 기본 우선 순 위 는 5 입 니 다.
stackSize: 스 레 드 스 택 크기, 기본 값 은 0 입 니 다. 기본 스 레 드 스 택 크기 (dalvik 의 전역 변수 gDvm. stackSize 에 의 해 결 정 됩 니 다) 를 사용 합 니 다.
target: runnable 대상, Thread 의 run () 방법 에서 이 target 의 run () 방법 을 바 꿉 니 다. 이것 은 스 레 드 가 진정 으로 사 무 를 처리 하 는 곳 입 니 다.
id: Android 스 레 드 id, 증가 count 를 통 해 이 id 를 얻 을 수 있 습 니 다. 스 레 드 설정 이름 이 표시 되 지 않 으 면 Thread + id 를 스 레 드 이름 으로 사용 합 니 다.이것 은 진정한 의미 의 스 레 드 id 가 아 닙 니 다. 즉, logcat 에서 인쇄 된 tid 는 이 id 가 아 닙 니 다. tid 는 dalvik 스 레 드 의 id 를 말 합 니 다.
localValues: 스 레 드 로 컬 저장 (TLS) 데이터;
다음은 Android Thread 의 구조 함 수 를 살 펴 보 겠 습 니 다. 대부분의 구조 함 수 는 정적 함수 create 를 통 해 이 루어 집 니 다. 다음은 create 라 는 관건 적 인 함 수 를 상세 하 게 분석 하 겠 습 니 다.
    private void create(ThreadGroup group, Runnable runnable, String threadName, long stackSize) {
        Thread currentThread = Thread.currentThread();
        if (group == null) {
            group = currentThread.getThreadGroup();
        }

        if (group.isDestroyed()) {
            throw new IllegalThreadStateException("Group already destroyed");
        }

        this.group = group;

        synchronized (Thread.class) {
            id = ++Thread.count;
        }

        if (threadName == null) {
            this.name = "Thread-" + id;
        } else {
            this.name = threadName;
        }

        this.target = runnable;
        this.stackSize = stackSize;

        this.priority = currentThread.getPriority();

        this.contextClassLoader = currentThread.contextClassLoader;

        // Transfer over InheritableThreadLocals.
        if (currentThread.inheritableValues != null) {
            inheritableValues = new ThreadLocal.Values(currentThread.inheritableValues);
        }

        // add ourselves to our ThreadGroup of choice
        this.group.addThread(this);
    }

우선, 정적 함수 currentThread 를 통 해 스 레 드 가 있 는 현재 스 레 드 를 만 든 다음 현재 스 레 드 의 일부 속성 을 만 들 새 스 레 드 에 전달 합 니 다.이것 은 VMThread 를 통 해 dalvik 의 코드 를 변환 하여 이 루어 진 것 입 니 다.
    public static Thread currentThread() {
        return VMThread.currentThread();
    }

VMThread 의 currentThread 는 native 방법 으로 JNI 를 실현 합 니 다. android/dalvik/vm/native/java_lang_VMThread. cpp 의 Dalvik_java_lang_VMThread_currentThread 방법:
static void Dalvik_java_lang_VMThread_currentThread(const u4* args,
    JValue* pResult)
{
    UNUSED_PARAMETER(args);

    RETURN_PTR(dvmThreadSelf()->threadObj);
}

방법 dvmThreadSelf () 방법 정의 android / dalvik / vm / thread. cpp 중:
Thread* dvmThreadSelf()
{
    return (Thread*) pthread_getspecific(gDvm.pthreadKeySelf);
}

위의 호출 스 택 에서 볼 수 있 습 니 다. 모든 dalvik 스 레 드 는 자신 을 key 에 저장 합 니 다. pthreadKeySelf 의 스 레 드 로 컬 저장 소 에서 현재 스 레 드 를 가 져 올 때 이 key 조회 에 따라 가 져 오 면 됩 니 다. dalvik Thread 에는 threadObj 라 는 구성원 변수 가 있 습 니 다.
    /* the java/lang/Thread that we are associated with */
    Object*     threadObj;

dalvik Thread 이 멤버 변수 threadObj 연 결 된 것 은 대응 하 는 Android Thread 대상 이기 때문에 native 방법 을 통 해 VMThread. currentThread () 는 TLS 에 저 장 된 현재 dalvik 스 레 드 에 대응 하 는 Android Thread 를 되 돌려 줍 니 다.
이 어 위의 코드 를 분석 하고 새 스 레 드 에 group 을 지정 하지 않 으 면 group 을 현재 스 레 드 가 있 는 group 으로 지정 한 다음 새 스 레 드 에 name, priority 등 을 설정 합 니 다.마지막 으로 ThreadGroup 호출 을 통 해 addThread 방법 은 새 스 레 드 를 group 에 추가 합 니 다.
    /**
     * Called by the Thread constructor.
     */
    final void addThread(Thread thread) throws IllegalThreadStateException {
        synchronized (threadRefs) {
            if (isDestroyed) {
                throw new IllegalThreadStateException();
            }
            threadRefs.add(new WeakReference<Thread>(thread));
        }
    }

ThreadGroup 의 코드 는 상대 적 으로 간단 합 니 다. 이름 이 있 습 니 다. threadRefs 의 목록 은 같은 그룹 에 속 하 는 thread 인용 을 가지 고 있 으 며, thread 그룹 에 대해 스 레 드 작업 을 할 수 있 습 니 다.
위 에서 분석 한 것 은 Android Thread 의 구조 과정 이다. 위의 분석 을 통 해 알 수 있 듯 이 Android Thread 의 구조 방법 은 일부 스 레 드 속성 을 설정 한 것 일 뿐 새로운 것 을 만 들 지 않 았 습 니 다. dalvik Thread,dalvik Thread 생 성 과정 은 클 라 이언 트 코드 가 Android Thread 의 start () 방법 을 호출 할 때 까지 기 다 려 야 합 니 다.다음은 저희 가 분석 을 해 보도 록 하 겠 습 니 다. Java Thread 의 start () 방법:
public synchronized void start() {

        if (hasBeenStarted) {
            throw new IllegalThreadStateException("Thread already started."); // TODO Externalize?
        }

        hasBeenStarted = true;

        VMThread.create(this, stackSize);
    }
}

Android Thread 의 start 방법 은 간단 합 니 다. VMThread 를 바 꾸 는 native 방법 create 일 뿐 JNI 는 android/dalvik/vm/native/java_lang_VMThread. cpp 의 Dalvikjava_lang_VMThread_create 방법:
static void Dalvik_java_lang_VMThread_create(const u4* args, JValue* pResult)
{
    Object* threadObj = (Object*) args[0];
    s8 stackSize = GET_ARG_LONG(args, 1);

    /* copying collector will pin threadObj for us since it was an argument */
    dvmCreateInterpThread(threadObj, (int) stackSize);
    RETURN_VOID();
}
dvmCreateInterpThread      Thread.cpp  ,           ,           :

bool dvmCreateInterpThread(Object* threadObj, int reqStackSize)
{
    Thread* self = dvmThreadSelf();
    ...
    Thread* newThread = allocThread(stackSize); 
    newThread->threadObj = threadObj;
    ...
    Object* vmThreadObj = dvmAllocObject(gDvm.classJavaLangVMThread, ALLOC_DEFAULT);
    dvmSetFieldInt(vmThreadObj, gDvm.offJavaLangVMThread_vmData, (u4)newThread);
    dvmSetFieldObject(threadObj, gDvm.offJavaLangThread_vmThread, vmThreadObj);
    ...
    pthread_t threadHandle;
    int cc = pthread_create(&threadHandle, &threadAttr, interpThreadStart, newThread);

    /*
     * 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();
    ...
}

/*
 * Alloc and initialize a Thread struct.
 *
 * Does not create any objects, just stuff on the system (malloc) heap.
 */
static Thread* allocThread(int interpStackSize)
{
    Thread* thread;
    thread = (Thread*) calloc(1, sizeof(Thread));
    ...
    thread->status = THREAD_INITIALIZING;
}

우선 호출 을 통 해 allocThread 이름 만 들 기 new Thread 의 dalvik Thread  그리고 일부 속성 을 설정 하여 구성원 변 수 를 설정 합 니 다. threadObj 는 들 어 오 는 Android Thread 입 니 다. 그러면 dalvik Thread 는 Android Thread 와 연 결 됩 니 다.그리고 vmThreadObj 라 는 이름 을 만 듭 니 다. VMThread 대상, 구성원 변수 설정 vmData newThread, Android Thread 설정 threadObj 의 구성원 변수 이것 괜찮아요? vmThreadObj, 이렇게 하면 Android Thread 는 VMThread 의 구성원 변 수 를 통 해 vmData 와 dalvik Thread 가 연결 되 었 습 니 다.
그리고 pthread 를 통 해create pthread 스 레 드 를 만 들 고 이 스 레 드 start 를 시작 하면 이 스 레 드 에 들 어 갑 니 다. thread entry 가 실 행 됩 니 다. 새 스 레 드 의 thread entry 방법 을 보 겠 습 니 다. interpThreadStart, 마찬가지 로 중요 한 곳 만 표시 합 니 다:
/*
 * pthread entry function for threads started from interpreted code.
 */
static void* interpThreadStart(void* arg)
{
    Thread* self = (Thread*) arg;

    std::string threadName(dvmGetThreadName(self));
    setThreadName(threadName.c_str());

    /*
     * Finish initializing the Thread struct.
     */
    dvmLockThreadList(self);
    prepareThread(self);

    while (self->status != THREAD_VMWAIT)
        pthread_cond_wait(&gDvm.threadStartCond, &gDvm.threadListLock);

    dvmUnlockThreadList();

    /*
     * Add a JNI context.
     */
    self->jniEnv = dvmCreateJNIEnv(self);

    /*
     * Change our state so the GC will wait for us from now on.  If a GC is
     * in progress this call will suspend us.
     */
    dvmChangeStatus(self, THREAD_RUNNING);

    /*
     * Execute the "run" method.
     *
     * At this point our stack is empty, so somebody who comes looking for
     * stack traces right now won't have much to look at.  This is normal.
     */
    Method* run = self->threadObj->clazz->vtable[gDvm.voffJavaLangThread_run];
    JValue unused;

    ALOGV("threadid=%d: calling run()", self->threadId);
    assert(strcmp(run->name, "run") == 0);
    dvmCallMethod(self, run, self->threadObj, &unused);
    ALOGV("threadid=%d: exiting", self->threadId);

    /*
     * Remove the thread from various lists, report its death, and free
     * its resources.
     */
    dvmDetachCurrentThread();

    return NULL;
}

/*
 * Finish initialization of a Thread struct.
 *
 * This must be called while executing in the new thread, but before the
 * thread is added to the thread list.
 *
 * NOTE: The threadListLock must be held by the caller (needed for
 * assignThreadId()).
 */
static bool prepareThread(Thread* thread)
{
    assignThreadId(thread);
    thread->handle = pthread_self();
    thread->systemTid = dvmGetSysThreadId();

    setThreadSelf(thread);
    ...

    return true;
}

/*
 * Explore our sense of self.  Stuffs the thread pointer into TLS.
 */
static void setThreadSelf(Thread* thread)
{
    int cc;

    cc = pthread_setspecific(gDvm.pthreadKeySelf, thread);
    ...
}

새 라인 의 thread entry 방법 interpThreadStart 에서 먼저 스 레 드 의 이름 을 설정 한 다음 prepareThread 를 통 해 스 레 드 id 와 다른 속성 을 설정 하고 호출 합 니 다. setThreadSelf 새 dalvik Thread 자 체 를 TLS 에 저장 하면 통과 할 수 있 습 니 다. dvmThreadSelf 방법 은 TLS 에서 가 져 옵 니 다.상태 THREAD_RUNNING, 그리고 Android Thread 에 대응 하 는 run 방법 을 호출 하여 클 라 이언 트 코드 를 실행 합 니 다.
    public void run() {
        if (target != null) {
            target.run();
        }
    }

Android Thread 에서 Looper 가 있 는 Android HandlerThread 를 계승 하 는 데 있어 서 run 방법 () 을 복사 합 니 다. (Looper 에 관 한 이 야 기 는 다음 편 에서 설명 하 겠 습 니 다. 여 기 는 잠시 생략 합 니 다)
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

target 은 앞에서 이미 소 개 했 습 니 다. 이것 은 스 레 드 가 논리 적 인 사 무 를 진정 으로 처리 하 는 곳 입 니 다.논리 적 인 업무 처리 가 끝나 면 run 에서 돌아 오 면 스 레 드 가 돌아 옵 니 다. interpThreadStart 방법 에서 dvmDetachCurrentThread 방법 을 계속 실행 합 니 다.
/*
 * Detach the thread from the various data structures, notify other threads
 * that are waiting to "join" it, and free up all heap-allocated storage.
 * /
void dvmDetachCurrentThread()
{
    Thread* self = dvmThreadSelf();
    Object* vmThread;
    Object* group;
    ...
    group = dvmGetFieldObject(self->threadObj, gDvm.offJavaLangThread_group);

    /*
     * Remove the thread from the thread group.
     */
    if (group != NULL) {
        Method* removeThread =
            group->clazz->vtable[gDvm.voffJavaLangThreadGroup_removeThread];
        JValue unused;
        dvmCallMethod(self, removeThread, group, &unused, self->threadObj);
    }

    /*
     * Clear the vmThread reference in the Thread object.  Interpreted code
     * will now see that this Thread is not running.  As this may be the
     * only reference to the VMThread object that the VM knows about, we
     * have to create an internal reference to it first.
     */
    vmThread = dvmGetFieldObject(self->threadObj,
                    gDvm.offJavaLangThread_vmThread);
    dvmAddTrackedAlloc(vmThread, self);
    dvmSetFieldObject(self->threadObj, gDvm.offJavaLangThread_vmThread, NULL);

    /* clear out our struct Thread pointer, since it's going away */
    dvmSetFieldObject(vmThread, gDvm.offJavaLangVMThread_vmData, NULL);

    ...

    /*
     * Thread.join() is implemented as an Object.wait() on the VMThread
     * object.  Signal anyone who is waiting.
     */
    dvmLockObject(self, vmThread);
    dvmObjectNotifyAll(self, vmThread);
    dvmUnlockObject(self, vmThread);

    dvmReleaseTrackedAlloc(vmThread, self);
    vmThread = NULL;

    ...

    dvmLockThreadList(self);

    /*
     * Lose the JNI context.
     */
    dvmDestroyJNIEnv(self->jniEnv);
    self->jniEnv = NULL;

    self->status = THREAD_ZOMBIE;

    /*
     * Remove ourselves from the internal thread list.
     */
    unlinkThread(self);

    ...

    releaseThreadId(self);
    dvmUnlockThreadList();

    setThreadSelf(NULL);

    freeThread(self);
}

/*
 * Free a Thread struct, and all the stuff allocated within.
 */
static void freeThread(Thread* thread)
{
    ...
    free(thread);
}

... 에 있다 dvmDetachCurrentThread 함수 에서 현재 스 레 드 self 를 먼저 가 져 옵 니 다. 여기 서 얻 은 것 은 현재 thread entry 를 실행 하 는 새로운 스 레 드 입 니 다. 그 다음 에 해당 하 는 Android Thread 대상 을 통 해 threadObj 이 대상 이 있 는 group 를 가 져 온 후, threadObj 이 Android Thread 대상 은 group 에서 제거 합 니 다.이 어 Android 와 dalvik 스 레 드 간 의 관 계 를 제거 하고 join 에 게 이 스 레 드 의 다른 스 레 드 를 알려 줍 니 다.마지막 으로 스 레 드 상 태 를 설정 합 니 다. THREAD_ZOMBIE, TLS 에 저 장 된 스 레 드 값 을 제거 하고 호출 을 통 해 freeThread 메모 리 를 방출 하면 스 레 드 가 종 료 됩 니 다.

좋은 웹페이지 즐겨찾기