Java基礎之線程
一、線程的基本概念
程序(program)是為完成特定任務、用某種語言編寫的一組指令的集合。即指一段靜態的代碼,靜態對象。
進程(process)是程序的一次執行過程,或是正在運行的一個程序。動態過程:有它自身的產生、存在和消亡的過程。 如:運行中的QQ,運行中的MP3播放器 程序是靜態的,進程是動態的
線程(thread),進程可進一步細化為線程,是一個程序內部的一條執行路徑。 若一個程序可同一時間執行多個線程,就是支持多線程的。
進程與多線程:
什麽時候需要多線程:
1、程序需要同時執行兩個或多個任務。
2、程序需要實現一些需要等待的任務時,如用戶輸入、文件讀寫操作、網絡操作、搜索等。
3、需要一些後臺運行的程序時。
註意:每個Java程序都有一個隱含的主線程: main 方法
二、線程的創建與運行
Java語言的JVM允許程序運行多個線程,它通過java.lang.Thread類來實現。
Thread類的特性:
每個線程都是通過某個特定Thread對象的run()方法來完成操作的,經常把run()方法的主體稱為線程體。
通過該Thread對象的start()方法來調用這個線程。
1、Thread類
構造方法
Thread():創建新的Thread對象
Thread(String threadname):創建線程並指定線程實例名
Thread(Runnable target):指定創建線程的目標對象,它實現了Runnable接口中的run方法
Thread(Runnable target, String name):創建新的Thread對象
2、創建線程的兩種方式
(1)、繼承Thread類
1)、 定義子類繼承Thread類。
2) 、子類中重寫Thread類中的run方法。
3) 、創建Thread子類對象,即創建了線程對象。
4) 、調用線程對象start方法:啟動線程,調用run方法。
public class MyThread extends Thread {public void run(){ for (int a = 1; a < 10; a++){ System.out.println(Thread.currentThread().getName() + a); } } }
public class Test_Thread { public static void main(String[] args) { for (int a = 1; a < 10; a++){ System.out.println(Thread.currentThread().getName() + a);//主線程 } MyThread myThread1 = new MyThread();//開啟第一個線程 myThread1.start(); MyThread myThread2 = new MyThread();//開啟第二個線程 myThread2.start(); MyThread myThread3 = new MyThread();//開啟第三個線程 myThread3.start(); } }
. (2)、實現Runnable接口
1)、定義子類,實現Runnable接口。
2)、子類中重寫Runnable接口中的run方法。
3)、通過Thread類含參構造器創建線程對象。
4)、將Runnable接口的子類對象作為實際參數傳遞給 Thread類的構造方法中。
5)、調用Thread類的start方法:開啟線程,調用 Runnable子類接口的run方法。
public class MyThread implements Runnable { int a = 30; @Override public void run() { for (int i = 0; i < a; i++){ System.out.println(Thread.currentThread().getName() + i); } } }
public class Test_Thread { public static void main(String[] args) { for (int a = 0; a < 10; a++){ System.out.println(Thread.currentThread().getName()+a);//主線程 } MyThread myThread = new MyThread();//創建線程對象 Thread thread1 = new Thread(myThread);//開啟第一個線程 thread1.start(); Thread thread2 = new Thread(myThread);//開啟第二個線程 thread2.start(); Thread thread3 = new Thread(myThread);//開啟第三個線程 thread3.start(); } }
(3)、實現資源共享
public class Test_Thread { public static void main(String[] args) { for (int a = 0; a < 10; a++){ System.out.println(Thread.currentThread().getName() + a); } MyThread myThread = new MyThread(); Thread thread1 = new Thread(myThread); thread1.start(); thread1.setPriority(Thread.MAX_PRIORITY); Thread thread2 = new Thread(myThread); thread2.start(); Thread thread3 = new Thread(myThread); thread3.start(); thread3.setPriority(Thread.MIN_PRIORITY); } }
public class MyThread implements Runnable{ int a = 10; @Override public void run() { while (true){ if (a < 0){ break; } System.out.println(Thread.currentThread().getName() + a--); } } }
3、兩種創建線程的區別與聯系
(1)、繼承Thread: 線程代碼存放Thread子類run方法中。 實現Runnable:線程代碼存在接口的子類的run方法。
(2)、實現方式創建線程的好處
1)避免了單繼承的局限性
2)多個線程可以共享同一個接口實現類的對象,非常適合多個相同線程來處理同一份資源。
4、Thread類的有關方法
(1)、第一類
void start(): 啟動線程,並執行對象的run()方法
run(): 線程在被調度時執行的操作 String
getName(): 返回線程的名稱
void setName(String name):設置該線程名稱
static currentThread(): 返回當前線程
(2)、第二類(線程的調度)
調度策略:
時間片:
搶占式:高優先級的線程搶占CPU
Java的調度方法: 同優先級線程組成先進先出隊列(先到先服務),使用時間片策略 對高優先級,使用優先調度的搶占式策略
線程的優先級控制:
MAX_PRIORITY(10);
MIN _PRIORITY (1);
NORM_PRIORITY (5);
涉及的方法: getPriority() :返回線程優先值
setPriority(int newPriority) :改變線程的優先級 線程創建時繼承父線程的優先級
(3)、第三類
static void yield():線程讓步 暫停當前正在執行的線程,把執行機會讓給優先級相同或更高的線程 若隊列中沒有同優先級的線程,忽略此方法
join() :當某個程序執行流中調用其他線程的 join() 方法時,調用線程將被阻塞,直到 join() 方法加入的 join 線程執行完為止 低優先級的線程也可以獲得執行
static void sleep(long millis):(指定時間:毫秒) 令當前活動線程在指定時間段內放棄對CPU控制,使其他線程有機會被執行,時間到後重排隊。 拋出InterruptedException異常
stop(): 強制線程生命期結束
boolean isAlive():返回boolean,判斷線程是否還活著
5、線程的分類
Java中的線程分為兩類:一種是守護線程,一種是用戶線程。
它們在幾乎每個方面都是相同的,唯一的區別是判斷JVM何時離開。
守護線程是用來服務用戶線程的,通過在start()方法前調用thread.setDaemon(true)可以把一個用戶線程變成一個守護線程。 Java垃圾回收就是一個典型的守護線程。 若JVM中都是守護線程,當前JVM將退出。
6、為什麽要使用多線程
背景:只使用單個線程完成多個任務(調用多個方法),肯定比用多個線程來完成用的時間更短,為何仍需多線程呢?
(1)多線程程序的優點: 提高應用程序的響應。對圖形化界面更有意義,可增強用戶體驗。
(2)提高計算機系統CPU的利用率。
(3)改善程序結構,將既長又復雜的進程分為多個線程,獨立運行,利於理解和修改。
7、線程的生命周期
要想實現多線程,必須在主線程中創建新的線程對象。Java語言使用Thread類及其子類的對象來表示線程,在它的一個完整的生命周期中通常要經歷如下的五種狀態:
新建: 當一個Thread類或其子類的對象被聲明並創建時,新生的線程對象處於新建狀態。
就緒:處於新建狀態的線程被start()後,將進入線程隊列等待CPU時間片,此時它已具備了運行的條件。
運行:當就緒的線程被調度並獲得處理器資源時,便進入運行狀態, run()方法定義了線程的操作和功能。
阻塞:在某種特殊情況下,被人為掛起或執行輸入輸出操作時,讓出 CPU 並臨時中止自己的執行,進入阻塞狀態。
死亡:線程完成了它的全部工作或線程被提前強制性地中止。
三、線程的同步
多個線程執行的不確定性引起執行結果的不穩定 多個線程對賬本的共享,會造成操作的不完整性,會破壞數據。
舉個例子:
(1)、你的賬戶上有3000元。有一個存折和一張卡對應的是這一個賬戶。
(2)、你去銀行櫃臺取2000元,存折顯示你賬戶有3000元,櫃臺員工正在給你辦理。但是同時你的老婆在取款機上也取2000元,賬戶也顯示有3000元。這樣你們就取出4000元,這是很明顯的錯誤。
(3)、解決辦法就是,在你取錢的同時,你的賬戶是不允許有其他人取錢的,你取過錢,你的賬戶的錢減去2000元後,才能夠其他取錢操作(資源獨占)。
線程安全產生的原因:
當多條語句在操作同一個線程共享數據時,一個線程對多條語句只執行了一部分,還沒有執行完,另一個線程參與進來執行。導致共享數據的錯誤。
解決辦法:
對多條操作共享數據的語句,只能讓一個線程都執行完,在執行過程中,其他線程不可以參與執行。
package main.dyh.test_two; class Ticket implements Runnable{ private static int tick = 100; public void run(){ while(true){ if(tick>0){ System.out.println(Thread.currentThread().getName()+"售出車票,tick號為:"+ tick--); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } else { break; } } } } class TicketDemo{ public static void main(String[] args) { Ticket t = new Ticket(); Thread t1 = new Thread(t); Thread t2 = new Thread(t); Thread t3 = new Thread(t); t1.setName("t1窗口"); t2.setName("t2窗口"); t3.setName("t3窗口"); t1.start(); t2.start(); t3.start(); } }
安全問題的解決:
Synchronized的使用方法:
(1)、synchronized (對象){
// 需要被同步的代碼;
}
即有synchronized關鍵字修飾的方法。
由於java的每個對象都有一個內置鎖,當用此關鍵字修飾方法時,內置鎖會保護整個方法。在調用該方法前,需要獲得內置鎖,否則就處於阻塞狀態。
synchronized關鍵字也可以修飾靜態方法,此時如果調用該靜態方法,將會鎖住整個類。
synchronized還可以放在方法聲明中,表示整個方法 為同步方法。 例如:
(2)、public synchronized void show (String name){
….
}
即有synchronized關鍵字修飾的語句塊。
被該關鍵字修飾的語句塊會自動被加上內置鎖,從而實現同步
註:同步是一種高開銷的操作,因此應該盡量減少同步的內容。
通常沒有必要同步整個方法,使用synchronized代碼塊同步關鍵代碼即可。
互斥鎖:
在Java語言中,引入了對象互斥鎖的概念,來保證共享數據操作的完整性。
每個對象都對應於一個可稱為“互斥鎖”的標記,這個標記用來保證在任一時刻,只能有一個線程訪問該對象。
關鍵字synchronized 來與對象的互斥鎖聯系。當某個對象用synchronized修飾時,表明該對象在任一時刻只能由一個線程訪問。
同步的局限性:導致程序的執行效率要降低 同步方法(非靜態的)的鎖為this。
同步方法(靜態的)的鎖為當前類本身。
package main.dyh.test_two; class Ticket implements Runnable{ private static int tick = 100; public void run(){ while(true){ synchronized (this){ if(tick>0){ System.out.println(Thread.currentThread().getName()+"售出車票,tick號為:"+ tick--); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } else break; } } } } class TicketDemo{ public static void main(String[] args) { Ticket t = new Ticket(); Thread t1 = new Thread(t); Thread t2 = new Thread(t); Thread t3 = new Thread(t); t1.setName("t1窗口"); t2.setName("t2窗口"); t3.setName("t3窗口"); t1.start(); t2.start(); t3.start(); } }
單例模式之懶漢模式:
class Singleton { private static Singleton instance = null; private Singleton(){} public static Singleton getInstance(){ if(instance==null){ synchronized(Singleton.class){ if(instance == null){ instance=new Singleton(); } } } return instance; } } public class TestSingleton{ public static void main(String[] args){ Singleton s1=Singleton.getInstance(); Singleton s2=Singleton.getInstance(); System.out.println(s1==s2); } }View Code
四、線程死鎖
死鎖:不同的線程分別占用對方需要的同步資源不放棄,都在等待對方放棄自己需要的同步資源,就形成了線程的死鎖
解決方法: 專門的算法、原則 盡量減少同步資源的定義
五、線程通信
wait() 與 notify() 和 notifyAll()
- wait():令當前線程掛起並放棄CPU、同步資源,使別的線程可訪問並修改共享資源,而當前線程排隊等候再次對資源的訪問
- notify():喚醒正在排隊等待同步資源的線程中優先級最高者結束等待
- notifyAll ():喚醒正在排隊等待資源的所有線程結束等待
- Java.lang.Object提供的這三個方法只有在synchronized方法或synchronized代碼塊中才能使用,否則會報java.lang.IllegalMonitorStateException異常
(1)、wait()
- 在當前線程中調用方法: 對象名.wait()
- 使當前線程進入等待(某對象)狀態 ,直到另一線程對該對象發出 notify (或notifyAll) 為止。
- 調用方法的必要條件:當前線程必須具有對該對象的監控權(加鎖)
- 調用此方法後,當前線程將釋放對象監控權 ,然後進入等待
- 在當前線程被notify後,要重新獲得監控權,然後從斷點處繼續代碼的執行。
(2)、notify()與notifyAll()
- 在當前線程中調用方法: 對象名.notify()
- 功能:喚醒等待該對象監控權的一個線程。
- 調用方法的必要條件:當前線程必須具有對該對象的監控權(加鎖)
經典案例:生產者消費者問題
生產者(Productor)將產品交給店員(Clerk),而消費者(Customer)從店員處取走產品,店員一次只能持有固定數量的產品(比如:20),如果生產者試圖生產更多的產品,店員會叫生產者停一下,如果店中有空位放產品了再通知生產者繼續生產;如果店中沒有產品了,店員會告訴消費者等一下,如果店中有產品了再通知消費者來取走產品。
這裏可能出現兩個問題:
生產者比消費者快時,消費者會漏掉一些數據沒有取到。
消費者比生產者快時,消費者會取相同的數據。
package main.dyh.test_two; public class TestProduct { public static void main(String[] args) { Clerk clerk = new Clerk(); Thread productorThread = new Thread(new Productor(clerk)); Thread consumerThread = new Thread(new Consumer(clerk)); productorThread.start(); consumerThread.start(); } } class Clerk{ //售貨員 private int product = 0; public synchronized void addProduct(){ if(product >= 20){ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } }else{ product++; System.out.println("生產者生產了第"+product+"個產品"); notifyAll(); } } public synchronized void getProduct(){ if(this.product <= 0){ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } }else{ System.out.println("消費者取走了第"+product+"個產品"); product--; notifyAll(); } } } class Productor implements Runnable{ //生產者 Clerk clerk; public Productor(Clerk clerk){ this.clerk = clerk; } public void run(){ System.out.println("生產者開始生產產品"); while(true){ try { Thread.sleep((int)Math.random()*1000); } catch (InterruptedException e) { } clerk.addProduct(); } } } class Consumer implements Runnable{ //消費者 Clerk clerk; public Consumer(Clerk clerk){ this.clerk = clerk; } public void run(){ System.out.println("消費者開始取走產品"); while(true){ try { Thread.sleep((int)Math.random()*1000); } catch (InterruptedException e) { } clerk.getProduct(); } } }
Java基礎之線程