java多線程編程

來源:互聯網
上載者:User

標籤:電腦程式   thread   後台運行   虛擬機器   計數器   

1.多線程基本概念

1.1 進程和線程

進程:一個電腦程式的運行執行個體,包含了需要執行的指令;有自己的獨立地址空間,包含程式內容和資料;不同進程的地址空間是互相隔離的;進程擁有各種資源和狀態資訊,包括開啟的檔案、子進程和訊號處理。

線程:表示程式的執行流程,是CPU調度執行的基本單位;線程有自己的程式計數器、寄存器、堆棧和幀。同一進程中的線程共用相同的地址空間,同時共用進程鎖擁有的記憶體和其他資源。

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

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

線程主要包括表示線程的java.lang.Thread類,在虛擬機器啟動之後,通常只有Java類的main方法這個使用者線程運行(主線程),運行時可以建立和啟動新的線程(子線程);還有一類守護線程(damon thread),守護線程在後台運行,提供者運行時所需的服務。當虛擬機器中啟動並執行所有線程都是守護線程時,虛擬機器終止運行。

2、Thread類和Runnable介面

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

2.1 建立線程

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、 線程同步方法

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時間,對效能造成影響。

java多線程編程

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.