Java中線程(Thread)的知識很重要,沒有它,咱們項目中的不少功能都沒法實現。跟線程有關的是進程,平常生活中咱們聽的比較多的是進程,一般咱們的電腦卡了,咱們就會說要殺進程。進程跟線程是不一樣的概念,二者有區別也有聯繫。進程,通俗的講就是咱們電腦中運行中的程序,程序的概念是靜態的,進程是動態的概念。像咱們電腦運行的視頻播放器,音樂播放器都是進程。線程,是運行在進程中的順序控制流,只能使用分配給進程的資源和環境。一個進程中至少會有一個線程。java
瞭解線程的相關概念後,咱們如今來將如何實現線程。線程的實現方式有兩種。一種是經過繼承Thread類,並重寫run()方法實現;另外一種是經過實現Runnable接口並實現其run()方法。下面經過例子來分析兩種實現的區別。算法
一、經過繼承Thread類編程
1 package thread; 2 /** 3 * 4 * @author CIACs 5 *線程的生成經過繼承Thread類實現 6 */ 7 public class ThreadTest { 8 public static void main(String[] args) { 9 MyThread1 t1 = new MyThread1(); 10 t1.start(); 11 MyThread2 t2 = new MyThread2(); 12 t2.start(); 13 } 14 15 } 16 17 class MyThread1 extends Thread 18 { 19 20 @Override 21 public void run() { 22 for(int i=0;i<50;i++) 23 { 24 System.out.println("MyThread1 running: "+i); 25 } 26 } 27 } 28 class MyThread2 extends Thread 29 { 30 @Override 31 public void run() { 32 for(int i=0;i<50;i++) 33 { 34 System.out.println("MyThread2 running: "+i); 35 } 36 } 37 }
控制檯輸出結果:多線程
在這裏咱們能夠看到兩個線程會交叉執行,並非一個先執行完後,另外一個再執行。這就是說當線程啓動後咱們是不能控制執行順序的。(固然是在還沒用synchronized、wait()、notify()的時候)dom
二、經過實現Runnable接口ide
1 package thread; 2 /** 3 * 4 * @author CIACs 5 *線程經過實現Runnable接口生成 6 */ 7 public class ThreadTest2 { 8 public static void main(String[] args) { 9 Thread1 t1 = new Thread1(); 10 new Thread(t1).start(); 11 Thread2 t2 = new Thread2(); 12 new Thread(t2).start(); 13 } 14 15 } 16 17 class Thread1 implements Runnable 18 { 19 @Override 20 public void run() { 21 for(int i=0;i<50;i++) 22 { 23 System.out.println("Thread1 running "+i); 24 } 25 26 } 27 } 28 29 class Thread2 implements Runnable 30 { 31 @Override 32 public void run() { 33 for(int i=0;i<50;i++) 34 { 35 System.out.println("Thread2 running "+i); 36 } 37 38 } 39 }
控制檯輸出結果:學習
在這裏我只上傳了部分結果的截圖,咱們所要的在這部分截圖中就能夠看出了。this
在編寫程序時咱們把但願線程執行的代碼放到run()方法中,而後經過調用start()方法來啓動線程,start()方法會爲線程的執行準備好資源,以後再去調用run()方法,當某個類繼承了Thread類後,該類就是線程類。線程的消亡不能經過調用stop()方法,而是讓run()方法天然結束。spa
每一個線程有其優先級,最高爲10(MAX_PRIORITY),最低爲1(MIN_PRIORITY),設置優先級是爲了在多線程環境中便於系統對線程的調度,同等狀況下,優先級高的會先比優先級低的執行。固然操做系統也不是徹底按照優先級高低執行的,不然有可能優先級低的會一直處於等待狀態,操做系統有本身的調度算法(這裏就先不展開討論了)。當運行中的線程調用了yield()方法,就會讓出cpu的佔用;調用sleep()方法會使線程進入睡眠狀態,此時其餘線程也就能夠佔用cpu資源了;有另外一個更高優先級的線程出現也會致使運行中的線程讓出cpu資源。有些調度算法是分配固定的時間片給線程執行,在這段時間內能夠佔用cpu,一旦用完就必須讓出cpu資源。操作系統
線程的生命週期有以下四個
一、建立狀態,當用new建立一個新的線程對象時,該線程處於建立狀態,但此時系統沒有分配資源給它。
二、可運行狀態,執行線程的start()方法後系統將會分配線程運行所需的系統資源,並調用線程體run()方法,此時線程處於可運行狀態。
三、不可運行狀態,當線程調用了sleep()方法,或者wait()方法時,線程處於不可運行狀態,線程的輸入輸出阻塞時也會致使線程不可運行。
四、消亡狀態,當線程的run()方法執行結束後,就進入消亡狀態。
下圖是普通線程的狀態轉換圖:
當使用了synchronized關鍵字時,線程的狀態轉換圖會有點不一樣,以下圖
控制多線程同步,使用wait()和notify()的線程狀態轉換圖以下:
對於單核cpu來講,某一時刻只能有一個線程在執行,但在宏觀上咱們會看到多個進程在執行,這就是微觀串行,宏觀上並行。如今單核的電腦基本上已經沒有了。多核的電腦就能夠實現微觀並行。多線程編程就是爲了最大限度的利用cpu資源。例如當某一個線程和外設打交道時,此時它不須要用到cpu資源,但它仍然佔着cpu,其餘的線程就不能利用,多線程編程就能夠解決該問題。多線程是多任務處理的一種特殊形式。可是多線程可能會形成衝突,兩個或多個線程要同時訪問一個共同資源。
下面以銀行取款爲例,有一個帳號,裏面存1000元,而後建立兩個線程模擬從銀行櫃檯和ATM同時取700元。這裏咱們的銀行卡不能透支。
1 package thread; 2 /** 3 * 4 * @author CIACs 5 * 6 */ 7 public class BankAccount { 8 private int MyMoney = 1000; 9 10 public void getMoney(int money) 11 { 12 if(MyMoney<=0||(MyMoney-money)<0) 13 { 14 System.out.println("餘額不足"); 15 } 16 else 17 { 18 try { 19 Thread.sleep((long)(Math.random()*1000)); 20 } catch (InterruptedException e) { 21 e.printStackTrace(); 22 } 23 System.out.println("取"+money); 24 MyMoney = MyMoney-money; 25 } 26 System.out.println("帳戶剩餘的錢"+MyMoney); 27 } 28 }
1 package thread; 2 /** 3 * 4 * @author CIACs 5 * 6 */ 7 public class Thread1 extends Thread{ 8 private BankAccount MyBank; 9 public Thread1(BankAccount ba) 10 { 11 this.MyBank = ba; 12 } 13 @Override 14 public void run() { 15 16 MyBank.getMoney(700); 17 } 18 19 }
1 package thread; 2 3 public class Client { 4 public static void main(String[] args) { 5 BankAccount MyBank = new BankAccount(); 6 Thread1 t1 = new Thread1(MyBank); 7 Thread1 t2 = new Thread1(MyBank); 8 t1.start(); 9 t2.start(); 10 11 } 12 }
控制檯輸出結果:
咱們取了1400,帳戶餘額未-400,很明顯銀行是不容許咱們這樣作的。這就說,可能當一個線程進入到取錢代碼部分時先進行了睡眠,另外一個也進來了,且也進入睡眠。當其中一個醒來時,取完錢後,另外一個也醒來繼續取錢。咱們如何解決這個問題呢?這時咱們就要用synchronized關鍵字來解決。只需在取錢的方法處加上synchronized關鍵字修飾就能夠解決該問題。
加上後控制檯輸出結果:
synchronized關鍵字是同步的意思,當synchronized修飾一個方法時,該方法叫作同步方法。Java中的每一個對象都有一個鎖(lock)或者叫監視器(monitor),當咱們使用synchronized關鍵字修飾一個對象的方法時,就會把這個對象上鎖,當該對象上鎖時,其餘的線程就沒法再訪問該對象的方法,直到線程結束或拋出異常,該對象的鎖就會釋放掉。看似在方法前加上synchronized關鍵字修飾好像完美的解決了多線程訪問同一資源的衝突問題,可是因爲synchronized是粗粒度的,也就是使用了該關鍵字會把方法所對應的類也會鎖上,該類的任何其餘方法都不會被其餘線程所訪問,這樣就致使了資源利用率下降。爲了解決這個方法,咱們仍是要利用synchronized關鍵字。此時synchronized關鍵字鎖的是咱們建立的任何一個對象。synchronized塊代碼以下
1 private obj = new Object(); 2 synchronized(obj) 3 { 4 //線程要執行的代碼 5 }
此時咱們鎖的是咱們建立的一個任一對象,synchronized塊外面的方法是沒有被鎖的,也就是說其餘線程能夠訪問synchronized塊外面的方法。
wait()跟notify()方法能夠實現線程的等待和喚醒操做,這兩個方法都是定義在Object類中的,並且是final的,所以會被全部的java類所繼承,且沒法重寫。調用這兩個方法要求線程已經得到了對象的鎖,所以會把這兩個方法放在synchronized方法或塊中,且是成對出現的。執行wait()時,線程會釋放對象的鎖,還有一個方法也會使線程暫停執行,那就是sleep()方法,不過該方法不會釋放對象的鎖。
生產者,消費者問題在每一本學習線程的書上都會有提到,大概就是說生產者生產了商品就通知消費者消費商品,當消費者消費了商品酒通知生產者生產商品,這裏涉及到了線程間的通訊。咱們用輸出0和1來模擬生產者消費者問題。當number爲1時就要減一,當number爲0時就要加一。要解決該問題就要用wait()跟notify()方法結合synchronized的使用。
Number類
1 package thread; 2 /** 3 * 4 * @author CIACs 5 * 6 */ 7 public class Number 8 { 9 private int number = 1; 10 public synchronized void AddNumber() 11 { 12 if(number > 0) 13 { 14 try 15 { 16 //當不符合條件時進入等待狀態,讓出cpu資源 17 wait(); 18 } catch (InterruptedException e) 19 { 20 e.printStackTrace(); 21 } 22 } 23 //執行加操做 24 number++; 25 System.out.println(number); 26 //喚醒減操做的線程 27 notify(); 28 } 29 30 public synchronized void SubNumber() 31 { 32 if(number == 0) 33 { 34 try 35 { 36 //等於0時進入等待狀態 37 wait(); 38 } catch (InterruptedException e) 39 { 40 e.printStackTrace(); 41 } 42 } 43 //執行減操做 44 number--; 45 System.out.println(number); 46 //喚醒加操做的線程 47 notify(); 48 } 49 }
AddThread類
1 package thread; 2 /** 3 * 4 * @author CIACs 5 * 6 */ 7 public class AddThread extends Thread 8 { 9 //設置線程執行的標誌,初始爲true 10 boolean flag = true; 11 private Number number; 12 public AddThread(Number number) 13 { 14 this.number = number; 15 } 16 //改變判斷標誌,使線程中止 17 public void setFlag() 18 { 19 this.flag = false; 20 } 21 22 @Override 23 public void run() 24 { 25 while(flag) 26 { 27 try 28 { 29 Thread.sleep((long)(Math.random()*1000)); 30 this.number.AddNumber(); 31 } catch (InterruptedException e) 32 { 33 e.printStackTrace(); 34 } 35 } 36 } 37 }
SubThread類
1 package thread; 2 /** 3 * 4 * @author CIACs 5 * 6 */ 7 public class SubThread extends Thread 8 { 9 //設置線程執行的標誌,初始爲true 10 boolean flag = true; 11 private Number number; 12 public SubThread(Number number) 13 { 14 this.number = number; 15 } 16 //改變標誌的值,使線程中止 17 public void setFlag() 18 { 19 this.flag = false; 20 } 21 22 @Override 23 public void run() 24 { 25 while(flag) 26 { 27 try 28 { 29 Thread.sleep((long)(Math.random()*1000)); 30 this.number.SubNumber(); 31 } catch (InterruptedException e) 32 { 33 e.printStackTrace(); 34 } 35 } 36 } 37 }
客戶端
1 package thread; 2 /** 3 * 4 * @author CIACs 5 * 6 */ 7 public class Client 8 { 9 public static void main(String[] args) 10 { 11 Number num = new Number(); 12 AddThread t1 = new AddThread(num); 13 SubThread t2 = new SubThread(num); 14 t2.start(); 15 t1.start(); 16 try 17 { 18 //過了三秒後使其中一個線程中止,一個線程中止後,另外一個也不能執行,由於另外一個線程處於等待狀態。 19 Thread.sleep((long)(Math.random()*3000)); 20 t1.setFlag(); 21 22 } catch (InterruptedException e) 23 { 24 e.printStackTrace(); 25 } 26 27 } 28 }
控制檯輸出結果:
在這個例子中我利用flag標誌來控制線程的中止和執行,咱們不會用stop()方法去控制線程的中止。
在線程的使用中咱們還要注意的是若是線程中有控制的是局部變量則每一個線程對局部變量的改變互不影響,若是是成員變量則會影響到其餘線程。使用好線程,咱們能提升作事的效率。