標籤:log except 靜態 java代碼 延時 hdr tom 通訊 記憶體回收
一、線程的調度與控制
通常電腦只有一個CPU,CPU在某一個時刻只能執行一條命令,線程只有得到CPU時間片,也就是使用權,才可以執行命令。在單核CPU的機器上,線程並不是並行啟動並執行。java虛擬機器主要負責線程調度,取得CPU的使用權,目前有兩種調度模型:分時調度模型和搶佔式調度模型,Java使用搶佔式調度模型。
分時調度模型:所有線程輪流使用CPU的使用權,平均分配每個線程佔用CPU的時間片
搶佔式調度模型:優先順序高的線程擷取CPU時間片相對會多一些,如果線程的優先順序相同,那麼會隨機選用一個 。
下面我們將通過代碼來瞭解如何在java代碼中使用線程,一般我們在工作中是不會去寫多線程的代碼,但如果你是寫伺服器端代碼,如在IBM公司或者寫機器之間的通訊,自己寫一個伺服器端的代碼,那我們就必須考慮多線程了,正常我們用的伺服器像tomcat等已經底層實現多線程了。
1、關於線程的一些基本方法
(1)擷取當前線程對象 Thread.currentThread();
(2)給線程起名 t.setName("t1");
(3)擷取線程的名字 t.getName();
public class _04線程調度 { public static void main(String[] args){ //如何擷取當前線程對象? //t儲存的記憶體位址指向的線程是“主線程對象” Thread t=Thread.currentThread(); //擷取線程的名字 System.out.println(t.getName()); Thread t1=new Thread(new Processor03()); //給線程起名 t1.setName("t1"); t1.start(); Thread t2=new Thread(new Processor03()); //給線程起名 t2.setName("t2"); t2.start(); }}class Processor03 implements Runnable{ public void run(){ //t儲存的記憶體位址指向的線程是“t1線程對象” Thread t=Thread.currentThread(); System.out.println(t.getName()); }}
2、線程的優先順序
線程優先順序高的線程擷取的cpu時間片相對多一些,也就是獲得執行的機會會大一些,優先順序 1-10
線程的優先順序主要分三種:MAX_PRIORITY(最進階)(10);MIN_PRIORITY(最低級)(1);NOM_PRIORITY(標準)預設(5)
擷取以及設定線程的優先順序方法為Thread.getPriority()和Thread.setPriority(),
從運行結果中可以看到t1線程和t2線程搶奪cpu時間片,由於設定了t1的優先順序高一些,所以t1搶奪的時間片會相對多一些,所以執行的會多一些
3、Thrad.sleep(ms),線程休眠的方法,關於線程休眠有如下幾點注意:
1.Thread.sleep;
2.sleep方法是一個靜態方法
3.該方法的作用,阻塞當前線程(回顧一中線程的執行流),騰出CPU讓給其他線程
4.此方法是靜態方法,所以"引用."和"類名."是一樣的
下面的代碼中,t.sleep等同於Thread.sleep,故阻塞的並不是t線程,而是阻塞的主線程。
public class _07面試題 { public static void main(String[] args) throws Exception{ Thread t=new Processor07(); t.setName("t"); t.start(); //等同於Threadsleep //Thread.sleep()是靜態方法,所以t.sleep()等同於Thread.sleep() //所以t線程是不會阻塞的,阻塞的是主線程 t.sleep(5000); }}class Processor07 extends Thread{ public void run(){ for(int i=0;i<3000;i++){ System.out.println(Thread.currentThread().getName()+"---->"+i); } }}
5.中斷休眠的方法
(1)使用異常機制 t.interrupt(),此interrupt會觸發Interrupted異常可以從休眠中退出。
(2)正常終止,正確的停止一個線程,在外面增加一個表示位
二、線程的同步(加鎖)
1、非同步編程模型與同步編程模型。
非同步編程模型:t1線程執行t1的,t2線程執行t2的。兩個線程誰也不等誰
同步編程模型:t1線程和t2線程執行,當t1線程必須等t2線程執行結束之後,t1線程才能執行,這是同步編程模型
2、什麼時候同步呢?為什麼要引入線程同步呢?
1.為了資料安全。暫時就不考慮效率了,儘管應用程式的使用率降低,但是為了保證資料是安全的必須加入同步機制,線程是同步的,所以程式就變成了單線程,就像兩條直線
原來是平行,現在要接在一起變成一根線。
比如現實生活中,我和你共用一個銀行賬戶,此時這個賬戶裡有5萬塊錢,今天我從這個賬戶裡取了兩萬塊錢,剛取出來,在賬戶還沒有減掉2萬塊錢的時候(此時賬戶裡還是5萬,但我已經取了2萬),
你也來訪問這個賬戶取了3萬塊,這時你返回給賬戶是5萬減3萬,賬戶還有2萬,這是取錢的反饋資訊也返回,賬戶還剩3萬。最後結果是,賬戶原本有5萬,我取了2萬,你取了3萬,賬戶裡還有3萬,這
樣就出現的賬戶安全問題,下面我們會用這個例子來做說明。
2.什麼條件下要使用線程同步?
(1):必須是多線程環境
(2):多線程環境共用同一個資料
(3):共用的資料涉及到修改操作
以下程式示範取款例子,首先我們看一下不使用線程同步機制,多線程同時對同一個賬戶進行取款操作,會出現什麼問題?
首先我們建立一個Account類類比賬戶,裡面有賬戶名稱以及賬戶金額,裡面提供一個withdraw()的提款方法,之後用建立Processer()類實現Runnable介面實現多線程
在構造器中加入賬戶類,run()方法中使用提款方法,在主方法中建立一個新賬戶,建立多線程,我們為了能夠實現會出現安全問題,在withdraw()方法中加了一個1s的sleep延時,
這樣當t1線程,或者t2線程執行時,一個線程執行後,延時1s內,另一個線程會運行這個方法,另一個線程取到的賬戶餘額就不是第一個線程剩下的餘額。而是最開始的餘額,我
們看到最後的結果是t1去了1000 t2取了1000 但是還剩4000元
public class _00線程的同步加鎖 { public static void main(String[] args) throws Exception{ //建立一個公用賬戶 Account act=new Account("actno--01",5000.0); //建立線程對同一個賬戶取款 Porcessor p=new Porcessor(act); Thread t1=new Thread(p); Thread t2=new Thread(p); t1.start(); //t1.sleep(2000); t2.start(); }}//取款線程class Porcessor implements Runnable{ //賬戶 Account act; //Constructor Porcessor(Account act){ this.act=act; } public void run(){ act.withdraw(1000.0); System.out.println("取款1000成功,餘額:"+act.getBalance()); }}//賬戶class Account{ private String actno; private double balance; public Account(){}; public Account(String actno,double balance){ this.actno=actno; this.balance=balance; } //setter and getter方法 public String getActno(){return this.actno;} public void setActno(String actno){this.actno=actno;} public double getBalance(){return this.balance;} public void setBalance(double balance){this.balance=balance;} //對外提供一個取款的方法 public void withdraw(double money){//對當前賬戶進行取款 double before=this.balance; double after=before-money; //延遲 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //更新 this.setBalance(after); }}
運行結果:
取款1000成功,餘額:4000.0取款1000成功,餘額:4000.0
3.使用線程同步 synchronized關鍵字
同步原理:
原理:t1線程和t2線程,t1線程執行到withdraw()方法裡,遇到了synchronized關鍵字,就會去找this對象鎖(本質是每個對象上都有一個0和1的標誌,我們形象的稱它為鎖),如果找到this對象鎖則進入同步語句塊中執行程式,當同步語句塊中的代碼結束之後,t1線程歸還this的對象鎖。
在本程式中,t1線程執行同步語句塊的過程中,如果t2也過來執行以下代碼,也遇到了synchronized關鍵字,也會去找this的對象鎖,但是該對象鎖被t1持有,只能在這等待this對象鎖歸還才能執行t2線程,
也就是說t1線程在sleep()1s的時候,t2線程也執行到這裡,發現t1還持有鎖,只能等t1線程把賬戶中的錢都減掉才可以執行t2線程的代碼。我們發現啟動並執行結果是正確的。
但我們在啟動並執行過程中會發現,啟動並執行時間變長了。因為t2線程會等待。
class Account01{ private String actno; private double balance; public Account01(){}; public Account01(String actno,double balance){ this.actno=actno; this.balance=balance; } //setter and getter方法 public String getActno(){ return this.actno; } public void setActno(String actno){ this.actno=actno; } public double getBalance(){ return this.balance; } public void setBalance(double balance){ this.balance=balance; } //對外提供一個取款的方法 //public void synchronized withdraw(double money){ //} synchronized添加到成員方法上,線程拿走的也是this對象鎖 public void withdraw(double money){//對當前賬戶進行取款 //把需要同步的代碼,放到同步語句塊中 synchronized(this){ double before=this.balance; double after=before-money; //延遲 try{Thread.sleep(1000);}catch(Exception e){} //更新 this.setBalance(after); } }}
運行結果:
取款1000成功,餘額:4000.0取款1000成功,餘額:3000.0
4.使用setDaemon()方法,添加守護線程。
從線程類型上分可以分為,使用者線程(以上講的都是使用者線程),另一個是守護線程。守護線程是這樣的,所有的使用者線程結束生命週期,守護線程才會結束生命週期
只要有一個使用者線程存在,那麼守護線程就不會結束,例如java中著名的記憶體回收行程就是一個守護線程,只有應用程式中所有的線程結束,它才會結束,
守護線程一般都是無限執行的。我們可以使用setDaemon()方法將一個線程變為守護線程
下面的程式如果沒有setDaemon()方法,那麼t1線程會一下運行下去,但是加了setDaemon()方法,t1變成守護線程,當主線程運行完後,t1線程就會結束。
public class _00守護線程 { public static void main(String[] args) throws Exception{ Thread t1=new Processor(); t1.setName("守護線程"); //將t1這個使用者線程修改成守護線程 t1.setDaemon(true); t1.start(); for(int i=0;i<10;i++){ System.out.println(Thread.currentThread().getName()+"-->"+i); Thread.sleep(1000); } }}class Processor extends Thread{ public void run(){ int i=0; while(true){ i++; System.out.println(Thread.currentThread().getName()+"-->"+i); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } }}
線程基礎的一些理解(二)