併發編程之多線程基礎

一。多線程建立方式算法

    1。第一種繼承Thread類 重寫run方法編程

class CreateThread extends Thread {

    // run方法中,須要線程須要執行代碼
    @Override
    public void run() {
        System.out.println("運行子線程");
    }

}

public class ThreadDemo01 {

    public static void main(String[] args) {
        System.out.println("main... 主線程開始...");
        // 1.建立線程
        CreateThread CreateThread = new CreateThread();
        // 2.啓動線程
        CreateThread.start();
        System.out.println("main... 主線程結束...");
    }

}

    2。 第二種實現Runnable接口,重寫run方法多線程

class CreateRunnable implements Runnable {

    @Override
    public void run() {
        System.out.println("運行子線程");
    }
}

public class ThreadDemo2 {
    public static void main(String[] args) {
        System.out.println("-----多線程建立開始-----");
        // 1.建立一個線程
        CreateRunnable createThread = new CreateRunnable();
        // 2.開始執行線程 注意 開啓線程不是調用run方法,而是start方法
        System.out.println("-----多線程建立啓動-----");
        Thread thread = new Thread(createThread);
        thread.start();
        System.out.println("-----多線程建立結束-----");
    }

}

     3。使用匿名內部類方式併發

public class ThreadDemo3 {
    public static void main(String[] args) {
        System.out.println("-----多線程建立開始-----");
        Thread thread = new Thread(new Runnable() {
            public void run() {
                System.out.println("運行子線程");
            }
        });
        
        thread.start();
        System.out.println("-----多線程建立結束-----");
    }
}

    注意:ide

        1>  通常使用Runnable接口,符合面向接口編程,並且實現了接口還能夠繼續繼承,繼承了類不能再繼承。函數

      2> 開啓線程不是調用run方法,而是start方法。優化

二。守護線程this

    Java中有兩種線程,一種是用戶線程,另外一種是守護線程。spa

    用戶線程是指用戶自定義建立的線程,主線程中止,用戶線程不會中止。線程

    守護線程當進程不存在或主線程中止,守護線程也會被中止。

    使用setDaemon(true)方法設置爲守護線程。若是不設置爲守護線程,子線程還好運行。

public class DaemonThread {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(1000);
                    } catch (Exception e) {
                        // TODO: handle exception
                    }
                    System.out.println("我是子線程...");
                }
            }
        });
        thread.setDaemon(true);
        thread.start();
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(1000);
            } catch (Exception e) {

            }
            System.out.println("我是主線程");
        }
        System.out.println("主線程執行完畢!");
    }
}

三。多線程運行狀態

    線程從建立、運行到結束老是處於下面五個狀態之一:新建狀態、就緒狀態、運行狀態、阻塞狀態及死亡狀態。

    1。新建狀態

        當用new操做符建立一個線程時, 例如new Thread(r),線程尚未開始運行,此時線程處在新建狀態。 當一個線程處於新生狀態時,程序尚未開始運行線程中的代碼。

    2。就緒狀態

        當線程對象調用start()方法即啓動了線程,start()方法建立線程運行的系統資源,並調度線程運行run()方法。當start()方法返回後,線程就處於就緒狀態。

        處於就緒狀態的線程並不必定當即運行run()方法,線程還必須同其餘線程競爭CPU時間,只有得到CPU時間才能夠運行線程。

    3。運行狀態

        當線程得到CPU時間後,它才進入運行狀態,真正開始執行run()方法。

    4。阻塞狀態      

  線程運行過程當中,可能因爲各類緣由進入阻塞狀態:
      1> 線程經過調用sleep方法進入睡眠狀態;
      2> 線程調用一個在I/O上被阻塞的操做,即該操做在輸入輸出操做完成以前不會返回到它的調用者;
      3> 線程試圖獲得一個鎖,而該鎖正被其餘線程持有;
      4> 線程在等待某個觸發條件;

    5。死亡狀態     

     有兩個緣由會致使線程死亡:
       1>  run方法正常退出而天然死亡,
       2> 一個未捕獲的異常終止了run方法而使線程猝死。
     爲了肯定線程在當前是否存活着(就是要麼是可運行的,要麼是被阻塞了),須要使用isAlive方法。若是是可運行或被阻塞,這個方法返回true;

      若是線程仍舊是new狀態且不是可運行的, 或者線程死亡了,則返回false。

四。join()方法做用

    當在主線程當中執行到t1.join()方法時,就認爲主線程應該把執行權讓給t1,執行完子線程後才執行主線程。

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {

            @Override
            public void run() {
                System.out.println("先執行子線程");
            }
        });
        t1.start();
        // 當在主線程當中執行到t1.join()方法時,就認爲主線程應該把執行權讓給t1
        t1.join();
        System.out.println("接着執行主線程");
    }
}

     2。優先級

     在JAVA線程中,經過一個int priority來控制優先級,範圍爲1-10,其中10最高,默認值爲5。優先級越高,被分配執行的機率就越大。

class PrioritytThread implements Runnable {

    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().toString() + "---i:" + i);
        }
    }
}

public class ThreadDemo4 {
    public static void main(String[] args) {
        PrioritytThread prioritytThread = new PrioritytThread();
        Thread t1 = new Thread(prioritytThread);
        Thread t2 = new Thread(prioritytThread);
        t1.start();
        // 注意設置了優先級, 不表明每次都必定會被執行。 只是被CPU調度機率高。
        t1.setPriority(10);
        t2.start();
    }
}

    3。Yield方法

    yield()讓當前正在運行的線程回到可運行狀態,以容許具備相同優先級的其餘線程得到運行的機會。但可能會沒有效果,由於該線程可能會再次被選中運行。

五。內置的鎖

    Java提供了一種內置的鎖機制來支持原子性,每個Java對象均可以用做一個實現同步的鎖,稱爲內置鎖。

    線程進入同步代碼塊以前自動獲取到鎖,代碼塊執行完成正常退出或代碼塊中拋出異常退出時會釋放掉鎖。

    內置鎖爲互斥鎖,即線程A獲取到鎖後,線程B阻塞直到線程A釋放鎖,線程B才能獲取到同一個鎖。

    內置鎖使用synchronized關鍵字實現,synchronized關鍵字有兩種用法:

        *** 修飾須要進行同步的方法(全部訪問狀態變量的方法都必須進行同步),此時充當鎖的對象爲調用同步方法的對象。

        *** 同步代碼塊和直接使用synchronized修飾須要同步的方法是同樣的,可是鎖的粒度能夠更細,而且充當鎖的對象不必定是this,也能夠是其它對象,因此使用起來更加靈活。

class SynObj{
    // 同步方法,鎖對象爲this
    public synchronized void showA(){
        System.out.println("showA..");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void showB(){
        // 同步代碼塊,鎖對象爲this
        synchronized (this) {
            System.out.println("showB..");
        }
    }

    public void showC(){
        String s="1";
        // 同步代碼塊,鎖對象爲s
        synchronized (s) {
            System.out.println("showC..");
        }
    }
    // 靜態同步函數,鎖是該函數所屬字節碼文件對象,可使用this.getClass()方法獲取,也能夠用當前 類名.class表示。
    public static synchronized void showD(){
        System.out.println("showD..");
    }
}

public class TestSyn {
    public static void main(String[] args) {
        final SynObj sy=new SynObj();
        new Thread(new Runnable() {

            @Override
            public void run() {
                sy.showA();
            }
        }).start();
        new Thread(new Runnable() {

            @Override
            public void run() {
                sy.showB();
            }
        }).start();
        new Thread(new Runnable() {

            @Override
            public void run() {
                sy.showC();
            }
        }).start();
        new Thread(new Runnable() {

            @Override
            public void run() {
                SynObj.showD();
            }
        }).start();
    }
}

    shouA和showC以及showD會馬上打印出來,showC三秒後纔打印處理,由於showA和showB的鎖對象都是this。

     2。多線程死鎖

     多個併發進程因爭奪系統資源而產生相互等待的現象。

    死鎖產生的4個必要條件:

        1>  互斥:某種資源一次只容許一個進程訪問,即該資源一旦分配給某個進程,其餘進程就不能再訪問,直到該進程訪問結束。

        2> 佔有且等待:一個進程自己佔有資源(一種或多種),同時還有資源未獲得知足,正在等待其餘進程釋放該資源。

        3> 不可搶佔:別人已經佔有了某項資源,你不能由於本身也須要該資源,就去把別人的資源搶過來。

        4> 循環等待:存在一個進程鏈,使得每一個進程都佔有下一個進程所需的至少一種資源。

    避免死鎖:銀行家算法等

六。ThreadLocal

    ThreadLocal爲每一個使用該變量的線程提供獨立的變量副本,因此每個線程均可以獨立地改變本身的副本,而不會影響其它線程所對應的副本。

    ThreadLocal類接口很簡單,只有4個方法:

        1。void set(Object value)設置當前線程的線程局部變量的值。

        2。public Object get()該方法返回當前線程所對應的線程局部變量。

        3。public void remove()將當前線程局部變量的值刪除,目的是爲了減小內存的佔用。

            當線程結束後,對應該線程的局部變量將自動被垃圾回收,因此顯式調用該方法清除線程的局部變量並非必須的操做,但它能夠加快內存回收的速度。

        4。protected Object initialValue()返回該線程局部變量的初始值。

            這個方法是一個延遲調用方法,在線程第1次調用get()或set(Object)時才執行,而且僅執行1次。ThreadLocal中的缺省實現直接返回一個null。

class Res {
    public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
        protected Integer initialValue() {
            return 0;
        };
    };

    public Integer getNumber() {
        int count = threadLocal.get() + 1;
        threadLocal.set(count);
        return count;
    }

}

public class ThreadLocaDemo2 extends Thread{
    private Res res;

    public ThreadLocaDemo2(Res res) {
        this.res = res;
    }

    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName() + "," + res.getNumber());
        }
    }

    public static void main(String[] args) {
        Res res = new Res();
        ThreadLocaDemo2 t1 = new ThreadLocaDemo2(res);
        ThreadLocaDemo2 t2 = new ThreadLocaDemo2(res);
        t1.start();
        t2.start();
    }
}

    ThreadLoca實現原理:

    每一個Thread的對象都有一個ThreadLocalMap,當建立一個ThreadLocal的時候,就會將該ThreadLocal對象添加到該Map中,其中鍵就是ThreadLocal,值能夠是任意類型。

七。多線程三大特性

    1。原子性:一個操做或者多個操做 要麼所有執行而且執行的過程不會被任何因素打斷,要麼就都不執行。

    2。可見性:當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其餘線程可以當即看獲得修改的值。

    3。有序性:程序執行的順序按照代碼的前後順序執行。

        通常來講處理器爲了提升程序運行效率,可能會對輸入代碼進行優化。

        它不保證程序中各個語句的執行前後順序同代碼中的順序一致,可是它會保證程序最終執行結果和代碼順序執行的結果是一致的。

相關文章
相關標籤/搜索