注:本文轉載自coder-pig
原文請戳:http://blog.csdn.net/coder_pig/article/details/22791141
一口吃不成一個胖子,所以咱們慢慢來,堅持就好。。。
Java多線程:
前言:
當我們使用電腦時,可以一邊聽歌,一邊和小學生打lol,一邊用QQ和基友聊天;如果你夠屌的話,還可以
用wps做實驗報告呢= =;那麼CPU怎麼同時敢這麼多事情呢?這就涉及到兩個名詞:多進程(Process)和多線程(Thread)
Java語言的一個重要特點就是對多線程的支持,使得開發人員可以開發出同時處理多個任務的application,實現
任務的併發處理!
PS:因爲多線程灰常的重要,所以這裏分開兩節講,不讓讀者有太大的負擔,這一節稍微簡單一點
好了廢話不多說!
相關概念:
線程的生命週期:
Java線程的創建:
代碼示例:
以下兩個代碼實現的都是同一個功能;
完成計時的功能,每秒打印一行
繼承Thread類:
- package com.jay.example;
- //自定義一個線程類繼承Thread類,重寫Thread中的run()方法
- class MyThread extends Thread
- {
- public MyThread() {
- //這裏是標識進程名的,可以通過getName()獲得進程的名稱
- super("MyThread");
- System.out.println("新建一個線程:"+getName());
- }
- //重寫的run()方法;通過循環打印1~10;
- //而每秒打印一行的功能是添加了sleep(1000); 1000毫秒 = 1秒
- //使用sleep需要對異常進行捕獲
- public void run() {
- for(int i = 1;i <= 3;i++)
- {
- System.out.println(Thread.currentThread().getName() +" : " + i );
- try {
- sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
- public class ThreadTest1 {
- public static void main(String[] args) {
- Thread t = Thread.currentThread();
- //新建一個MyThread的線程對象
- MyThread mt = new MyThread();
- //通過線程.start()方法啓動線程中的run()方法
- mt.start();
- //這裏是爲了讓線程運行完畢後纔打印主線程中的語句
- //join():讓異步執行的線程改成同步執行,直到這個線程退出
- //程序纔會繼續執行
- try {
- mt.join();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("打印完畢!");
- }
- }
運行截圖:
實現Runnable接口:
- package com.jay.example;
- //該代碼實現了Runnable接口,重寫了其中的run()方法
- class MyThread2 implements Runnable
- {
- public void run() {
- for(int i = 1;i <= 3;i++)
- {
- System.out.println(Thread.currentThread().getName() +" : " + i );
- //調用sleep方法需要對異常進行補貨
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }
- }
- public class ThreadTest2 {
- public static void main(String[] args) {
- //實例化自定義線程
- MyThread2 th = new MyThread2();
- //新建一個線程對象,把子線程對象作爲參數傳入,後面的參數是進程的name
- Thread td = new Thread(th,"MyThread2");
- td.start();
- }
- }
運行截圖:
多線程的控制:
都說了是多線程,那麼就是有多個線程同時運行,這就涉及到了多線程之間的控制了
多個線程的併發執行:
如果當前有多個線程在併發執行的話,多次運行的結果可能不是唯一的
Java對於線程啓動後唯一能保證的就是每個線程都被啓動並且結束,但對於哪個線程優先執行,何時執行都沒有保證
實例代碼:兩個進程打印的先後順序
- package com.jay.example;
- /*
- * 該程序演示的是線程的併發執行;
- * 我們定義一個MyThread1類實現Runnable接口
- * 重寫run()方法,在該方法中打印區分哪個線程的語句
- * 在main方法中實例化兩個MyThread1對象並且作爲參數
- * 傳遞給Thread對象,start()方法調用,輸出結果!
- * */
- class MyThread1 implements Runnable
- {
- public void run() {
- for(int i=0;i<10;i++)
- {
- System.out.println(Thread.currentThread().getName());
- }
- }
- }
- public class ThreadTest1 {
- public static void main(String[] args) {
- MyThread1 my1 = new MyThread1();
- MyThread1 my2 = new MyThread1();
- Thread t1 = new Thread(my1,"第一個線程被調用!");
- Thread t2 = new Thread(my2,"第二個線程被調用!");
- t1.start();
- t2.start();
- }
- }
運行截圖:
結果分析:
運行後發現,線程是交替交替運行的,也說明了Java多線程的特點;
而這裏又涉及到另一個概念了,就是線程的優先級
線程的優先級:
操作系統中的線程是具有優先級的,Java運行環境採用的是固定優先級調度算法(Fixed Priority Scheduling)
某一時刻有多個線程在運行,JVM會選擇優先級最高的線程運行,優先級較高的線程會搶佔cpu時間,
相同優先級的線程可能順序執行,也可能分時執行,取決於本地操作系統的線程調度策略
雖然說在任意時刻,應該是最高優先級的線程在運行,但是這樣是不能保證的,調度程序有可能選擇
優先級較低的線程以避免飢餓(starvation)
搶佔(pre-empt)策略:當一個優先級更高的進程進行可運行狀態時發生搶佔,終止當正在運行的進程而
立即去執行優先級更高 的線程,而兩個相同優先級的線程則採用循環執行策略(round-robin);
飢餓(starvation):當等待時間給進程的推進和響應帶來明顯的影響時就稱發生了進程飢餓
飢餓到一定程度的進程所賦予的任務即使完成,也已經不再具有實際意義,該進程就餓死了!
如:一個打印機文件打印的問題,採用短文件有限的策略,如果短文件太多,
那麼長文件一直無限期的推遲,那麼還打印個毛啊!
在Java中,優先級從,0~10,整數數值越大,優先級越大;默認線程優先級爲5;可以通過setPriority()方法改變線程的優先級;
通過getPriority()獲得線程的優先級;注意Thread類優先級的幾個字段:
MIN_PRIORITY(最低優先級1) MAX_PRIORITY(最高優先級10) NORM_PRIORITY(默認優先級5)
只能說優先級高的線程有更大的可能性獲得CPU,但這並不能說優先級較低的線程就永遠最後執行!!
代碼示例:
- package com.jay.example;
- /*
- * 該代碼演示的是線程優先級的使用
- * 定義了三個線程,重新設置了優先級
- * 在調用start()方法輸出哪個線程被調用了
- * */
- class MyThread1 implements Runnable
- {
- public void run() {
- for(int i=0;i<10;i++)
- {
- System.out.println(Thread.currentThread().getName());
- }
- }
- }
- public class ThreadTest1 {
- public static void main(String[] args) {
- MyThread1 my1 = new MyThread1();
- MyThread1 my2 = new MyThread1();
- MyThread1 my3 = new MyThread1();
- Thread t1 = new Thread(my1,"第一個線程被調用!");
- Thread t2 = new Thread(my2,"第二個線程被調用!");
- Thread t3 = new Thread(my2,"第三個線程被調用!");
- t1.setPriority(Thread.MIN_PRIORITY);
- t2.setPriority(Thread.MAX_PRIORITY);
- t3.setPriority(Thread.NORM_PRIORITY);
- System.out.println("設置後的線程優先級依次是:" + t1.getPriority() + " " + t2.getPriority() + " "+ t3.getPriority());
- t1.start();
- t2.start();
- t3.start();
- }
- }
運行截圖:
結果分析:
進程優先級最低的第一個執行?這是不是有點意外,其實不然,只要讀者運行幾次會發現打印結果都是不一樣的;
好像我們設置了優先級沒什麼用,對吧?也不能說沒用,只是給系統提供一個參考吧,讓他優先考慮分配CPU時間給該線程
除了優先級,操作系統還要考慮其他的一些東東哦!當然,我們在開發中肯定要有方法來規定線程的執行的!那就是線程調度的一些方法!
線程調度:
按照前面的說法,線程的運行是沒有保證的,那麼我們還怎麼玩多線程;顯然是不可能的;
Thread類給我們提供了一些用於實現線程調度的方法
sleep():
讓線程睡一會兒,參數是毫秒級,使用該方法時需要捕獲InterruptedException異常
eg:Thread.sleep(1000); //讓當前線程休眠1秒
yield():
暫停當前線程,運行其他線程,即讓出CPU運行時間,進入就緒狀態,從而讓其他的進程獲得CPU時間執行
不過對於搶佔式操作系統沒有什麼意義,或者說沒效果
join():
想讓一個線程等待另一個線程執行完畢後再繼續執行的情況下使用
可以添加參數,long類型的毫秒,等待該線程終止的時間最長爲多少毫秒
可以參考第一個例子,比如m.join,那麼等m進程執行完畢後才能執行其他程序這樣!
鎖與線程同步:
當有兩個或以上線程操作同一時刻併發地訪問同一資源,可能會帶來一些問題;
比如比較經典的生產者和消費者的問題,如果生產者還沒生產,那麼消費者就進行消費了,那麼這樣肯定是錯誤的!
所以這裏要引入一個鎖的概念:就是對共享的資源加以限制,讓該資源同一時間僅僅只能夠有一個線程對他進行訪問
使用實例:
定義一個資源類,在裏面實現兩個同步方法,再對其進行訪問
代碼:
- package com.jay.example;
- /*
- * 該代碼演示的是同步方法與同步塊的使用
- * 定義一個資源類,分別用同步方法和同步代碼塊定義兩個同步的方法
- * 然後定義一個線程類,在線程類中實現對該資源的訪問
- * 因爲加了同步鎖,所以同一時間只允許一個進程訪問
- * 實例化兩個自定義線程對象,作爲傳給兩個Thread對象
- * 調用start()方法,啓動自定義線程的run()方法,完成打印輸出
- * */
- class Source
- {
- //定義同步方法
- synchronized void method1()
- {
- System.out.println("進入方法一,獲得鎖");
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("方法一執行完畢,釋放鎖");
- }
- //在該方法中使用同步塊
- void method2()
- {
- synchronized(this)
- {
- System.out.println("進入方法二,獲得鎖");
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("方法二執行完畢,釋放鎖");
- }
- }
- }
- //定義一個操作資源的線程類:
- class MyThread implements Runnable
- {
- private String name;
- Source s = null;
- MyThread(Source s,String name)
- {
- this.s = s;
- this.name = name;
- }
- public void run() {
- if(name.equals("method1"))s.method1();
- else
- {System.out.println("方法二啓動,準備調用第二個方法");s.method2();}
- }
- }
- public class ThreadTest2 {
- public static void main(String[] args) {
- //實例化一個資源對象傳入到線程類中
- Source s = new Source();
- //創建兩個MyThread對象
- MyThread mt1 = new MyThread(s, "method1");
- MyThread mt2 = new MyThread(s, "method2");
- //自定義線程對象傳入Thread對象中
- Thread t1 = new Thread(mt1);
- Thread t2 = new Thread(mt2);
- //調用start方法啓動兩個線程
- t1.start();
- t2.start();
- }
- }
運行截圖:
線程死鎖問題:
簡單的說就說,兩個線程都在等待對方擁有的資源,就是A等B的資源,B也在等A的資源,
我不給你你不給我,就一直在那裏糾結,一直無限的等下去這個就是死鎖了
一個簡單的死鎖例子:
該代碼演示的是定義了一個資源類,然後自定義了一個線程類,該類在run方法中使用了兩個synchronized嵌套;
就是每個線程對象需要獲得兩個資源纔可以運行;爲了方便演示,我故意實例化了三個資源對象和自定義線程對象
一號線程需要得到1,2資源,二號線程需要得到2,3資源,三號線程需要得到3,1資源
然後每個線程都獲得了對應的一個資源,一直在等另一個資源;一等二的2資源,二等三的3資源,三等一的1資源
就這樣一直僵持,就形成了死鎖
代碼演示:
- package com.jay.example;
- /*
- * 該代碼演示的是進程的死鎖
- * 定義了一個資源類,在自定義線程的run()方法中
- * 使用了synchronized的嵌套,實現一個線程需要兩個資源纔可以運行
- * 在main()方法中依次實例化了三個:資源對象,自定義線程對象
- * 讓1線程需要1.2號資源;讓2線程需要2.3號資源;讓1線程需要3.1號資源
- * 結果就互相等唄,就形成了死鎖
- * */
- //定義資源類
- class Resource
- {
- String name;
- public Resource(String name) {
- this.name = name;
- }
- }
- //自定義線程類
- class MyThread2 implements Runnable
- {
- Resource res1 = null;
- Resource res2 = null;
- String name;
- //構造方法的重寫
- public MyThread2(Resource res1,Resource res2,String name) {
- this.res1 = res1;
- this.res2 = res2;
- this.name = name;
- }
- public void run() {
- //外層synchronized塊
- synchronized (res1) {
- System.out.println(this.name + " 獲得了 "+ res1.name);
- try{Thread.sleep(1000);}catch(InterruptedException e){e.printStackTrace();};
- System.out.println(this.name + "等待 "+res2.name + "釋放資源!");
- //裏層syncronized塊
- synchronized (res2) {
- System.out.println(this.name + "獲得 了 " +res2.name);
- try{Thread.sleep(1000);}catch(InterruptedException ex){ex.printStackTrace();};
- }
- }
- }
- }
- public class ThreadTest3 {
- public static void main(String[] args) {
- //創建資源對象
- Resource res1 = new Resource("資源一");
- Resource res2 = new Resource("資源二");
- Resource res3 = new Resource("資源三");
- //實例化三個自定義線程對象,並且把他們傳給Thread對象
- MyThread2 mt1 = new MyThread2(res1,res2, "線程一");
- MyThread2 mt2 = new MyThread2(res2,res3, "線程二");
- MyThread2 mt3 = new MyThread2(res3,res1, "線程三");
- Thread t1 = new Thread(mt1);
- Thread t2 = new Thread(mt2);
- Thread t3 = new Thread(mt3);
- t1.start();
- t2.start();
- t3.start();
- }
- }
運行截圖:
結果分析:
如圖,就這樣卡住了,但是顯示程序還在運行,就這樣一直在那裏等啊等
這樣的程序是已經死掉的沒有意義的程序了,所以在編寫代碼的時候要
預防死鎖!這就是操作系統的概念了,有興趣的可以看下參考系統的書籍
總結:
在這一節中,我們瞭解了多線程的相關概念,生命週期;
以及創建線程的兩個方法,多個線程的併發執行,線程的優先級
線程調度的三個常用方法;鎖與線程同步的問題等
大家回去要自己動手寫下代碼理解下,有什麼問題可以寫代碼測試一下
ps:清明因爲回家祭祖,就沒更新了,今天又繼續拉,
如果對本文有什麼疑問,意見,紕漏,建議的話,
歡迎指出,感激萬分,謝謝!O(∩_∩)O!