java線程基礎知識----線程基礎知識


  不知道從什么時候開始,學習知識變成了一個短期記憶的過程,總是容易忘記自己當初學懂的知識(fuck!),不知道是自己沒有經常使用還是當初理解的不夠深入.今天准備再對java的線程進行一下系統的學習,希望能夠更好的理解使用java線程.

  1. 什么是線程,線程與進程的差別?(這一塊內容我想我已經有了一個理解,這里就不再做記錄了)

  2.java線程的狀態:

從百度上隨便找了一張圖,圖中已經很清楚的標注了thread的各個狀態以及狀態的變化的場景.我們會在接下來的章節中進行相關講解.

  3.java實現多線程的方式:

      A: 繼承Thread類:(在下面的章節進行源碼分析)

      

public class ThreadTest {
    public static void main(String[] args) {
        Thread1 thread1 = new Thread1();
        thread1.start();
    }
}
class Thread1 extends Thread{
    public void run () {
        try {
            Thread.sleep(100L);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

    B: 實現Runnable接口.

public class ThreadTest {
    public static void main(String[] args) {
        Thread thread2 = new Thread(new Thread2());
        thread2.start();
    }
}
class Thread2 implements Runnable{
    @Override
    public void run() {
        try {
            Thread.sleep(100L);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

  4. 實現多線程的兩種方法的差別:

    A. 眾所周知java不允許多繼承,那么我們集成Runnable接口實現多集成就能夠很好的避免這個限制.

    B.集成Runnable接口實現多線程有利於程序操作共享資源(后面會提到)

    這個理解起來很簡單:  我們繼承了Thread類實現run方法之后我們可以發現這樣一個問題,我們再進行線程實例化之后我們必須分別啟動線程任務.

              而我們實現Runnable接口的話,我們可以實例化多個Thread類來運行這個任務.

              當然集成Thread類也並不是不能完成共享資源的分發,而是比較費勁.

  5.  實例化:我們在初始化Thread類的時候會調用Thread內部的init方法,即便是我們不提供任何參數.init函數的結構: private void init(ThreadGroup g, Runnable target, String name,long stackSize)

    參數有:ThreadGroup,Target,name.stackSize,其中ThreadGroup會遞歸去調用父類的getThreadGroup來進行初始化,等待初始化完成之后我們會通過ThreadGroup調用checkAccess()方法來檢查當前線程是否有權限操作此線程.

java源碼:
 Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            /* Determine if it's an applet or not */

            /* If there is a security manager, ask the security manager
               what to do. */
            if (security != null) {
                g = security.getThreadGroup();
            }

            /* If the security doesn't have a strong opinion of the matter
               use the parent thread group. */
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }

        /* checkAccess regardless of whether or not threadgroup is
           explicitly passed in. */
        g.checkAccess();

  其中Thread類的daemon,priority屬性會由父類繼承.

  6.Thread類中的方法:

    Thread.sleep(): 此方法調用的是native的方法,本人不才,記得當初看過jdk源碼,但是並沒看懂底層實現。sleep方法是使當前線程休眠,講cpu占用權交給其他任意優先級的線程。但是我們應該注意:sleep方法並不會釋放對象鎖。

    Thread.join():  記得當初查看api的時候覺得api對join方法的解釋非常模糊。到底是誰等待誰結束,這有歧義。其實是這樣的,在java7 api中介紹的很清楚,是調用join的線程等待被調用線程執行結束之后再開始執行。這里有一個很值得注意的問題,join的底層調用的是wait方法,而且是循環調用,源碼如下:

 long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }

  我們可以看到源碼中join方法會在while循環中一直調用wait方法,這是因為如果wait的時間是1000ms,如果在100ms的時候另外一個線程調用了notifyAll方法,那么線程就會蘇醒。還要注意第二個問題,就是join調用wait方法,那么我們知道當main線程調用ThreadA.join的時候,main函數會獲取ThreadA對象的鎖。當ThreadA線程執行完成之后釋放該對象鎖。下面我們通過一個例子來驗證一下上面的論述:我們新建三個線程,B,C,D,然后在B-C-D中進行循環調用。

public class ThreadTest {
    public static ThreadB threadB = new ThreadB();
    public static void main(String[] args) throws InterruptedException{
        System.out.println("main線程開始調用B.join");
        threadB.start();
        threadB.join();
    }
}
class ThreadB  extends Thread{
    public void run(){
        try {
            System.out.println("ThreadB執行ThreadC.join");
            ThreadC threadC = new ThreadC();
            threadC.start();
            threadC.join();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
class ThreadC extends Thread{
    public void run(){
        try {
            System.out.println("ThreadC執行ThreadD.join");
            ThreadD threadD = new ThreadD();
            threadD.start();
            threadD.join();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
class ThreadD  extends Thread{
    public void run(){
        try {
            System.out.println("ThreadD執行ThreadB.join");
            ThreadTest.threadB.join();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

結果:
main線程開始調用B.join
ThreadB執行ThreadC.join
ThreadC執行ThreadD.join
ThreadD執行ThreadB.join

  可以發現我們的程序一直停留在這個位置,這是因為三個線程滿足了死鎖的條件,同時也可以證明,thread.join()的調用者必定會獲取被調用者的鎖。

  Thread.yield:  此方法與sleep方法類似,但是需要注意一個問題就是Thread.yield只能講cpu的使用權轉交給同等優先級的線程。

  Thread.start: 最后我們談一談Thread.start方法,想必大家都知道Thread.start方法會啟動線程,並且執行run方法中的內容。你是否會想我們為什么不直接調用Thread.run來執行呢?其實是這樣的,如果我們調用Thread.run來執行的話,jvm並不會真正的啟動一個線程,而是將其當做一個普通的方法執行。而調用start的話,在start內部會調用start0方法來新建一個線程。

 

  至此: 線程的基礎知識就結束了,下一章我們會學習關於線程鎖的相關知識。

 

    


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM