java多線程編程

1.多線程基本概念java

1.1 進程和線程程序員

進程:一個計算機程序的運行實例,包含了須要執行的指令;有本身的獨立地址空間,包含程序內容和數據;不一樣進程的地址空間是互相隔離的;進程擁有各類資源和狀態信息,包括打開的文件、子進程和信號處理。編程

線程:表示程序的執行流程,是CPU調度執行的基本單位;線程有本身的程序計數器、寄存器、堆棧和幀。同一進程中的線程共用相同的地址空間,同時共享進程鎖擁有的內存和其餘資源。小程序

1.2 Java標準庫提供了進程和線程相關的API緩存

進程主要包括表示進程的java.lang.Process類和建立進程的java.lang.ProcessBuilder類;多線程

線程主要包括表示線程的java.lang.Thread類,在虛擬機啓動以後,一般只有Java類的main方法這個用戶線程運行(主線程),運行時能夠建立和啓動新的線程(子線程);還有一類守護線程(damon thread),守護線程在後臺運行,提供程序運行時所需的服務。當虛擬機中運行的全部線程都是守護線程時,虛擬機終止運行。ide

二、Thread類和Runnable接口
性能

在java中可有兩種方式實現多線程,一種是繼承Thread類,一種是實現Runnable接口;Thread類是在java.lang包中定義的。一個類只要繼承了Thread類同時覆寫了本類中的run()方法就能夠實現多線程操做了。ui

2.1 建立線程this

Java定義了兩種方式用來建立線程:

(1) 經過繼承Thread類,重寫Thread的run()方法,將線程運行的邏輯放在其中

(2)經過實現Runnable接口,實例化Thread類

舉例說這兩種方法的使用:在實際應用中,咱們常常用到多線程,如車站的售票系統,車站的各個售票口至關於各個線程。當咱們作這個系統的時候可能會想到兩種方式來實現,繼承Thread類或實現Runnable接口,如今看一下這兩種方式實現的兩種結果。

(1)經過繼承Thread類

package com.threadtest;
class MyThread extends Thread{    
    private int ticket = 10;
    private String name;
    public MyThread(String name){
        this.name =name;
    }    
    public void run(){
        for(int i =0;i<500;i++){
            if(this.ticket>0){
                System.out.println(this.name+"賣票---->"+(this.ticket--));
            }
        }
    }
}
public class ThreadDemo {   
    public static void main(String[] args) {
        MyThread mt1= new MyThread("一號窗口");
        MyThread mt2= new MyThread("二號窗口");
        MyThread mt3= new MyThread("三號窗口");
        mt1.start();
        mt2.start();
        mt3.start();
    }
}

運行結果

一號窗口賣票---->10
一號窗口賣票---->9
二號窗口賣票---->10
一號窗口賣票---->8
一號窗口賣票---->7
一號窗口賣票---->6
三號窗口賣票---->10
一號窗口賣票---->5
一號窗口賣票---->4
一號窗口賣票---->3
一號窗口賣票---->2
一號窗口賣票---->1
二號窗口賣票---->9
二號窗口賣票---->8
三號窗口賣票---->9
三號窗口賣票---->8
三號窗口賣票---->7
三號窗口賣票---->6
三號窗口賣票---->5
三號窗口賣票---->4
三號窗口賣票---->3
三號窗口賣票---->2
三號窗口賣票---->1
二號窗口賣票---->7
二號窗口賣票---->6
二號窗口賣票---->5
二號窗口賣票---->4
二號窗口賣票---->3
二號窗口賣票---->2
二號窗口賣票---->1

(2)經過實現Runnable接口

package com.threadtest;
class MyThread1 implements Runnable{
    private int ticket =10;
    private String name;
    public void run(){
        for(int i =0;i<500;i++){
            if(this.ticket>0){
                System.out.println(Thread.currentThread().getName()+"賣票---->"+(this.ticket--));
            }
        }
    }
}
public class RunnableDemo {
    
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        //設計三個線程
         MyThread1 mt = new MyThread1();
         Thread t1 = new Thread(mt,"一號窗口");
         Thread t2 = new Thread(mt,"二號窗口");
         Thread t3 = new Thread(mt,"三號窗口");
         t1.start();
         t2.start();
         t3.start();
    }
}
運行結果以下
一號窗口賣票---->10
三號窗口賣票---->9
三號窗口賣票---->7
三號窗口賣票---->5
三號窗口賣票---->4
三號窗口賣票---->3
三號窗口賣票---->2
三號窗口賣票---->1
一號窗口賣票---->8
二號窗口賣票---->6


 爲何會出現這種結果呢?咱們不妨作個比喻:

繼承Thread類的,咱們至關於拿出三件事即三個賣票10張的任務分別分給三個窗口,他們各作各的事各賣各的票各完成各的任務,由於MyThread繼承Thread類,因此在new MyThread的時候在建立三個對象的同時建立了三個線程;

實現Runnable的, 至關因而拿出一個賣票10張得任務給三我的去共同完成,new MyThread至關於建立一個任務,而後實例化三個Thread,建立三個線程即安排三個窗口去執行。

2.2 Thread類和Runnable接口的區別:

Thread類定義了許多方法,它的派生類能夠重寫這些方法,在這些方法中,只有run()方法是必須重寫的。固然,在實現Runnable接口時也須要實現這一方法。大多數狀況下,若是隻想重寫 run() 方法,而不重寫他 Thread 方法,那麼應使用 Runnable 接口。這很重要,由於除非程序員打算修改或加強類的基本行爲,不然不該爲該類(Thread)建立子類。總的來講,使用Thread類和Runnable類主要有如下區別:

(1)當使用繼承的時候,主要是爲了避免必從新開發而且在沒必要了解實現細節的狀況下擁有了父類我所須要的特徵。它也有一個很大的缺點,那就是若是咱們的類已經從一個類繼承(如小程序必須繼承自 Applet 類),則沒法再繼承 Thread 類,
(2)java只能單繼承,所以若是是採用繼承Thread的方法,那麼在之後進行代碼重構的時候可能會遇到問題,由於你沒法繼承別的類了,在其餘的方面,二者之間並沒什麼太大的區別。
(3)implement Runnable是面向接口,擴展性等方面比extends Thread好。
(4)使用 Runnable 接口來實現多線程使得咱們可以在一個類中包容全部的代碼,有利於封裝,它的缺點在於,咱們只能使用一套代碼,若想建立多個線程並使各個線程執行不一樣的代碼,則仍必須額外建立類,若是這樣的話,在大多數狀況下也許還不如直接用多個類分別繼承 Thread 來得緊湊。

三、 線程同步方法

3.1 synchronized關鍵字

當使用多線程時,有時須要協調兩個以上的活動,這個過程稱經過同步(Synchronization)實現。同步的主要緣由有兩個:一是兩個以上的線程須要訪問同一共享資源,而該資源每次只能由一個線程訪問,例如,不容許兩個線程同時對同一文件進行寫操做;二是當一個線程等待另外一個線程引發的事件時,必需要有某種方法來保證前一個線程在事件發生前處於掛起狀態,而在事件發生後,等待的線程恢復執行。

全部的Java對象都有一個與synchronzied關聯的監視器對象(monitor),容許線程在該監視器對象上進行加鎖和解鎖操做。

a、靜態方法:Java類對應的Class類的對象所關聯的監視器對象。

b、實例方法:當前對象實例所關聯的監視器對象。

c、代碼塊:代碼塊聲明中的對象所關聯的監視器對象。

注:當鎖被釋放,對共享變量的修改會寫入主存;當得到鎖,CPU緩存中的內容被置爲無效。編譯器在處理synchronized方法或代碼塊,不會把其中包含的代碼移動到synchronized方法或代碼塊以外,從而避免了因爲代碼重排而形成的問題。

例:如下方法getNext()和getNextV2() 都得到了當前實例所關聯的監視器對象

public class SynchronizedIdGenerator{  
   private int value = 0;  
   public synchronized int getNext(){  
      return value++;  
   }  
   public int getNextV2(){  
      synchronized(this){  
         return value++;  
      }  
   }  
}

3.2 Object類的notify()、wait()和notifyAll()方法

考慮下面一種情形:線程T在一個同步的方法中執行,須要訪問資源R,而資源R又暫時沒法訪問,線程T會如何作呢?若是線程T進入了某種等待資源R的輪詢循環中,T就鎖定了這個對象,防止其餘線程訪問。然而,這並非一種好的方法,它抵消了多線程環境的編程優點,更好的解決方法是讓T暫時放棄控制資源R,容許其餘線程容許,當資源R能夠訪問時,通知線程T,恢復它的執行。這種方法依賴於java提供的線程通訊方法notify()、wait()和notifyAll()。
Object類實現了方法notify()、wait()和notifyAll(),所以這些方法是全部對象的一部分,這些方法只能在synchronized內容中調用,他們的使用方法以下:
wait: 將當前線程放入,該對象的等待池中,線程A調用了B對象的wait()方法,線程A進入B對象的等待池,而且釋放B的鎖。(這裏,線程A必須持有B的鎖,因此調用的代碼必須在synchronized修飾下,不然直接拋出java.lang.IllegalMonitorStateException異常)。
notify:將該對象中等待池中的線程,隨機選取一個放入對象的鎖池,噹噹前線程結束後釋放掉鎖, 鎖池中的線程便可競爭對象的鎖來得到執行機會。
notifyAll:將對象中等待池中的線程,所有放入鎖池。

這是一種生產者和消費者模式,判斷緩衝區是否滿來消費,緩衝區是否空來生產的邏輯。若是用while 和 volatile也能夠作,不過本質上會讓線程處於忙等待,佔用CPU時間,對性能形成影響。

相關文章
相關標籤/搜索