1. 程式人生 > >入坑兩個月,java.lang包下的Thread類

入坑兩個月,java.lang包下的Thread類

java程序 edm r+ 阻塞 https color dex prime 好的

Thread類API中的英文描述:

file:///D:/Java/JDK8/Docs/docs/api/index.html

技術分享圖片

英語不好,大致的意思是:

線程是程序執行時的線程,java虛擬機(JVM)允許一個應用運行多個線程(並發)。

每一個線程都自己的優先權,優先級高的線程會比優先級低的線程優先執行。每一個線程可以被設置成守護線程(daemon),當一段代碼運行一些線程時,會創建一個Thread類的對象,這個新的線程初始的優先級是創建這個線程對象時程序員設定好的,僅且僅當用戶把這個線程設定成daemon,這個線程才是daemon

當java虛擬機(JVM)開啟時,這兒通常有一個非daemon線程(一般的會調用某個類的main方法),java虛擬機(JVM)會一直執行這些線程,直到出現下面幾種情況:

技術分享圖片

第一種:程序調用Runtime類的退出方法,並且安全管理器允許該退出操作

第二種:除了daemon線程外的其他所有線程執行完畢(處於dead狀態);線程run方法中遇到return語句;線程拋出一個run方法無法處理的異常

技術分享圖片

有兩種方法來創建一個新的線程。

第一種:聲明一個類,該類繼承Thread,該類應該重寫Thread類的run方法,通過調用start方法來啟動該線程,下面給出一個列子:

 1 class PrimeThread extends Thread {
 2   long minPrime;
 3   PrimeThread(long minPrime) {
 4     this
.minPrime = minPrime; 5   } 6 7   public void run() { 8     // compute primes larger than minPrime 9     . . . 10   } 11 }

技術分享圖片

第二種:聲明一個類,實現Runnable接口,重寫Runnable接口中的run方法,下面也給出一個例子:

 1 class PrimeRun implements Runnable {
 2     long minPrime;
 3     PrimeRun(long minPrime) {
 4         this.minPrime = minPrime;
5 } 6 public void run() { 7 // compute primes larger than minPrime 8 . . . 9 } 10 }

下面我們來看一下Thread的源代碼部分:

1. Thread類實現了Runnable接口:

public class Thread implements Runnable

2. Thread類的構造器:

1 public Thread()
2 public Thread(Runnable target)
3 Thread(Runnable target, AccessControlContext acc)
4 public Thread(ThreadGroup group, Runnable target)
5 public Thread(String name)
6 public Thread(ThreadGroup group, String name)
7 public Thread(Runnable target, String name)
8 public Thread(ThreadGroup group, Runnable target, String name)
9 public Thread(ThreadGroup group, Runnable target, String name, long stackSize)

Thread類一共有9個構造器。其中第3個構造器沒有public修飾,默認用default修飾,同一個包下可用,不多做說明。

通過上面9個構造器(除3)可以看出,用戶在創建一個Thread類的對象時,可以設定的參數有:ThreadGroup、Runnable、name、stackSize

ThreadGroup:是java.lang包下的一個ThreadGroup類,ThreadGroup對象表示一個線程的集合,也可以包含另一個ThreadGroup對象。

Thread類init方法關於ThreadGroup部分源碼:

 1         Thread parent = currentThread();
 2         SecurityManager security = System.getSecurityManager();
 3         if (g == null) {
 4             /* Determine if it‘s an applet or not */
 5 
 6             /* If there is a security manager, ask the security     
 7             manager what to do. */
 8             if (security != null) {
 9                 g = security.getThreadGroup();
10             }
11 
12             /* If the security doesn‘t have a strong opinion of the 
13             matter use the parent thread group. */
14             if (g == null) {
15                 g = parent.getThreadGroup();
16             }
17         }
18 
19         /* checkAccess regardless of whether or not threadgroup 
20         is explicitly passed in. */
21         g.checkAccess();

其中currentThread()方法是獲取當前運行的線程,下面寫段代碼做個試驗:

1 public class Demo7{
2     public static void main(String[] args){
3         Thread t = Thread.currentThread();
4         System.out.println(t.getName());
5     }
6 }

技術分享圖片

System.getSecurityManager()是System類中的一個靜態方法,該方法是用來返回一個SecurityManager類的對象security,下面是System類中getSecurityManager():

    /**
     * Gets the system security interface.
     *
     * @return  if a security manager has already been established for the
     *          current application, then that security manager is returned;
     *          otherwise, <code>null</code> is returned.
     * @see     #setSecurityManager
     */
    public static SecurityManager getSecurityManager() {
        return security;
    }

註釋說明,如果這個安全管理已經創建security對象,則返回這個security,如果沒有,則返回null,其中System類的security初始值被設置為null。

    /* The security manager for the system.
     */
    private static volatile SecurityManager security = null;

再看Thread類中init方法關於ThreadGroup部分做了什麽處理?

先創建兩個對象parent和security:

Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();

接著對用戶創建Thread類對象設置的參數做一個判斷,如果用戶沒有設定ThreadGroup參數,則傳遞一個null值

init方法先判斷這個參數是否為null,如果不為null,則判斷security是否為空,如果不為空,則獲取security所在的ThreadGroup賦值給用戶創建的Thread對象,即g的值

接著再判斷g是否為空,如果還為null,則將當前線程的ThreadGroup賦值給g。總之,如果用戶未設置g值,就把security的g值賦值給g,如果security的g值也為空,就把parent的g值賦給g。

最後再調用g(ThreadGroup)的checkAccess方法,ThreadGroup類的checkAccess方法源碼:

1     public final void checkAccess() {
2         SecurityManager security = System.getSecurityManager();
3         if (security != null) {
4             security.checkAccess(this);
5         }
6     }

可以看出該方法,其實是調用了SecurityManager對象的checkAccess(ThreadGroup g)方法:

 1     public void checkAccess(ThreadGroup g) {
 2         if (g == null) {
 3             throw new NullPointerException("thread group can‘t be null");
 4         }
 5         if (g == rootGroup) {
 6             checkPermission(SecurityConstants.MODIFY_THREADGROUP_PERMISSION);
 7         } else {
 8             // just return
 9         }
10     }

其中rootGroup應該是g的最高級parent(未必正確),看一下源碼:

1 private static ThreadGroup rootGroup = getRootGroup();
2 private static ThreadGroup getRootGroup() {
3         ThreadGroup root =  Thread.currentThread().getThreadGroup();
4         while (root.getParent() != null) {
5             root = root.getParent();
6         }
7         return root;
8 }

checkPermission(SecurityConstants.MODIFY_THREADGROUP_PERMISSION)這個坑之後再填吧,挖不下去了。

Runnable:前面介紹了創建Thread類對象的兩種方法,其中一種就是傳入一個Runnable對象。

如果在創建一個線程時,沒有傳入Runnable對象,則init方法默認傳入的是一個null值,來看一下源碼:

public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}

在看看init方法對應參數代表什麽意思:

private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
  init(g, target, name, stackSize, null, true);
}

從中還可以看出,如果在創建Thread類對象時,沒有指定其姓名,會默認設置名字,即Thread-加上nextThreadNum()返回值:

private static int threadInitNumber;
private static synchronized int nextThreadNum() {
    return threadInitNumber++;
}

所以第一個線程的名字會默認為Thread-0,第二個線程的名字為Thread-1......

stackSize:棧的大小,待補充......

3. 線程的狀態

Thread類有個內部枚舉類State,該類就聲明了線程的幾種狀態:

public enum State {
    NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED;
}

NEW:線程剛創建時具有的狀態;

public class Demo8{
    public static void main(String[] args){
        Thread t = new Thread();
        System.out.println(t.getState());
    }
}

技術分享圖片

RUNNABLE:是指線程正在運行,線程獲取CPU的時間片

public class Demo8{
    public static void main(String[] args){
        Thread t = new Thread();
        t.start();
        System.out.println(t.getState());
    }
}

技術分享圖片

BLOCKED:是指程序進入阻塞狀態,假如有兩個線程,並且兩個線程都是同步安全的(synchronized),當一個線程處於runnable狀態時,則另一個線程處於blocked狀態。

public class Demo8{
    public static void main(String[] args){
        Thread t1= new Thread(){
            public void run(){
                synchronized(Thread.class){
                    for(int i = 0; i < 100; i++){
    
                    }
                }
            }
        };
        Thread t2 = new Thread(){
            public void run(){
                synchronized(Thread.class){
                    System.out.println(t1.getState());
                }
            }
        };
        t1.setPriority(1);
        t2.setPriority(10);
        t1.start();
        t2.start();
    }
}

技術分享圖片

WAITING:程序處於等待狀態,調用wait()、join()、await()、lock()等方法,都會使線程處於waiting狀態,需要註意的是這些方法必須是無參數的。

public class Demo8{
    public static void main(String[] args){
        Thread t1 = new Thread(){
            public void run(){
                try{
                    join();    
                }
                catch(InterruptedException e){
                    e.printStackTrace();
                }
            }
        };
        Thread t2 = new Thread(){
            public void run(){
                System.out.println(t1.getState());
            }
        };
        t1.start();
        t2.start();
    }
}

技術分享圖片

TIMED_WAITING:程序處於限時等待狀態,調用wait()、join()、await()、lock()、sleep()等方法,都會使線程處於waiting狀態,需要註意的是這些方法必須加入參數。

TERMINATED:終止狀態,即線程結束

4. Thread類中部分方法源碼解析

————this.start()方法

public synchronized void start() {
    if (threadStatus != 0)
        throw new IllegalThreadStateException();
    group.add(this);
    boolean started = false;
    try {
            start0();
            started = true;
    } finally {
        try {
                if (!started) {
                    group.threadStartFailed(this);
                }
        } catch (Throwable ignore) {
        }
    }
}

可以看出線程是在調用start方法時加入init方法中指定的線程池的,其次線程在創建時並不是把內部的枚舉類State的NEW值給這個線程,而是定義一個int型的threadStatus

變量,並且這個變量初始值為0。

private volatile int threadStatus = 0;

並且start還調用了一個本地的start0()方法,由於沒看過JVM相關的知識,所以對於native修飾的方法無能無力:

private native void start0();

不過IBM有一篇文章對此講解的比較詳細,這裏附上鏈接:https://www.ibm.com/developerworks/cn/java/j-lo-processthread/#icomments

大概的意思是java的start方法會調用 JVM_StartThread方法,而 JVM_StartThread方法會創建一個與本地相關的線程,該線程與java創建的線程有著一一對應關系。

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread)) 
   …
    native_thread = new JavaThread(&thread_entry, sz); 
   …

這篇文章並沒有詳細講解JVM的代碼實現過程,而是給出了方法調用圖,該圖來自上面鏈接的那篇文章:

技術分享圖片

技術分享圖片

————setPriority(int)方法

這裏先留坑,簡單講一下,線程的優先級可以理解為線程搶占cpu時間片的概率,因此並不能保證優先級高一定會先執行

 1 public class Demo2 {
 2     public static void main(String[] args) {
 3         Thread t1 = new Thread() {
 4             @Override
 5             public void run() {
 6                 for(int i = 0; i < 1000; i++) {
 7                     System.out.println("********");
 8                 }
 9             }
10         };
11         Thread t2 = new Thread() {
12             @Override
13             public void run() {
14                 for(int i = 0; i < 1000; i++) {
15                     System.out.println("--------");
16                 }
17             }
18         };
19         t1.setPriority(1);
20         t2.setPriority(10);
21         t1.start();
22         t2.start();
23     }
24 }

技術分享圖片

其次,windows操作系統的優先級為7個級別(未驗證),而java的優先級分為10個級別,所以java程序1~10幾個優先級中,必定有幾個優先級在windows操作系統下級別是一樣的,真正決定優先級的應該是本地的setPriority0()方法。

setPriority0(priority = newPriority);

—————activeCount方法,獲取當前線程池中的線程數量

 1 import java.lang.reflect.InvocationTargetException;
 2 import java.lang.reflect.Method;
 3 public class Demo3 {
 4     public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
 5         MyRunnable m = new MyRunnable();
 6         Thread t1 = Thread.currentThread();
 7         ThreadGroup tg = t1.getThreadGroup();
 8         Thread t2 = new Thread(m);
 9         Class c = ThreadGroup.class;
10         Method declaredMethod = c.getDeclaredMethod("add", Thread.class);
11         Method method = declaredMethod;
12         method.setAccessible(true);
13         method.invoke(tg,t2);
14         System.out.println(Thread.activeCount());
15         System.out.println(t1.getName());
16         System.out.println(tg.getName());
17     }
18 }
19 class MyRunnable implements Runnable{
20     public void run() {}
21 }

技術分享圖片

這裏是利用反射的機制,調用ThreadGroup類中的add(Thread t)方法,把線程添加到指定的線程池,這裏要說明的是,創建的線程對象時,Thread類構造器調用的init初始化方法,並沒有把線程加入指定的線程池,而是在start方法中調用了ThreadGroup的add方法。

未完待續......

入坑兩個月,java.lang包下的Thread類