Java多線程

本節內容:html

  • 什麼是線程
  • 多線程並行和併發的區別
  • 多線程程序實現方式
  • 多線程中的一些方法
  • 多線程之線程同步
  • 線程安全
  • 多線程的死鎖
  • 線程安全的類
  • 多線程設計模式之單例設計模式
  • 線程組的概述和使用
  • 線程的五種狀態
  • 線程池的概述和使用
  • 設計模式之簡單工廠模式
  • 設計模式之工廠方法模式

 

若是對什麼是線程、什麼是進程仍存有疑惑,請先Google之,由於這兩個概念不在本文的範圍以內。一篇淺顯易懂的介紹進程和線程的文章:http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.htmljava

 

1、什麼是線程

1. 線程是什麼程序員

線程是程序執行的一條路徑,一個進程中能夠包含多條線程,那麼這個進程就是多線程的。面試

多線程:指的是這個程序(一個進程)運行時產生了不止一個線程。windows

多線程併發執行能夠提升程序的效率,能夠同時完成多項工做。設計模式

 

好比我打開360,點擊「查殺修復」、「電腦清理」和「優化加速」,以下圖。安全

這3個都在運行,這就是多線程。若是是單線程,在運行「查殺修復」時,「電腦清理」和「優化加速」都得等着。等「查殺修復」運行完了,才能運行下一個。服務器

 

多線程的效率爲何會高?見:https://www.cnblogs.com/shann/p/6851889.html多線程

 

2. 多線程的應用場景併發

  • 紅蜘蛛同時共享屏幕給多個電腦
  • 迅雷就是多條線程一塊兒下載
  • QQ同時和多我的一塊兒視頻
  • 服務器同時處理多個客戶端請求

 

2、多線程並行和併發的區別

  • 並行就是兩個任務同時運行,就是甲任務進行的同時,乙任務也在進行(須要多核CPU)
  • 併發是指兩個任務都請求執行,而處理器只能接受一個任務,就把兩個任務安排輪流執行,因爲時間間隔較短,令人感受兩個任務都在運行。
    • 好比我跟兩個網友聊天,左手操做一個電腦和甲聊天,同時右手用另外一臺電腦和乙聊天,這就叫並行。
    • 若是用一臺電腦我先給甲發個消息,而後馬上再給乙發消息,而後再跟甲聊,再跟乙聊,這就叫併發。

Java程序運行原理:

  Java命令會啓動java虛擬機,啓動JVM,等於啓動了一個應用程序,也就是啓動了一個進程。該進程會自動啓動一個「主線程」,而後主線程去調用某個類的 main 方法。

JVM的啓動是多線程的

  JVM啓動至少啓動了垃圾回收線程和主線程,因此JVM是多線程的。

Demo1_Thread.java

package com.wisedu.thread;

/**
 * Created by jkzhao on 1/22/18.
 */
public class Demo1_Thread {

    /**
     * @param args
     * 證實jvm是多線程的
     */
    public static void main(String[] args) {
        for(int i = 0; i < 1000000; i++) {
            new Demo(); //創造些垃圾出來,匿名對象就是垃圾
        }

        for(int i = 0; i < 100000; i++) { //看看主線程和垃圾回收線程是否會在cpu上切換
            System.out.println("我是主線程的執行代碼");
        }
    }

}

class Demo {

    @Override
    public void finalize() { //Object類中的方法
        System.out.println("垃圾被清掃了");
    }

}

「我是主線程的執行代碼」和「垃圾被清掃了」這兩句話會交替打印,說明是間隔執行的,是多線程的。若是不是多線程的,必定是所有打印完了「垃圾被清掃了」,纔會去打印「我是主線程的執行代碼」。

 

3、多線程程序實現方式

1. 繼承Thread

【步驟】:

  1. 定義類繼承Thread
  2. 重寫run方法
  3. 把新線程要作的事情寫在run方法中
  4. 建立線程對象
  5. 開啓新線程,內部會自動執行run方法

【示例】:Demo2_Thread.java

package com.wisedu.thread;

/**
 * Created by jkzhao on 1/22/18.
 */
public class Demo2_Thread {

    /**
     * @param args
     */
    public static void main(String[] args) { //執行main方法,發現有b有a間隔輸出了
        MyThread mt = new MyThread();		//4,建立Thread類的子類對象
        mt.start();				//5,開啓線程 注意這裏不是調用run方法

        for(int i = 0; i < 1000; i++) { //爲了看出是多線程執行,在主方法裏也寫點內容
            System.out.println("bb");
        }
    }

}

class MyThread extends Thread {				//1,繼承Thread
    public void run() {						//2,重寫run方法
        for(int i = 0; i < 1000; i++) {		//3,將要執行的代碼寫在run方法中
            System.out.println("aaaaaaaaaaaa");
        }
    }
}

  

2. 實現Runnable

【步驟】:

  1. 定義類實現Runnable接口
  2. 實現run方法
  3. 把新線程要作的事情寫在run方法中
  4. 建立自定義的Runnable的子類對象
  5. 建立Thread對象,傳入Runnable
  6. 調用start()開啓新線程,內部會自動調用Runnable的run()方法

【示例】:Demo3_Thread.java

package com.wisedu.thread;

/**
 * Created by jkzhao on 1/22/18.
 */
public class Demo3_Thread {

    /**
     * @param args
     */
    public static void main(String[] args) {
        MyRunnable mr = new MyRunnable();	//4,建立Runnable的子類對象
        //Runnable target = mr;	假如mr = 0x0011 //父類引用指向子類對象,編譯看的是父類,運行看的是子類
        Thread t = new Thread(mr);			//5,將其看成參數傳遞給Thread的構造函數
        t.start();							//6,開啓線程 Thread類中才有start方法

        for(int i = 0; i < 1000; i++) {
            System.out.println("bb");
        }
    }

}

class MyRunnable implements Runnable {		//1,定義一個類實現Runnable

    @Override
    public void run() {						//2,重寫run方法
        for(int i = 0; i < 1000; i++) {		//3,將要執行的代碼寫在run方法中
            System.out.println("aaaaaaaaaaaa");
        }
    }

}

  

3. 實現Runnable的原理

查看源碼,能夠發現:

  1. 看Thread類的構造方法,傳遞了Runnable接口的引用
  2. 經過init()方法找到傳遞的target給成員變量的target賦值
  3. 查看run方法,發現run方法中有判斷,若是target不爲null就會調用Runnable接口子類對象的run方法

 

4. 兩種實現多線程方式的區別

查看源碼的區別:

  • 繼承Thread:因爲子類重寫了Thread類的run(),當調用start()時,能夠找子類的run()方法(調用start方法時,找子類的run方法,這是底層JVM幫咱們完成的)
  • 實現Runnable:構造方法中傳入了Runnable的引用,成員變量記住了它,start()方法調用run()方式時內部判斷成員變量Runnable的引用是否爲空,不爲空編譯時看的是Runnable的run(),運行時執行的是子類的run()方法。

 

繼承Thread:

  • 好處是:能夠直接使用Thread類中的方法,代碼簡單
  • 弊端是:若是已經有了父類,就不能用這種方法

實現Runnable:

  • 好處是:即便本身定義的線程類有了父類也不要緊,由於有了父類也能夠實現接口,並且接口是能夠多實現的
  • 弊端是:不能直接使用Thread中的方法,須要先獲取到線程對象後,才能獲得Thread的方法,代碼複雜。

 

5. 匿名內部類實現線程的兩種方式

好處就是不用找一個類去繼承Thread類或者實現Runnable接口了。

【示例】:Demo4_Thread.java

package com.wisedu.thread;

/**
 * Created by jkzhao on 1/22/18.
 */
public class Demo4_Thread {

    /**
     * @param args
     */
    public static void main(String[] args) {
        new Thread() {										//1,繼承Thread類
            public void run() {								//2,重寫run方法
                for(int i = 0; i < 1000; i++) {				//3,將要執行的代碼寫在run方法中
                    System.out.println("aaaaaaaaaaaaaa");
                }
            }
        }.start();											//4,開啓線程

        new Thread(new Runnable() {							//1,將Runnable的子類對象傳遞給Thread的構造方法
            public void run() {								//2,重寫run方法
                for(int i = 0; i < 1000; i++) {				//3,將要執行的代碼寫在run方法中
                    System.out.println("bb");
                }
            }
        }).start();											//4,開啓線程
    }

}

 

4、多線程中的一些方法

1. 獲取名字和設置名字

(1)獲取名字

經過getName()方法獲取線程對象的名字

(2)設置名字

經過構造函數能夠傳入String類型的名字

經過setName(String )方法能夠設置線程對象的名字

【示例】:Demo1_Name.java

package com.wisedu.threadmethod;

/**
 * Created by jkzhao on 1/22/18.
 */
public class Demo1_Name {

    /**
     * @param args
     */
    public static void main(String[] args) {
        //demo1();
        Thread t1 = new Thread() {
            public void run() {
                //this.setName("張三");
                System.out.println(this.getName() + "....aaaaaaaaaaaaa"); //this就至關於這個匿名內部類對象
            }
        };

        Thread t2 = new Thread() {
            public void run() {
                //this.setName("李四");
                System.out.println(this.getName() + "....bb");
            }
        };

        t1.setName("張三");
        t2.setName("李四");
        t1.start();
        t2.start();
    }

    public static void demo1() {
        new Thread("芙蓉姐姐") {	//經過構造方法給name賦值
            public void run() {
                System.out.println(this.getName() + "....aaaaaaaaa");
            }
        }.start();

        new Thread("鳳姐") {
            public void run() {
                System.out.println(this.getName() + "....bb");
            }
        }.start();
    }

}

  

2. 獲取當前線程的對象

Thread.currentThread(),主線程也能夠獲取。

【示例】:Demo2_CurrentThread.java

package com.wisedu.threadmethod;

/**
 * Created by jkzhao on 1/22/18.
 */
public class Demo2_CurrentThread {

    /**
     * @param args
     */
    public static void main(String[] args) {
        new Thread() {
            public void run() {
                System.out.println(getName() + "....aaaaaa");
            }
        }.start();

        new Thread(new Runnable() {
            public void run() {
                //Thread.currentThread()獲取當前正在執行的線程
                System.out.println(Thread.currentThread().getName() + "...bb");
            }
        }).start();

        Thread.currentThread().setName("我是主線程");
        System.out.println(Thread.currentThread().getName());
    }

}

  

3. 休眠線程

Thread.sleep(毫秒,納秒),控制當前線程休眠若干毫秒,windows不太支持納秒值。 1秒 = 1000毫秒,1秒 = 1000 * 1000 * 1000m納秒。

【示例】:Demo3_Sleep.java

package com.wisedu.threadmethod;

/**
 * Created by jkzhao on 1/22/18.
 */
public class Demo3_Sleep {

    /**
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {
        //demo1();
        new Thread() {
            public void run() {
                for(int i = 0; i < 10; i++) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(getName() + "...aaaaaaaaaa");
                }
            }
        }.start();

        new Thread() {
            public void run() {
                for(int i = 0; i < 10; i++) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(getName() + "...bb");
                }
            }
        }.start();
    }

    public static void demo1() throws InterruptedException {
        for(int i = 20; i >= 0; i--) {
            Thread.sleep(1000); //毫秒
            System.out.println("倒計時第" +i + "秒");
        }
    }

}

  

4. 守護線程

setDaemon(),設置一個線程爲守護線程,該線程不會單獨執行,當其餘非守護線程都執行結束後,自動退出。

就像下象棋同樣,非守護線程至關於帥,守護線程至關於車馬相士。帥死掉了,其餘的車馬相士也隨之中止,車馬相士自殺有個緩衝時間。

再好比,用QQ向別人傳文件的時候,qq的主界面用的是非守護線程作的,而傳輸的窗口用的守護線程,主界面一關掉,傳輸並非立馬就中止的,它還會再發幾個數據包,由於它要等待接收退出的命令。

【示例】:Demo4_Daemon.java

package com.wisedu.threadmethod;

/**
 * Created by jkzhao on 1/22/18.
 */
public class Demo4_Daemon {

    /**
     * @param args
     * 守護線程
     */
    public static void main(String[] args) {
        Thread t1 = new Thread() { //帥
            public void run() {
                for(int i = 0; i < 2; i++) {
                    System.out.println(getName() + "...aaaaaaaaaaaaaaaaaaaa");
                }
            }
        };

        Thread t2 = new Thread() { //車馬相士
            public void run() {
                for(int i = 0; i < 50; i++) {
                    System.out.println(getName() + "...bb");
                }
            }
        };

        t2.setDaemon(true); //當傳入true,意味着設置爲守護線程

        t1.start();
        t2.start();
    }

}

  

5. 加入線程

  • join(),當前線程暫停,等待指定的線程執行結束後,當前線程再繼續。就至關於插隊。
  • join(int),能夠等待指定的毫秒以後繼續

【示例】:Demo5_Join.java

package com.wisedu.threadmethod;

/**
 * Created by jkzhao on 1/22/18.
 */
public class Demo5_Join {

    /**
     * @param args
     * join(), 當前線程暫停, 等待指定的線程執行結束後, 當前線程再繼續
     */
    public static void main(String[] args) {
        final Thread t1 = new Thread() {
            public void run() {
                for(int i = 0; i < 10; i++) {
                    System.out.println(getName() + "...aaaaaaaaaaaaa");
                }
            }
        };

        Thread t2 = new Thread() {
            public void run() {
                for(int i = 0; i < 10; i++) {
                    if(i == 2) {
                        try {
                            //t1.join();  //插隊線程執行完後,當前線程才能執行。這裏匿名內部類在使用它所在方法中的局部變量的時候,該變量必須用final修飾
                            t1.join(1);	  //插隊指定的時間,過了指定時間後,兩條線程交替執行
                        } catch (InterruptedException e) { //插隊過來,當前線程出現中斷異常
                            e.printStackTrace();
                        }
                    }
                    System.out.println(getName() + "...bb");
                }
            }
        };

        t1.start();
        t2.start();
    }

}

  

6. 禮讓線程

yield讓出CPU,可是這個yield實現的效果很差,會達不到這個效果。

【示例】:Demo6_Yield.java

package com.wisedu.threadmethod;

/**
 * Created by jkzhao on 1/22/18.
 */
public class Demo6_Yield {

    /**
     * yield讓出cpu,禮讓線程
     */
    public static void main(String[] args) {
        new MyThread().start();
        new MyThread().start();
    }

}

class MyThread extends Thread {
    public void run() {
        for(int i = 1; i <= 1000; i++) {
            System.out.println(getName() + "..." + i);
            if(i % 10 == 0) {  //10的倍數
                Thread.yield();	 //讓出CPU
            }
        }
    }
}

  

7. 設置線程優先級

setPriority() 設置線程優先級

【示例】:Demo7_Priority.java

package com.wisedu.threadmethod;

/**
 * Created by jkzhao on 1/22/18.
 */
public class Demo7_Priority {

    /**
     * @param args
     */
    public static void main(String[] args) {
        Thread t1 = new Thread(){
            public void run() {
                for(int i = 0; i < 100; i++) {
                    System.out.println(getName() + "...aaaaaaaaa" );
                }
            }
        };

        Thread t2 = new Thread(){
            public void run() {
                for(int i = 0; i < 100; i++) {
                    System.out.println(getName() + "...bb" );
                }
            }
        };

        //t1.setPriority(10);
        //t2.setPriority(1);    //直接給值也能夠,可是不能超過下面兩個常量

        t1.setPriority(Thread.MIN_PRIORITY);		//設置最小的線程優先級
        t2.setPriority(Thread.MAX_PRIORITY);		//設置最大的線程優先級

        t1.start();
        t2.start();
    }

}

  

Thread.interrupted()

待補充。。。

  

5、多線程之線程同步

1. 什麼狀況下須要同步

當多線程併發,有多段代碼同時執行時,咱們但願某一段代碼執行的過程當中CPU不要切換到其餘線程工做,等當前這段代碼執行完再切換到其它線程,這時就須要同步。

若是兩段代碼是同步的,那麼同一個時間只能執行一段,在一段代碼沒執行結束以前,不會執行另一段代碼。

舉個例子:賺了3000塊錢存銀行,而後你有一存款折和一張銀行卡,都是對應於同一個帳戶,你拿這個摺子去櫃檯取錢去,櫃檯服務人員問你取多少錢,你說2000。這個時候服務人員得把2000輸入電腦,電腦會去檢查你如今有沒有2000塊錢。一查發現你有多餘2000塊的存款,這時候就把錢吐給你。而後把你帳戶上的錢減掉2000。咱們假設檢查完你帳戶發現錢夠,正要把錢出給你的時候,你老婆拿着你的銀行卡在取款機上取錢,取2000,取款機一檢查發現你的帳戶裏有超過2000的存款(此時你的帳戶上錢還沒減),而後取款機就把這2000吐給你老婆了,而後取款機把你的帳戶的餘額更新爲1000。而後櫃檯那邊繼續執行,又給了你2000,把你的帳戶餘額更新爲1000。

這顯然對銀行不公平,你和你老婆比如兩個線程,這兩個線程在執行一個對帳戶取款的方法的過程之中,可是大家兩個線程同時訪問同一個帳戶(同一個資源),這種狀況下線程順序協調很差的話,很容易出現先後數據不一致的狀況。咱們對多個線程訪問同一個資源的時候,咱們對這多個線程進行協調的這個東西叫作線程同步。

怎麼解決這個問題呢?

當某個線程在調用取款方法的時候,這個帳戶歸這個線程獨佔,其餘線程不能訪問。

代碼模擬:

public class TestSync implements Runnable {
    Timer timer = new Timer();

    public static void main(String[] args) {
        TestSync test = new TestSync();
        Thread t1 = new Thread(test);
        Thread t2 = new Thread(test);
        t1.setName("t1");
        t2.setName("t2");
        t1.start();
        t2.start();

    }

    public void run(){
        timer.add(Thread.currentThread().getName());
    }
}

class Timer{
    private int num = 0;

    public void add(String name){ //用來作計數用的
        num ++;
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {}

        System.out.println(name+", 你是第"+num+"個使用timer的線程");
    }
}

運行結果:

t1, 你是第2個使用timer的線程
t2, 你是第2個使用timer的線程

Process finished with exit code 0

問題出在執行 num ++ 和 打印語句 之間被打斷了(由於第一個線程睡眠了),第二個線程調add方法去修改同一個對象的成員變量num的值,接着第二個線程睡眠,第一個線程醒了過來,打印num的值爲2。而後第2個線程醒過來打印num的值爲2。

num ++ 和 打印語句 應做爲原子操做,不可再分。

怎麼解決呢?鎖住當前對象。

class Timer{
    private int num = 0;

    public void add(String name){ //用來作計數用的
        synchronized (this) { //鎖定當前對象,對於下面的語句,一個線程在執行的時候不會被另外的線程打斷。這個對象裏面的成員變量num也被鎖定了。這叫互斥鎖
            num++;
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
            }

            System.out.println(name + ", 你是第" + num + "個使用timer的線程");
        }
    }
}

還有一種更爲簡潔的寫法:

class Timer{
    private int num = 0;

    public synchronized void add(String name){ //在執行該方法時鎖定當前對象,也就是timer對象
        //synchronized (this) { //鎖定當前對象,對於下面的語句,一個線程在執行的時候不會被另外的線程打斷。這個對象裏面的成員變量num也被鎖定了。這叫互斥鎖
            num++;
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
            }

            System.out.println(name + ", 你是第" + num + "個使用timer的線程");
        //}
    }
}

 

2. 同步的機制:每一個對象都有的方法

synchronized, wait, notify 是任何對象都具備的同步工具。他們是應用於同步問題的人工線程調度工具。講其本質,首先就要明確monitor的概念,Java中的每一個對象都有一個監視器,來監測併發代碼的重入。在非多線程編碼時該監視器不發揮做用,反之若是在synchronized 範圍內,監視器發揮做用。

wait/notify必須存在於synchronized塊中。而且,這三個關鍵字針對的是同一個監視器(某對象的監視器)。這意味着wait以後,其餘線程能夠進入同步塊執行。

當某代碼並不持有監視器的使用權時(如圖中5的狀態,即脫離同步塊)去wait或notify,會拋出java.lang.IllegalMonitorStateException。也包括在synchronized塊中去調用另外一個對象的wait/notify,由於不一樣對象的監視器不一樣,一樣會拋出此異常。

 

用法以下:

  • synchronized單獨使用:
    • 同步代碼塊:以下,在多線程環境下,synchronized塊中的方法獲取了lock實例的monitor,若是實例相同,那麼只有一個線程能執行該塊內容
    • public class Thread1 implements Runnable {
         Object lock;
         public void run() {  
             synchronized(lock){
               ..do something
             }
         }
      }
    • 直接用於方法: 至關於上面代碼中用lock來鎖定的效果,實際獲取的是Thread1類的monitor。更進一步,若是修飾的是static方法,則鎖定該類全部實例。
    • public class Thread1 implements Runnable {
         public synchronized void run() {  
              ..do something
         }
      }
  • synchronized, wait, notify結合:典型場景生產者消費者問題
    • /**
         * 生產者生產出來的產品交給店員
         */
        public synchronized void produce()
        {
            if(this.product >= MAX_PRODUCT)
            {
                try
                {
                    wait();  
                    System.out.println("產品已滿,請稍候再生產");
                }
                catch(InterruptedException e)
                {
                    e.printStackTrace();
                }
                return;
            }
      
            this.product++;
            System.out.println("生產者生產第" + this.product + "個產品.");
            notifyAll();   //通知等待區的消費者能夠取出產品了
        }
      
        /**
         * 消費者從店員取產品
         */
        public synchronized void consume()
        {
            if(this.product <= MIN_PRODUCT)
            {
                try 
                {
                    wait(); 
                    System.out.println("缺貨,稍候再取");
                } 
                catch (InterruptedException e) 
                {
                    e.printStackTrace();
                }
                return;
            }
      
            System.out.println("消費者取走了第" + this.product + "個產品.");
            this.product--;
            notifyAll();   //通知等待去的生產者能夠生產產品了
        }

下面將進行更詳細的講解。

 

3. 同步代碼塊

使用synchronized關鍵字加上一個鎖對象來定義一段代碼,這就叫同步代碼塊。

多個同步代碼塊若是使用相同的鎖對象,那麼它們就是同步的。

【示例】:Demo1_Synchronized.java

package com.wisedu.sync;

/**
 * Created by jkzhao on 1/22/18.
 */
public class Demo1_Synchronized {

    /**
     * @param args
     * 同步代碼塊
     */
    public static void main(String[] args) {
        final Printer p = new Printer();

        new Thread() {
            public void run() {
                while(true) {
                    p.print1();
                }
            }
        }.start();

        new Thread() {
            public void run() {
                while(true) {
                    p.print2();
                }
            }
        }.start();
    }

}

class Printer {
    Demo d = new Demo();
    public void print1() {
        //synchronized(new Demo()) {							//同步代碼塊,鎖機制,鎖是對象來作的,鎖對象能夠是任意的,直接建立個Object對象也能夠
        synchronized(d) {
            System.out.print("黑");
            System.out.print("馬");
            System.out.print("程");
            System.out.print("序");
            System.out.print("員");
            System.out.print("\r\n");
        }
    }

    public void print2() {
        //synchronized(new Demo()) {							//鎖對象不能用匿名對象,由於匿名對象不是同一個對象
        synchronized(d) {
            System.out.print("傳");
            System.out.print("智");
            System.out.print("播");
            System.out.print("客");
            System.out.print("\r\n");
        }
    }
}

class Demo{}

  

4. 多線程同步方法

使用synchronized關鍵字修飾一個方法,該方法中全部的代碼都是同步的。

【示例】:Demo2_Synchronized.java

package com.wisedu.sync;

/**
 * Created by jkzhao on 1/22/18.
 */
public class Demo2_Synchronized {

    /**
     * @param args
     * 同步代碼塊
     */
    public static void main(String[] args) {
        final Printer2 p = new Printer2();

        new Thread() {
            public void run() {
                while(true) {
                    p.print1();
                    //p.print3();
                }
            }
        }.start();

        new Thread() {
            public void run() {
                while(true) {
                    p.print2();
                    //p.print4();
                }
            }
        }.start();
    }

}

class Printer2 {
    //非靜態的同步方法的鎖對象是神馬?
    //答:非靜態的同步方法的鎖對象是this
    public synchronized void print1() {		//同步方法只須要在方法上加synchronized關鍵字便可
        System.out.print("黑");
        System.out.print("馬");
        System.out.print("程");
        System.out.print("序");
        System.out.print("員");
        System.out.print("\r\n");
    }

    public void print2() {
        synchronized(this) {
            System.out.print("傳");
            System.out.print("智");
            System.out.print("播");
            System.out.print("客");
            System.out.print("\r\n");
        }
    }

    //靜態的同步方法的鎖對象是什麼?
    //答:是該類的字節碼對象
    public static synchronized void print3() {		//同步方法只須要在方法上加synchronized關鍵字便可
        System.out.print("黑");
        System.out.print("馬");
        System.out.print("程");
        System.out.print("序");
        System.out.print("員");
        System.out.print("\r\n");
    }

    public static void print4() {
        synchronized(Printer2.class) {
            System.out.print("傳");
            System.out.print("智");
            System.out.print("播");
            System.out.print("客");
            System.out.print("\r\n");
        }
    }
}

  

6、線程安全

多線程併發操做同一數據時,就有可能出現線程安全問題。

使用同步技術能夠解決這種問題,把操做數據的代碼進行同步,不要多個線程一塊兒操做。

【示例】:鐵路售票,一共100張,經過四個窗口賣完(四個窗口也就是四個線程)。

Demo3_Ticket.java --繼承Thread類

package com.wisedu.sync;

/**
 * Created by jkzhao on 1/22/18.
 */
public class Demo3_Ticket {

    /**
     * 需求:鐵路售票,一共100張,經過四個窗口賣完.
     */
    public static void main(String[] args) {
        new Ticket().start();
        new Ticket().start();
        new Ticket().start();
        new Ticket().start();
    }

}

class Ticket extends Thread {
    private static int ticket = 100; //全部對象共享這100張票
    //private static Object obj = new Object();		//若是用 引用數據類型的成員變量 看成鎖對象,必須是靜態的
    public void run() {
        while(true) {
            synchronized(Ticket.class) { //obj
                if(ticket == 0) {
                    break;
                }
                try { //模擬這邊可能有n多行代碼執行
                    Thread.sleep(10);				//10毫秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(getName() + "...這是第" + ticket-- + "號票");
            }
        }
    }
}

Demo4_Ticket.java --實現Runnable接口

package com.wisedu.sync;

/**
 * Created by jkzhao on 1/22/18.
 */
public class Demo4_Ticket {

    /**
     * @param args
     * 火車站賣票的例子用實現Runnable接口
     */
    public static void main(String[] args) {
        MyTicket mt = new MyTicket();
        new Thread(mt).start();
        new Thread(mt).start();
        new Thread(mt).start();
        new Thread(mt).start();

		/*Thread t1 = new Thread(mt);				//屢次啓動一個線程是非法的
		t1.start();
		t1.start();
		t1.start();
		t1.start();*/
    }

}

class MyTicket implements Runnable {
    private int tickets = 1000; //由於咱們不須要建立4個對象,因此不須要定義成靜態的
    @Override
    public void run() {
        while(true) {
            synchronized(Ticket.class) { //能夠用this,由於只建立了一個對象
                if(tickets == 0) {
                    break;
                }
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "...這是第" + tickets-- + "號票");
            }
        }
    }
}

  

7、多線程的死鎖

多線程同步的時候,若是代碼嵌套,使用相同鎖,就有可能出現死鎖。因此儘可能不要嵌套使用。

好比,桌子上擺了一桌滿漢全席,桌子周圍坐着一羣哲學家,而後給哲學家發筷子,一人一根,而後哲學家就用本身的三寸不爛之舌說服別人把筷子給本身。互相去說服,最後可能致使誰都沒有說服誰,致使成了死鎖,所有活活餓死在滿漢全席的邊上。

【示例】:Demo5_DeadLock.java

package com.wisedu.sync;

/**
 * Created by jkzhao on 1/22/18.
 */
public class Demo5_DeadLock {

    /**
     * @param args
     */
    private static String s1 = "筷子左"; //定義兩個字符串當作鎖
    private static String s2 = "筷子右";

    public static void main(String[] args) {
        new Thread() {
            public void run() {
                while(true) {
                    synchronized(s1) {
                        System.out.println(getName() + "...獲取" + s1 + "等待" + s2);
                        synchronized(s2) {
                            System.out.println(getName() + "...拿到" + s2 + "開吃");
                        }
                    }
                }
            }
        }.start();

        new Thread() {
            public void run() {
                while(true) {
                    synchronized(s2) {
                        System.out.println(getName() + "...獲取" + s2 + "等待" + s1);
                        synchronized(s1) {
                            System.out.println(getName() + "...拿到" + s1 + "開吃");
                        }
                    }
                }
            }
        }.start();
    }
}

執行結果:

Thread-0...獲取筷子左等待筷子右
Thread-0...拿到筷子右開吃
Thread-0...獲取筷子左等待筷子右
Thread-0...拿到筷子右開吃
Thread-0...獲取筷子左等待筷子右
Thread-0...拿到筷子右開吃
Thread-0...獲取筷子左等待筷子右
Thread-1...獲取筷子右等待筷子左

此時已經產生死鎖了,程序並無中止。。。

 

8、線程安全的類

Vertor、StringBuffer、Hashtable、Collections.synchronized(xxx)

1. Vector是線程安全的

查看源碼java.util.Vector.java,找到add方法

    public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }

  

2. ArrayList是線程不安全的

查看源碼,java.util.ArrayList.java,找到add方法

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

  

3. StringBuffer是線程安全的,StringBuilder是線程不安全的

查看源碼,java.lang.StringBuffer,java.lang.StringBuilder,找到append方法

    public synchronized StringBuffer append(Object obj) {
        super.append(String.valueOf(obj));
        return this;
    }

    public synchronized StringBuffer append(String str) {
        super.append(str);
        return this;
    }
...
    public StringBuilder append(Object obj) {
        return append(String.valueOf(obj));
    }

    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }
    ...

 

4. Hashtable是線程安全的,HashMap是線程不安全的

查看源碼,java.lang.Hashtable,java.lang.HashMap,找到append方法

java.lang.Hashtable

    public synchronized V put(K key, V value) {
        // Make sure the value is not null
        if (value == null) {
            throw new NullPointerException();
        }
        ...

java.lang.HashMap

    public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        ...

  

5. Collections.synchronized(xxx)

能夠將線程不安全的線程變成線程安全的。能夠到API文檔裏搜索下Collections這個類,裏面有方法synchronized(xxx)

調用上面的這些方法就能夠將這幾種集合變成同步的。

 

9、多線程設計模式之單例設計模式

單例設計模式:保證類在內存中只有一個對象

如何保證類在內存中只有一個對象?

  1. 控制類的建立,不讓其餘類來建立本類的對象。private
  2. 在本類中定義一個本類的對象。Singleton s;
  3. 提供公共的訪問方式,public static Singleton getInstance(){return s;}

 

1. 單例模式兩種寫法

(1)餓漢式,開發使用這種方式

(2)懶漢式,面試寫這種方式。多線程問題?

Demo1_Singleton.java

package com.wisedu.thread2;

/**
 * Created by jkzhao on 1/23/18.
 */
public class Demo1_Singleton {

    /**
     * @param args
     * * 單例設計模式:保證類在內存中只有一個對象。
     */
    public static void main(String[] args) {

        Singleton s1 = Singleton.s;				//成員變量被私有,不能經過類名.調用
        //Singleton.s = null;    //修改爲員變量s
        Singleton s2 = Singleton.s;

        System.out.println(s1 == s2);

	/*	Singleton s1 = Singleton.getInstance();
		Singleton s2 = Singleton.getInstance();

		System.out.println(s1 == s2);*/
    }

}

/*
 * 餓漢式
 * 上來就是new對象,因此稱爲餓漢式 private static Singleton s = new Singleton();
 * /
/*class Singleton {
	//1,私有構造方法,其餘類不能訪問該構造方法了
	private Singleton(){}
	//2,建立本類對象
	private static Singleton s = new Singleton(); //私有化是爲了防止被修改
	//3,對外提供公共的訪問方法
	public static Singleton getInstance() {	 //因爲上面把成員變量s私有化,這裏提供get方法獲取實例
		return s;
	}
}*/
/*
 * 懶漢式,單例的延遲加載模式
 */
/*class Singleton {
	//1,私有構造方法,其餘類不能訪問該構造方法了
	private Singleton(){}
	//2,聲明一個引用
	private static Singleton s ;
	//3,對外提供公共的訪問方法
	public static Singleton getInstance() {				//獲取實例
		if(s == null) {
			//假設線程1剛進來,結果CPU執行權被別的線程搶走了,那麼線程1等待,線程2剛進來,CPU正好也切換去執行其餘線程了,線程2等待。
			//而後線程1搶回了CPU,因而執行下面語句建立了一個對象;線程2也搶回了CPU,因而執行下面語句又建立了一個對象。這就建立了兩個對象
			//因此開發的時候不用懶漢式,面試的時候用這個懶漢式,由於面試的時候會讓寫一個單例的延遲加載模式
			s = new Singleton();
		}

		return s;
	}
}*/

/*
 * 餓漢式和懶漢式的區別
 * 1,餓漢式是空間換時間,懶漢式是時間換空間
 * 2,在多線程訪問時,餓漢式不會建立多個對象,而懶漢式有可能會建立多個對象
 */

/**
 * 第三種,沒有名字
 */
class Singleton {
    //1,私有構造方法,其餘類不能訪問該構造方法了
    private Singleton(){}
    //2,聲明一個引用
    public static final Singleton s = new Singleton();

}

  

2. 單例設計模式應用場景之Runtime類

餓漢式

Demo2_Runtime.java

package com.wisedu.thread2;

/**
 * Created by jkzhao on 1/23/18.
 */
import java.io.IOException;

public class Demo2_Runtime {

    /**
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        Runtime r = Runtime.getRuntime();			//獲取運行時對象
        //r.exec("shutdown -s -t 300"); //在單獨的進程中執行指定的字符串命令
        r.exec("shutdown -a");
        //爲何使用單例設計模式?
        // 第一條代碼設置了5min後關機,第二條代碼取消關機,修改的是第一條語句修改後的結果
    }

}

  

3. 單例設計模式應用場景之Timer類

Timer類,計時器。一種工具,線程用其安排之後在後臺線程中執行的任務,能夠安排任務執行一次,或者按期重複執行。

與每一個Timer對象相對應的是單個後臺線程,用於順序地執行全部計時器任務。

Demo3_Timer.java

package com.wisedu.thread2;

/**
 * Created by jkzhao on 1/23/18.
 */
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class Demo3_Timer {

    /**
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {
        Timer t = new Timer();
        //在指定時間安排指定任務
        //第一個參數,是安排的任務,第二個參數是執行的時間,第三個參數是過多長時間再重複執行
        t.schedule(new MyTimerTask(), new Date(118, 0, 22, 16, 39, 51),3000); //year是要-1900   最後的3000是毫秒值

        while(true) {
            Thread.sleep(1000);
            System.out.println(new Date()); //隔1s打一次時間,看看到指定時間是否執行任務
        }
    }

}

class MyTimerTask extends TimerTask {

    @Override
    public void run() {
        System.out.println("起牀背英語單詞");
    }
}

  

10、線程通訊

1. 何時須要通訊

多個線程併發執行時,在默認狀況下CPU是隨機切換線程的

若是咱們但願它們有規律的執行,就可使用通訊,例如每一個線程執行一次打印

 

2. 兩個線程間通訊

  • 若是須要線程等待,就調用wait()
  • 若是但願喚醒等待的線程,就調用notify():喚醒此對象監聽器上等待的單個線程
  • 這兩個方法必須在同步代碼中執行,而且使用同步鎖對象來調用

Demo1_Notify.java

package com.wisedu.thread_communication;

/**
 * Created by jkzhao on 1/24/18.
 */
public class Demo1_Notify {

    /**
     * @param args
     * 等待喚醒機制
     */
    public static void main(String[] args) {
        final Printer p = new Printer();

        new Thread() {
            public void run() {
                while(true) {
                    try {
                        p.print1();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();

        new Thread() {
            public void run() {
                while(true) {
                    try {
                        p.print2();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }

}

//等待喚醒機制
class Printer {
    private int flag = 1;
    public void print1() throws InterruptedException {
        synchronized(this) {
            if(flag != 1) {
                this.wait();					//當前線程等待,沒有人喚醒它的話,就一直在等待
            }
            System.out.print("黑");
            System.out.print("馬");
            System.out.print("程");
            System.out.print("序");
            System.out.print("員");
            System.out.print("\r\n");
            flag = 2;
            this.notify();						//隨機喚醒單個等待的線程(假如此時沒有等待的線程,隨機喚醒下也是能夠的)
        }
    }

    public void print2() throws InterruptedException {
        synchronized(this) {
            if(flag != 2) {
                this.wait();
            }
            System.out.print("傳");
            System.out.print("智");
            System.out.print("播");
            System.out.print("客");
            System.out.print("\r\n");
            flag = 1;
            this.notify();
        }
    }
}

  

3. 三個或三個以上間的線程通訊

多個線程通訊的問題

  • notify()方法是隨機喚醒此對象監聽器上等待的一個線程
  • notifyAll()方法是喚醒此對象監聽器上等待的全部線程
  • JDK5以前沒法喚醒指定的一個線程
  • 若是多個線程之間通訊,須要使用notifyAll()通知全部線程,用while來反覆判斷條件

Demo2_NotifyAll.java

package com.wisedu.thread_communication;

/**
 * Created by jkzhao on 1/24/18.
 */
public class Demo2_NotifyAll {

    /**
     * 循環打印:黑馬程序員、傳智播客、itheima
     * @param args
     */
    public static void main(String[] args) {
        final Printer2 p = new Printer2();
        new Thread() {
            public void run() {
                while(true) {
                    try {
                        p.print1();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();

        new Thread() {
            public void run() {
                while(true) {
                    try {
                        p.print2();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();

        new Thread() {
            public void run() {
                while(true) {
                    try {
                        p.print3();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }

}
/* 1,在同步代碼塊中,用哪一個對象鎖,就用哪一個對象去調用wait方法,好比下面用的是this
 * 2,爲何wait方法和notify方法定義在Object這類中?
 * 	 由於鎖對象能夠是任意對象,Object是全部類的基類,因此wait方法和notify方法須要定義在Object這個類中
 * 3,sleep方法和wait方法的區別?
 *   a,sleep方法必須傳入參數,參數就是時間,時間到了自動醒來
 *     wait方法能夠傳入參數也能夠不傳入參數,傳入參數就是在參數的時間結束後等待,不傳入參數就是直接等待
 *   b,sleep方法在同步函數或同步代碼塊中,不釋放鎖,睡着了也抱着鎖睡(就是在sleep的時間內,也佔着CPU)
 * 	   wait方法在同步函數或者同步代碼塊中,釋放鎖(就是該線程調用wait方法等待了,把鎖釋放了,這樣CPU才能去執行其餘線程)
 */
class Printer2 {
    private int flag = 1;
    public void print1() throws InterruptedException {
        synchronized(this) {
            while(flag != 1) {
                this.wait();					//當前線程等待
            }
            System.out.print("黑");
            System.out.print("馬");
            System.out.print("程");
            System.out.print("序");
            System.out.print("員");
            System.out.print("\r\n");
            flag = 2;
            //this.notify();						//不能用這個
            this.notifyAll();
        }
    }

    public void print2() throws InterruptedException {
        synchronized(this) {
            while(flag != 2) {
                this.wait();					//線程2在此等待
            }
            System.out.print("傳");
            System.out.print("智");
            System.out.print("播");
            System.out.print("客");
            System.out.print("\r\n");
            flag = 3;
            //this.notify(); //不能用這個
            this.notifyAll();
        }
    }

    public void print3() throws InterruptedException {
        synchronized(this) {
            while(flag != 3) {
                this.wait();						//線程3在此等待,if語句是在哪裏等待,就在哪裏起來
                                                    //while循環是循環判斷,每次搶到執行權都會去判斷標記flag
            }
            System.out.print("i");
            System.out.print("t");
            System.out.print("h");
            System.out.print("e");
            System.out.print("i");
            System.out.print("m");
            System.out.print("a");
            System.out.print("\r\n");
            flag = 1;
            //this.notify(); //不能用這個
            this.notifyAll();
        }
    }
}

分析下執行過程:

  1. 極端狀況下,上來線程2搶到CPU執行權,CPU先執行線程2,此時flag=1,因此條件知足,線程2等待;
  2. 接着執行線程3,此時flag=1,因此條件知足,線程3等待;
  3. 如今活着的只有線程1,執行線程1,此時flag=1,條件不知足,因此線程1的代碼繼續往下走,開始打印,打印完將flag改成2,而後經過notifyAll()喚醒全部等待的線程,頗有可能線程1仍然有CPU執行權,因而繼續走while裏的判斷,2!=1,條件知足,線程1等待;
  4. 如今線程2和線程3都是活着的,假設先執行的線程3,2!=3,條件知足,因而線程3等待;
  5. 如今活着的只有線程2,2!=2,條件不知足,繼續往下走,因而開始打印,而後把flag改成3,而後經過notifyAll()喚醒全部等待的線程。。。

可是這不合理,也就是說無論符合不符合規則,到沒到時間,就把其它線程都叫起來。好比說園區有3個保安,一個值早上8點到下午4點的班,一個是下午4點到晚上12點,一個是晚上12點到次日早上8點的班。第一個值班後並不知道誰盯下一個班,因而就把另外兩個保安叫起來了。兩我的起來後看誰知足條件就去值班,另外一個回去睡覺。若是總這麼叫起人的話,人家就可能怒了。

這是JDK 1.5以前的解決方案,在JDK 1.5有個更好的解決方案叫互斥鎖。

 

4. JDK 1.5的新特性之互斥鎖

  • 同步
    • 使用ReentrantLock類的lock()和unlock()方法進行同步
    • lock()獲取鎖
    • unlock()釋放鎖
  • 通訊
    • 使用ReentrantLock類的newCondition()方法能夠獲取Condition對象
    • 須要等待的時候使用Condition的await()方法,喚醒的時候用signal()方法
    • 不一樣的線程使用不一樣的Condition,這樣就能區分喚醒的時候找哪一個線程了

Condition 將Object監視器方法(wait、notify和notifyAll)分解成大相徑庭的對象,以便經過將這些對象與任意Lock實現組合使用,爲每一個對象提供多個等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和語句的使用,Condition替代了Object監視器方法的使用。Condition實例實質上被綁定到一個鎖上,要爲特定 Lock 實例得到 Condition 實例,請用其 newCondition() 方法。

Demo3_ReetrantLock.java

package com.wisedu.thread_communication;

/**
 * Created by jkzhao on 1/24/18.
 */
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class Demo3_ReentrantLock {

    /**
     * @param args
     */
    public static void main(String[] args) {
        final Printer3 p = new Printer3();

        new Thread() {
            public void run() {
                while(true) {
                    try {
                        p.print1();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();

        new Thread() {
            public void run() {
                while(true) {
                    try {
                        p.print2();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();

        new Thread() {
            public void run() {
                while(true) {
                    try {
                        p.print3();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }

}

class Printer3 {
    private ReentrantLock r = new ReentrantLock();
    private Condition c1 = r.newCondition(); //建立3個監視器,一個線程上放一個監視器
    private Condition c2 = r.newCondition();
    private Condition c3 = r.newCondition();

    private int flag = 1;
    public void print1() throws InterruptedException {
        r.lock();								//獲取鎖
        if(flag != 1) {
            c1.await(); //等待
        }
        System.out.print("黑");
        System.out.print("馬");
        System.out.print("程");
        System.out.print("序");
        System.out.print("員");
        System.out.print("\r\n");
        flag = 2;
        //this.notify();
        c2.signal();  //喚醒
        r.unlock();								//釋放鎖
    }

    public void print2() throws InterruptedException {
        r.lock();
        if(flag != 2) {
            c2.await();
        }
        System.out.print("傳");
        System.out.print("智");
        System.out.print("播");
        System.out.print("客");
        System.out.print("\r\n");
        flag = 3;
        //this.notify();
        c3.signal();
        r.unlock();
    }

    public void print3() throws InterruptedException {
        r.lock();
        if(flag != 3) {
            c3.await();
        }
        System.out.print("i");
        System.out.print("t");
        System.out.print("h");
        System.out.print("e");
        System.out.print("i");
        System.out.print("m");
        System.out.print("a");
        System.out.print("\r\n");
        flag = 1;
        c1.signal();
        r.unlock();
    }
}

  

11、線程組的概述和使用

1. 線程組的概述

Java中使用ThreadGroup來表示線程組,它能夠對一批線程進行分類管理,Java容許程序直接對線程組進行控制。

默認狀況下,全部的線程都屬於主線程組。

  • public final ThreadGroup getThreadGroup() //經過線程對象獲取他所屬於的組
  • public final String getName() //經過線程組對象獲取他所在組的名字

咱們也能夠給線程設置分組

  1. ThreadGroup(String name) 建立線程組對象並給其賦值名字
  2. 建立線程對象
  3. Thread(ThreadGroup?group, Runnable?target, String?name)
  4. 設置整組的優先級或守護線程

 

2. 線程組的使用

【示例】:線程組的使用,默認是主線程組。

Demo4_ThreadGroup.java

package com.wisedu.thread_communication;

/**
 * Created by jkzhao on 1/24/18.
 */
public class Demo4_ThreadGroup {

    /**
     * @param args
     * ThreadGroup
     */
    public static void main(String[] args) {
        //demo1();
        ThreadGroup tg = new ThreadGroup("我是一個新的線程組");		//建立新的線程組
        MyRunnable mr = new MyRunnable();						//建立Runnable的子類對象

        Thread t1 = new Thread(tg, mr, "張三");					//將線程t1放在組中
        Thread t2 = new Thread(tg, mr, "李四");					//將線程t2放在組中

        System.out.println(t1.getThreadGroup().getName());		//獲取線程組的名字
        System.out.println(t2.getThreadGroup().getName());

        tg.setDaemon(true); //整個組內的線程都變成守護線程了
    }

    public static void demo1() {
        MyRunnable mr = new MyRunnable();
        Thread t1 = new Thread(mr, "張三");
        Thread t2 = new Thread(mr, "李四");

        ThreadGroup tg1 = t1.getThreadGroup();
        ThreadGroup tg2 = t2.getThreadGroup();

        System.out.println(tg1.getName());				//打印出來是main,線程默認的是在主線程組
        System.out.println(tg2.getName());
    }

}

class MyRunnable implements Runnable {

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

}

  

12、線程的六種狀態

其中,stop()方法太暴力,已過期了。在之前經過thread.stop()能夠中止一個線程,注意stop()方法是能夠由一個線程去中止另一個線程,這種方法太過暴力並且是不安全的,怎麼說呢,線程A調用線程B的stop方法去中止線程B,調用這個方法的時候線程A其實並不知道線程B執行的具體狀況,這種忽然間地中止會致使線程B的一些清理工做沒法完成,還有一個狀況是執行stop方法後線程B會立刻釋放鎖,這有可能會引起數據不一樣步問題。基於以上這些問題,stop()方法被拋棄了。

再來兩張圖:

線程狀態

線程狀態轉換

各類狀態一目瞭然,值得一提的是"blocked"這個狀態:
線程在Running的過程當中可能會遇到阻塞(Blocked)狀況

  • 調用join()和sleep()方法,sleep()時間結束或被打斷,join()中斷,IO完成都會回到Runnable狀態,等待JVM的調度。
  • 調用wait(),使該線程處於等待池(wait blocked pool),直到notify()/notifyAll(),線程被喚醒被放到鎖定池(lock blocked pool ),釋放同步鎖使線程回到可運行狀態(Runnable)
  • 對Running狀態的線程加同步鎖(Synchronized)使其進入(lock blocked pool ),同步鎖被釋放進入可運行狀態(Runnable)。

此外,在runnable狀態的線程是處於被調度的線程,此時的調度順序是不必定的。Thread類中的yield方法可讓一個running狀態的線程轉入runnable。

 

十3、線程池的概述和使用

1. 線程池概述

程序啓動一個新線程成本是比較高的,由於它涉及到要與操做系統進行交互。而使用線程池能夠很好的提升性能,尤爲是當程序中要建立大量生存期很短的線程時,更應該考慮使用線程池。線程池裏的每個線程代碼結束後,並不會死亡,而是再次回到線程池中成爲空閒狀態,等待下一個對象來使用。在JDK 5以前,咱們必須手動實現本身的線程池,從JDK 5開始,Java內置了線程池。

2. 內置線程池的使用概述

JDK 5新增了一個Executors工廠類來產生線程池,有以下幾個方法:

  • public static ExecutorService newFixedThreadPool(int nThreads)
  • public static ExecutorService newSingleThreadExecutor()  
  • 這些方法的返回值是ExecutorService對象,該對象表示一個線程池,能夠執行Runnable對象或者Callable對象表明的線程。它提供了以下方法:
    • Future<?> submit(Runnable task) //提交一個返回值的任務用於執行,返回一個表示任務的未決結果的Future
    • <T> Future<T> submit(Callable<T> task) //提交一個Runnable任務用於執行,並返回一個表示該任務的Future

 

使用步驟:

  1. 建立線程池對象
  2. 建立Runnable實例
  3. 提交Runnable實例
  4. 關閉線程池

 

【示例】:提交的是Runnable

Demo5_Executors.java

package com.wisedu.thread_communication;

/**
 * Created by jkzhao on 1/24/18.
 */
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo5_Executors {

    /**
     * public static ExecutorService newFixedThreadPool(int nThreads)
     * public static ExecutorService newSingleThreadExecutor()
     */
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(2);//建立線程池
        pool.submit(new MyRunnable());				//將線程放進池子裏並執行
        pool.submit(new MyRunnable());

        pool.shutdown();							//關閉線程池,啓動一次順序關閉,執行之前提交的任務,但不接受新任務。
                                                    // 若是不關閉池子,線程會不停的執行它裏面的代碼
    }

}

  

3. 多線程程序的實現方式三

Callable

future模式:併發模式的一種,能夠有兩種形式,即無阻塞和阻塞,分別是isDone和get。其中Future對象用來存放該線程的返回值以及狀態

ExecutorService e = Executors.newFixedThreadPool(3);
 //submit方法有多重參數版本,及支持callable也可以支持runnable接口類型.
Future future = e.submit(new myCallable());
future.isDone() //return true,false 無阻塞。若是任務已完成,返回true
future.get() // return 返回值,阻塞直到該線程運行結束

Demo6_Callable.java

package com.wisedu.thread_communication;

/**
 * Created by jkzhao on 1/24/18.
 */
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class Demo6_Callable {

    /**
     * @param args
     * @throws ExecutionException
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService pool = Executors.newFixedThreadPool(2);//建立線程池
        Future<Integer> f1 = pool.submit(new MyCallable(100));				//將線程放進池子裏並執行
        Future<Integer> f2 = pool.submit(new MyCallable(50));

        System.out.println(f1.get());
        System.out.println(f2.get());

        pool.shutdown();							//關閉線程池
    }

}

class MyCallable implements Callable<Integer> {
    private int num;

    public MyCallable(int num) {
        this.num = num;
    }

    @Override
    public Integer call() throws Exception { //call()計算結果,若是沒法計算結果,則拋出一個異常。原來的run()是不能夠拋異常的
        int sum = 0;
        for(int i = 1; i <= num; i++) {
            sum += i;
        }

        return sum;
    }

}

 

十4、設計模式之簡單工廠模式

1. 概述

簡單工廠模式又叫靜態工廠方法模式,它定義了一個具體的工廠類負責建立一些類的實例

  • 優勢:客戶端不須要在負責對象的建立,由工廠來建立,從而明確了各個類的職責
  • 缺點:這個靜態工廠類負責全部對象的建立,若是有新的對象增長,或者某些對象的建立方式不一樣,就須要不斷的修改工廠類,不利於後期的維護。

 

2. 案例

  • 動物抽象類
    • public abstract Animal { public abstract void eat(); }
  • 具體狗類
    • public class Dog extends Animal {}
  • 具體貓類
    • public class Cat extends Animal {}

開始,在測試類中每一個具體的內容本身建立對象,可是,建立對象的工做若是比較麻煩,就須要有人專門作這個事情,因此就製造了一個專門的類來建立對象。

先來3個類:

Animal.java

package com.wisedu.staticFactory;

/**
 * Created by jkzhao on 1/25/18.
 */
public abstract class Animal {
    public abstract void eat();
}

Dog.java

package com.wisedu.staticFactory;

/**
 * Created by jkzhao on 1/25/18.
 */
public class Dog extends Animal {

    @Override
    public void eat() {
        System.out.println("狗吃肉");
    }

}

Cat.java

package com.wisedu.staticFactory;

/**
 * Created by jkzhao on 1/25/18.
 */
public class Cat extends Animal {

    @Override
    public void eat() {
        System.out.println("貓吃魚");
    }

}

接下來得作一個動物工廠來生產動物,若是沒有這個工廠,用到對象就得本身建立。和上面的線程池工廠一個道理。

Animal.java

package com.wisedu.staticFactory;

/**
 * Created by jkzhao on 1/25/18.
 */
public class AnimalFactory {
    public static Dog createDog() {
        return new Dog();
    }

    public static Cat createCat() {
        return new Cat();
    }
}

接下來寫個測試類

test.java

package com.wisedu.staticFactory;

/**
 * Created by jkzhao on 1/25/18.
 */
public class Test {

    /**
     * @param args
     */
    public static void main(String[] args) {
        Dog d = AnimalFactory.createDog(); //不用本身建立了,用工廠來建立
        d.eat();
    }

}

可是咱們發現這種方式不太好,工廠類裏面對於建立Dog和Cat各寫了一個方法,若是動物不少的話,得定義不少方法,複用性太差。能夠改進下:

public class AnimalFactory {
	/*public static Dog createDog() {
		return new Dog();
	}

	public static Cat createCat() {
		return new Cat();
	}*/

    //發現方法會定義不少,複用性太差
    //改進
    public static Animal createAnimal(String name) {
        if("dog".equals(name)) {
            return new Dog();
        }else if("cat".equals(name)) {
            return new Cat();
        }else {
            return null;
        }
    }
}

修改測試類的main方法,以下:

public class Test {

    /**
     * @param args
     */
    /*public static void main(String[] args) {
        Dog d = AnimalFactory.createDog(); //不用本身建立了,用工廠來建立
        d.eat();
    }*/

    public static void main(String[] args) {
        //Dog d = AnimalFactory.createDog();

        Dog d = (Dog) AnimalFactory.createAnimal("dog");
        d.eat();

        Cat c = (Cat) AnimalFactory.createAnimal("cat");
        c.eat();
    }

}

  

十5、設計模式之工廠方法模式

1. 概述

工廠方法模式中的抽象工廠類負責定義建立對象的接口,具體對象的建立工做由繼承抽象工廠的具體類實現。

  • 優勢:客戶端不須要在負責對象的建立,從而明確了各個類的職責,若是有新的對象增長,只須要增長一個具體的類和具體的工廠類便可,不影響已有的代碼,後期維護容易,加強了系統的擴展性。
  • 缺點:須要額外的編寫代碼,增長了工做量。有的人想要增長這麼多代碼,還不如我本身建立對象了。可是有的時候你本身建立不容許,就得交給工廠來建立,就是大家家不能生產錢,就得交給國家來生產。

 

2. 案例

先來3個類,Animal.java Dog.java Cat.java,這幾個和上面的案例中的代碼同樣。

接着定義一個工廠接口,Factory.java

package com.wisedu.FactoryMethod;

/**
 * Created by jkzhao on 1/25/18.
 */
public interface Factory {
    public Animal createAnimal(); //多態的思想
}

接着編寫貓工廠和狗工廠

CatFactory.java

package com.wisedu.FactoryMethod;

/**
 * Created by jkzhao on 1/25/18.
 */
public class CatFactory implements Factory {

    @Override
    public Animal createAnimal() {
        return new Cat();
    }

}

DogFactory.java

package com.wisedu.FactoryMethod;

/**
 * Created by jkzhao on 1/25/18.
 */
public class DogFactory implements Factory {

    @Override
    public Animal createAnimal() {
        return new Dog();
    }

}  

再來編寫測試類

package com.wisedu.FactoryMethod;

/**
 * Created by jkzhao on 1/25/18.
 */
public class Test {

    /**
     * @param args
     */
    public static void main(String[] args) {
        DogFactory df = new DogFactory(); //先建立狗工廠
        Dog d = (Dog) df.createAnimal();
        d.eat();
    }

}

你會發現也很麻煩,都有優缺點。

相關文章
相關標籤/搜索