黑馬程式員——java基礎——多線程,黑馬程式員java

來源:互聯網
上載者:User

黑馬程式員——java基礎——多線程,黑馬程式員java


黑馬程式員——java基礎——多線程

------Java培訓、Android培訓、iOS培訓、.Net培訓、期待與您交流! -------

進程:是一個正在執行中的程式。每一個進程執行都有一個執行順序。該順序是一個執行路徑,或者叫一個控制單元。
線程:就是進程中的一個獨立的控制單元。線程在控制著進程的執行。一個進程中至少有一個線程。

一個進程至少有一個線程在運行,當一個進程中出現多個線程時,就稱這個應用程式是多線程應用程式,每個線程在棧區中都有自己的執行空間,自己的方法區、自己的變數。

jvm在啟動的時,首先有一個主線程,負責程式的執行,調用的是main函數。主線程執行的代碼都在main方法中。

當產生垃圾時,收垃圾的動作,是不需要主線程來完成,因為這樣,會出現主線程中的代碼執行會停止,會去運行記憶體回收行程代碼,效率較低,所以由單獨一個線程來負責記憶體回收。
擴充:其實更細節說明jvm,jvm啟動不止一個線程,還有負責記憶體回收機制的線程。

隨機性的原理:因為cpu的快速切換造成,哪個線程擷取到了cpu的執行權,哪個線程就執行。


返回當前線程的名稱:Thread.currentThread().getName()

線程的名稱是由:Thread-編號定義的。編號從0開始。

線程要啟動並執行代碼都統一存放在了run方法中。

線程要運行必須要通過類中指定的方法開啟。start方法。(啟動後,就多了一條執行路徑)

start方法:1)、啟動了線程;2)、讓jvm調用了run方法。

建立線程的方式

 建立線程共有兩種方式:繼承方式和實現方式

建立線程的第一種方式:繼承Thread,由子類複寫run方法。

步驟:

1,定義類繼承Thread類;

2,目的是複寫run方法,將要讓線程啟動並執行代碼都儲存到run方法中;

3,通過建立Thread類的子類對象,建立線程對象;

4,調用線程的start方法,開啟線程,並執行run方法。

class MyThread extends Thread{public MyThread() {super();// TODO Auto-generated constructor stub}public MyThread(String name) {super(name);// TODO Auto-generated constructor stub}public void run() {for(int i =0 ;i<10;i++){System.out.println("Demo:"+Thread.currentThread().getName()+":"+i);}}}public class ThreadDemo {public static void main(String[] args) {// TODO Auto-generated method stubMyThread demo1 = new MyThread("線程一");demo1.start();MyThread demo2 = new MyThread("線程二");demo2.start();for(int i = 0;i<10;i++){System.out.println("main:"+i);}}}

建立線程的第二種方式:實現一個介面Runnable

步驟:

1,定義類實現Runnable介面。

2,覆蓋介面中的run方法(用於封裝線程要啟動並執行代碼)。

3,通過Thread類建立線程對象;

4,將實現了Runnable介面的子類對象作為實際參數傳遞給Thread類中的建構函式。

為什麼要傳遞呢?因為要讓線程對象明確要啟動並執行run方法所屬的對象。

5,調用Thread對象的start方法。開啟線程,並運行Runnable介面子類中的run方法。

class Ticket implements Runnable{private  int tick = 100;public void run(){while(true){if(tick>0){System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);}}}}class  TicketDemo{public static void main(String[] args) {Ticket t = new Ticket();Thread t1 = new Thread(t);//建立了一個線程;Thread t2 = new Thread(t);//建立了一個線程;t1.start();t2.start();}}

為什麼要有Runnable介面的出現?

1:通過繼承Thread類的方式,可以完成多線程的建立。但是這種方式有一個局限性,如果一個類已經有了自己的父類,就不可以繼承Thread類,因為java單繼承的局限性。

可是該類中的還有部分代碼需要被多個線程同時執行。這時怎麼辦呢?

只有對該類進行額外的功能擴充,java就提供了一個介面Runnable。這個介面中定義了run方法,其實run方法的定義就是為了儲存多線程要啟動並執行代碼。

所以,通常建立線程都用第二種方式。

因為實現Runnable介面可以避免單繼承的局限性

2:其實是將不同類中需要被多線程執行的代碼進行抽取。將多線程要啟動並執行代碼的位置單獨定義到介面中。為其他類進行功能擴充提供了前提。

所以Thread類在描述線程時,內部定義的run方法,也來自於Runnable介面

實現Runnable介面可以避免單繼承的局限性。而且,繼承Thread,是可以對Thread類中的方法,進行子類複寫的。但是不需要做這個複寫動作的話,只為定義線程代碼存放位置,實現Runnable介面更方便一些。所以Runnable介面將線程要執行的任務封裝成了對象。

線程狀態:

被建立:start()

運行:具備執行資格,同時具備執行權;

凍結:sleep(time),wait()—notify()喚醒;線程釋放了執行權,同時釋放執行資格;

臨時阻塞狀態:線程具備cpu的執行資格,沒有cpu的執行權;

消亡:stop()


安全執行緒問題

多安全執行緒問題的原因:
通過圖解:發現一個線程在執行多條語句時,並運算同一個資料時,在執行過程中,其他線程參與進來,並操作了這個資料。導致到了錯誤資料的產生。

涉及到兩個因素:
1,多個線程在操作共用資料。
2,有多條語句對共用資料進行運算。
原因:這多條語句,在某一個時刻被一個線程執行時,還沒有執行完,就被其他線程執行了。
解決安全問題的原理:
只要將操作共用資料的語句在某一時段讓一個線程執行完,在執行過程中,其他線程不能進來執行就可以解決這個問題。

在java中對於多線程的安全問題提供了專業的解決方式——synchronized(同步)

這裡也有兩種解決方式,一種是同步代碼塊,還有就是同步函數。都是利用關鍵字synchronized來實現。

a、同步代碼塊

用法:

        synchronized(對象)

         {需要被同步的代碼}

 同步可以解決安全問題的根本原因就在那個對象上。其中對象如同鎖。持有鎖的線程可以在同步中執行。沒有持有鎖的線程即使擷取cpu的執行權,也進不去,因為沒有擷取鎖。

<span style="font-size:14px;">class Ticket implements Runnable{private  int tick = 1000;Object obj = new Object();public void run(){while(true){synchronized(obj){if(tick>0){//try{Thread.sleep(10);}catch(Exception e){}System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);}}}}}class  TicketDemo2{public static void main(String[] args) {Ticket t = new Ticket();Thread t1 = new Thread(t);Thread t2 = new Thread(t);t1.start();t2.start();}}</span>

b,同步函數

其實就是將同步關鍵字定義在函數上,讓函數具備了同步性。

同步函數是用的哪個鎖呢?

通過驗證,函數都有自己所屬的對象this,所以同步函數所使用的鎖就是this鎖。

<span style="font-size:14px;">class Ticket implements Runnable{private  int tick = 100;Object obj = new Object();boolean flag = true;public  void run(){if(flag){while(true){show();}}}public synchronized void show()//this{if(tick>0){try{Thread.sleep(10);}catch(Exception e){}System.out.println(Thread.currentThread().getName()+"....show.... : "+ tick--);}}}class  ThisLockDemo{public static void main(String[] args) {Ticket t = new Ticket();Thread t1 = new Thread(t);t1.start();}</span>

當同步函數被static修飾時,這時的同步用的是哪個鎖呢

靜態函數在載入時所屬於類,這時有可能還沒有該類產生的對象,但是該類的位元組碼檔案載入進記憶體就已經被封裝成了對象,這個對象就是該類的位元組碼檔案對象。

所以靜態載入時,只有一個對象存在,那麼靜態同步函數就使用的這個對象。

這個對象就是類名.class

class Single{private static Single s = null;private Single(){}public static  Single getInstance(){if(s==null){synchronized(Single.class){if(s==null)//--->A;s = new Single();}}return s;}}class SingleDemo {public static void main(String[] args) {System.out.println("Hello World!");}}

同步代碼塊和同步函數的區別

同步代碼塊使用的鎖可以是任意對象。

同步函數使用的鎖是this,靜態同步函數的鎖是該類的位元組碼檔案對象。

線程同步的利弊

好處:解決了安全執行緒問題。

弊端:相對降低效能,因為判斷鎖需要消耗資源,產生了死結。

定義同步是有前提的:

1,必須要有兩個或者兩個以上的線程,才需要同步。

2,多個線程必須保證使用的是同一個鎖。

同步死結

通常只要將同步進行嵌套,就可以看到現象。同步函數中有同步代碼塊,同步代碼塊中還有同步函數。


public class DeadLockDemo {public static void main(String[] args) {// TODO Auto-generated method stub// TODO Auto-generated method stubTicket1 ticket1 = new Ticket1();Thread thread1 = new Thread(ticket1);thread1.start();try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}Thread thread2 = new Thread(ticket1);ticket1.flag = false;thread2.start();}}class Ticket1 implements Runnable{private static int ticket1 = 10;private Object obj = new Object();boolean flag = true;@Overridepublic void run() {// TODO Auto-generated method stubif(flag){while(true){synchronized (obj) {show();}}}else{while(true){show();}}}public synchronized void show(){synchronized (obj) {if(ticket1>0){try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println(Thread.currentThread().getName()+":"+ticket1--);}}}}

線程間通訊:
其實就是多個線程在操作同一個資源,但是操作的動作不同。

1:將資源封裝成對象。

2:將線程執行的任務(任務其實就是run方法。)也封裝成對象。

<span style="font-size:14px;">class Res{String name;String sex;boolean flag = false;}class Input implements Runnable{private Res r ;Input(Res r){this.r = r;}public void run(){int x = 0;while(true){synchronized(r){if(r.flag)try{r.wait();}catch(Exception e){}if(x==0){r.name="mike";r.sex="man";}else{r.name="麗麗";r.sex = "女";}x = (x+1)%2;r.flag = true;r.notify();}}}}class Output implements Runnable{private Res r ;Output(Res r){this.r = r;}public void run(){while(true){synchronized(r){if(!r.flag)try{r.wait();}catch(Exception e){}System.out.println(r.name+"...."+r.sex);r.flag = false;r.notify();}}}}class  InputOutputDemo{public static void main(String[] args) {Res r = new Res();Input in = new Input(r);Output out = new Output(r);Thread t1 = new Thread(in);Thread t2 = new Thread(out);t1.start();t2.start();}}</span>

等待喚醒機制

涉及的方法:

wait:將同步中的線程處於凍結狀態。釋放了執行權,釋放了資格。同時將線程Object Storage Service到線程池中。

notify:喚醒線程池中某一個等待線程。

notifyAll:喚醒的是線程池中的所有線程

注意:

1:這些方法都需要定義在同步中。

2:因為這些方法必須要標示所屬的鎖。

   你要知道 A鎖上的線程被wait了,那這個線程就相當於處於A鎖的線程池中,只能A鎖的notify喚醒。

3:這三個方法都定義在Object類中。為什麼操作線程的方法定義在Object類中?

   因為這三個方法都需要定義同步內,並標示所屬的同步鎖,既然被鎖調用,而鎖又可以是任意對象,那麼能被任意對象調用的方法一定定義在Object類中。

wait和sleep區別:

分析這兩個方法:從執行權和鎖上來分析:

wait:可以指定時間也可以不指定時間。不指定時間,只能由對應的notify或者notifyAll來喚醒。

sleep:必須指定時間,時間到自動從凍結狀態轉成運行狀態(臨時阻塞狀態)。

wait:線程會釋放執行權,而且線程會釋放鎖。

Sleep:線程會釋放執行權,但是不釋放鎖。
















聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.