JAVA多線程編程實例詳解
本文實例講述了JAVA多線程編程。分享給大家供大家參考,具體如下:
- 進程是系統進行資源調度和分配的一個獨立單位。
- 進程的特點
獨立性:進程是系統中獨立存在的實體,擁有自己的獨立資源和私有空間。在沒有經過進程本身允許的情況下,不能直接訪問其他進程。
動態(tài)性:進程與程序的區(qū)別在于,前者是一個正在系統中活動的指令,而后者僅僅是一個靜態(tài)的指令集合
并發(fā)性:多個進程可以在單個處理器上并發(fā)執(zhí)行,而不受影響。
并發(fā)性和并行性的區(qū)別:
并行性:在同一時刻,有多條指令在多個處理器上同時執(zhí)行(多個CPU)
并發(fā)性:在同一時刻只能有一條指令執(zhí)行,但多個進程指令被快速輪換執(zhí)行,使得宏觀上具有多個進程同時執(zhí)行的效果(單核)。
- 通過繼承Thread類來創(chuàng)建并啟動多線程
public class Deom_1 extends Thread{ public void run(){ super.run(); System.out.println("MyThread01"); } public static void main(String[] args){ Deom_1 demo=new Deom_1(); demo.setName("Demo_1"); demo.start(); System.out.println("當前線程的名字:"+Thread.currentThread().getName()+" 運行結束"); } }
多次調用start會拋出
java.lang.IllegalThreadStateException
異常
如果只調用run(),不調用start()方法,就相當于調用了一個普通的函數,實際上還是在同一個線程中運行的run()方法。
- 多線程編程時不要忘記了Java程序運行時默認的主線程,main方法的方法體就是主線程的線程執(zhí)行體
- 使用繼承Thread類的方法來創(chuàng)建線程類,多條線程之間無法共享線程的實例變量
- 實現Runnable接口創(chuàng)建線程類
//通過實現Runnable接口來創(chuàng)建線程類 public class SecondThread implements Runnable { private int i ; //run方法同樣是線程執(zhí)行體 public void run() { for ( ; i < 20 ; i++ ) { //當線程類實現Runnable接口時, //如果想獲取當前線程,只能用Thread.currentThread()方法。 System.out.println(Thread.currentThread().getName() + " " + i); } } public static void main(String[] args) { for (int i = 0; i < 50; i++) { System.out.println(Thread.currentThread().getName() + " " + i); if (i == 20) { SecondThread st = new SecondThread(); //通過new Thread(target , name)方法創(chuàng)建新線程 new Thread(st , "新線程1").start(); new Thread(st , "新線程2").start(); } } } }
-
采用實現繼承方式的多線程:
編程相對簡單
多條線程之間無法共享線程類實例變量
繼承Thread類之后就不能繼承其他的父類 -
采用實現接口的方式的多線程:
線程類只是實現了Runnable接口,還可以繼承其他類
在這種方式下,可以多個線程共享同一個對象,非常適合多個相同線程來處理同一份資源的情況。
編程稍稍復雜一點 -
線程的生命周期:新建(NEW)、就緒(Runnable)、運行(Running)、阻塞(Blocked)、死亡(Dead)
-
當程序使用new關鍵字創(chuàng)建一個線程后,該線程就處于新建狀態(tài);當線程對象調用了start()方法之后,該線程就處于就緒狀態(tài);如果處于就緒狀態(tài)的線程獲得了CPU,開始執(zhí)行run方法的線程執(zhí)行體,則該線程就處于運行狀態(tài);但是它不可能一直處于運行狀態(tài),可能會被中斷進入阻塞狀態(tài);線程運行結束后就會進入到死亡狀態(tài)。
-
線程進入阻塞狀態(tài)的情況:
線程調用了sleep方法主動放棄所占有的資源
線程調用了一個阻塞式IO方法,在該方法返回的時候被阻塞
線程嘗試獲取一個同步監(jiān)聽器,但是被其他線程占有
在等待某個通知
調用線程suspend方法掛起,不推薦使用容易造成死鎖。 -
線程進入就緒狀態(tài)的情況:
調用sleep方法的線程經過了指定時間
線程調用的阻塞式IO方法已經返回
線程成功獲取試圖取得同步監(jiān)聽器
線程在等待某個通知,其他線程發(fā)出一個通知
處于掛起狀態(tài)的線程調用了resume恢復方法
進入阻塞狀態(tài)的線程在獲得執(zhí)行機會后重新進入就緒狀態(tài),而不是運行狀態(tài)。
- 線程進入死亡狀態(tài)的情況:
run()方法執(zhí)行完成,線程正常結束
線程拋出一個未捕獲的異常或者錯誤
直接調用該線程的stop方法來結束該線程——該方法容易導致死鎖,通常不推薦。
當主線程結束的時候,其他線程不受影響。一旦子線程啟動它就擁有和主線程一樣的地位。
-
不要試圖對一個已經死亡的線程調用start()方法使它重新啟動,會拋出
IllegalThreadStateException
異常。調用線程的isAlive()
方法可以測試某條線程是否死亡。 -
JAVA中控制線程的方法:
join線程
后臺線程
線程睡眠:sleep
線程讓步:yield
改變線程優(yōu)先級 -
join線程
join()
:等待被join的線程執(zhí)行完成
join(long millis)
:等待被join的線程時間最長millis毫秒
join(long millis, int nanos)
:等待被join的線程的時間最長millis毫秒加上nanos微秒 -
后臺線程:任務是給其他線程提供服務,成為“后臺線程”、“守候線程”。如果說有的前臺線程死亡,后臺線程會自動死亡。
調用Thread類的
setDaemon(true)
方法可以將指定線程設置為后臺線程。該方法一定要在啟動線程之前設置,否則會發(fā)生異常。同時提供isDaemon()
方法判斷是否是后臺線程。主線程一般默認為前臺線程。前臺線程創(chuàng)建的子線程默認是前臺,后臺線程創(chuàng)建的子線程默認是后臺。
- 改變線程的優(yōu)先級
PriorityTest low = new PriorityTest("低級"); low.start(); System.out.println("創(chuàng)建之初的優(yōu)先級:" + low.getPriority()); //設置該線程為最低優(yōu)先級 low.setPriority(Thread.MIN_PRIORITY);
PriorityTest high = new PriorityTest("高級"); high.start(); System.out.println("創(chuàng)建之初的優(yōu)先級:" + high.getPriority()); //設置該線程為最高優(yōu)先級 high.setPriority(Thread.MAX_PRIORITY);
每個線程的默認優(yōu)先級都與創(chuàng)建它的父線程具有相同的線程,在默認的情況下,main線程具有普通優(yōu)先級。
-
線程讓步
yield()方法是Thread提供的一個靜態(tài)方法,可以讓當前正在執(zhí)行的線程暫停轉入就緒狀態(tài)。等待下一次的重新調度。
實際上,當某個線程調用了yield()方法后只有優(yōu)先級相同或者高于當前線程的其他就緒狀態(tài)的線程才會獲得執(zhí)行的機會。 -
sleep和yield方法的區(qū)別
1、sleep方法暫停當前線程后,會給其他線程機會執(zhí)行,不會理會其他線程的優(yōu)先級。但是yield方法只會給優(yōu)先級相同或者更高的線程。
2、sleep方法會將線程轉入阻塞狀態(tài),直到經過阻塞時間才會轉入就緒狀態(tài)。而yield方法直接轉入就緒狀態(tài)。
3、sleep方法會拋出InterruptedException
異常,所以調用時需要顯示捕獲異常,yield方法不會拋出任何異常。
4、sleep方法比yield方法具有更多的移植性,通常不依靠yield方法控制并發(fā)線程執(zhí)行。 -
如果多個線程共同訪問1個對象中的實例變量,則有可能出現”非線程安全”
class SelfPrivateNum { private int num = 0; public void addI(String username) { try { if (username.equals("a")) { num = 100; System.out.println("a set over!"); Thread.sleep(2000); } else { num = 200; System.out.println("b set over!"); } System.out.println(username + " num=" + num); } catch (InterruptedException e) { e.printStackTrace(); } } } class ThreadAA extends Thread { private SelfPrivateNum numRef; public ThreadAA(SelfPrivateNum numRef) { super(); this.numRef = numRef; } @Override public void run() { super.run(); numRef.addI("a"); } } class ThreadBB extends Thread { private SelfPrivateNum numRef; public ThreadBB(SelfPrivateNum numRef) { super(); this.numRef = numRef; } @Override public void run() { super.run(); numRef.addI("b"); } } public class RunUnsafe { public static void main(String[] args) { SelfPrivateNum numRef = new SelfPrivateNum(); ThreadAA athread = new ThreadAA(numRef); athread.start(); ThreadBB bthread = new ThreadBB(numRef); bthread.start(); } }
- 同步方法:就是使用
synchronized
關鍵字來修飾某個方法。當多個線程調用這個方法的時,以排隊的方式進行處理。同步方法不具有繼承性。
class SelfPrivateNum2 { private int num = 0; public synchronized void addI(String username) { try { if (username.equals("a")) { num = 100; System.out.println("a set over!"); Thread.sleep(2000); } else { num = 200; System.out.println("b set over!"); } System.out.println(username + " num=" + num); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
關鍵字synchronized取的是鎖都是對象鎖,而不是代碼或者是方法當作鎖。當多個線程訪問的是同一個對象的同步方法的時候是排隊的,而當多個線程訪問多個對象的同步方法的時候運行的順序是異步的。
- A線程先持有object對象的鎖,B線程可以以異步的方式調用object對象總的非synchronizaed類型的方法
-
A線程先持有object對象的鎖,B線程如果調用object的synchronizaed類型的方法則需要等待,也就是同步。
-
臟讀
為了避免數據出現交叉的情況,使用synchronized關鍵字來進行同步。雖然在賦值時進行了同步,但是可能在取值的時候出現臟讀(dirtyRead)的現象。發(fā)生臟讀的情況是在讀取實例變量時。出現臟讀是應為getValue方法不是同步方法,解決方法可以定義為同步方法。
- synchronized方法是對當前對象進行加鎖,而synchronized代碼塊是對某一個對象進行加鎖。
//線程開始執(zhí)行同步代碼塊之前,必須先獲得對同步監(jiān)控器的鎖定 synchronized (Obj){ ……… //此處的代碼就是同步代碼塊 }
通常使用可能被并發(fā)訪問的共享資源充當同步監(jiān)視器。
- 對同步監(jiān)視器釋放的情況:
當前線程的同步方法或代碼塊執(zhí)行結束
當前線程的同步方法或代碼塊中遇到break、return終止了該代碼塊、方法的繼續(xù)執(zhí)行
當前線程的同步方法或代碼塊中出現未處理的Error或Exception
當前線程的同步方法或代碼塊中執(zhí)行了同步監(jiān)控器對象的wait()方法 -
以下情況不會釋放同步鎖:
當前線程的同步方法或代碼塊調用Thread.sleep()
,Thread.yield()
方法來暫停當前線程執(zhí)行
當前線程的同步方法或代碼塊時,其他線程調用了該線程的suspend方法讓該線程掛起 -
LOCK鎖
Class A{ private final ReentrantLock lock= new ReentrantLock (); //需要保證線程安全的方法 public void m(){ //加鎖 lock.lock(); try{ …………… } finally{ lock.unlock(); } } }
同步方法的比較
1、同步方法和同步代碼塊使用與競爭資源相關的、隱式的同步監(jiān)視器,并且強制要求加鎖和釋放鎖要出現在同一個塊結構中,而且當獲取多個鎖的時候,他們必須按照相反的順序依次釋放。
2、Lock方法不僅可以使用與非結構快中,還可以試圖中斷鎖和再次加鎖的功能。被Lock加鎖的代碼中還可以嵌套調用。
3、資源競爭不是很激烈的情況下,Synchronized的性能要優(yōu)于ReetrantLock,但是在資源競爭很激烈的情況下,Synchronized的性能會下降幾十倍。
-
Volatile關鍵字
強制性從公共堆棧中(存放實例)中取得修飾的變量的值,而不是從線程的私有數據棧中取得變量的值。不能保證不會出現臟讀的情況,需要用關鍵字synchronized
來解決。 -
ThreadLocal類
ThreadLocal在每個線程中對該變量會創(chuàng)建一個副本,即每個線程內部都會有一個該變量,且在線程內部任何地方都可以使用,線程之間互不影響。ThreadLocal提供資源的共享對象!
T get()
:返回此線程局部變量中當前線程副本的值
void remove()
:刪除此線程局部變量中當前副本的值
void set(T value)
:設置此線程局部變量中當前線程副本的值
如果需要進行多個線程之間共享資源,以達到線程之間的通信功能,就使用同步機制;如果僅僅需要隔離多個線程之間的共享沖突,可以使Threadlocal。
-
死鎖:當兩個線程相互等待對象釋放同步監(jiān)視器的時候就會發(fā)生死鎖。
使用一種稱為資源排序的簡單技術可以輕易避免死鎖。
-
等待通知機制
方法wait()的作用是使當前執(zhí)行代碼線程進行等待,是Object類的方法,該方法用來將當前線程置于“阻塞隊列”中,并在wait()所在的代碼處停止執(zhí)行,直到接到通知被喚醒。
在調用wait()
之前,線程必須持有該對象的對象級別鎖,只能在同步方法或者是同步塊中調用此方法。在執(zhí)行wait方法后,當前線程釋放鎖。進入和其他線程競爭重新獲得鎖。如果調用wait方法沒有持有鎖,則會拋出異常。
方法notify()用來通知那些可能等待該對象的對象鎖的其他線程,如果有多個線程等待,則由線程規(guī)劃器隨便選擇一個呈wait狀態(tài)的線程。
方法nofity()
也是在同步方法或者同步塊中調用,調用前同樣要獲得對象的對象級別所,否則拋出異常。在執(zhí)行notify方法后,當前線程不會馬上釋放該對象鎖,要等到執(zhí)行notify方法的線程將程序執(zhí)行完才能會釋放鎖。
方法notifyAll()方法可以使正在等待隊列中等待同一共享資源的”全部”線程從等待狀態(tài)退出,進入可運行狀態(tài)。
public class Test3 { //main方法中有三個等待線程,一個喚醒線程,一個喚醒線程只能喚醒一個等待線程,程序出現阻塞 public static void main(String[] args) throws InterruptedException { Object lock = new Object(); ThreadA a = new ThreadA(lock); new ThreadA(lock).start(); new ThreadA(lock).start(); new ThreadA(lock).start(); Thread.sleep(1000); NotifyThread notifyThread = new NotifyThread(lock); notifyThread.start(); } } class ThreadA extends Thread { private Object lock; public ThreadA(Object lock) { super(); this.lock = lock; } @Override public void run() { Service service = new Service(); service.testMethod(lock); } } class NotifyThread extends Thread { private Object lock; public NotifyThread(Object lock) { super(); this.lock = lock; } @Override public void run() { synchronized (lock) { //notify()只能喚醒一個線程,nofiyAll()喚醒所有的等待線程 lock.notify(); // lock.notifyAll(); } } } class Service { public void testMethod(Object lock) { try { synchronized (lock) { System.out.println("begin wait() ThreadName=" + Thread.currentThread().getName()); lock.wait(); System.out.println(" end wait() ThreadName=" + Thread.currentThread().getName()); } } catch (InterruptedException e) { e.printStackTrace(); } } }
- 使用條件變量協調線程
在沒有使用synchronized關鍵字保證同步,而采用Lock的程序。Java提供了condition類來保持協調。Lock替代synchronized,Condition替代同步監(jiān)視器功能。
await
: 類似于wait。
signal
:喚醒在此Lock對象上等待的單個線程,選擇是任意的。
signalAll
:喚醒在此Lock對象上等待的所有線程,選擇是任意的。
public class Account { //顯示定義Lock對象 private final Lock lock = new ReentrantLock(); //獲得指定Lock對象對應的條件變量 private final Condition cond = lock.newCondition(); private String accountNo; private double balance; //標識賬戶中是否已經存款的旗標 private boolean flag = false; public Account(){} public Account(String accountNo , double balance) { this.accountNo = accountNo; this.balance = balance; } public void setAccountNo(String accountNo) { this.accountNo = accountNo; } public String getAccountNo() { return this.accountNo; } public double getBalance() { return this.balance; } public void draw(double drawAmount) { //加鎖 lock.lock(); try { //如果賬戶中還沒有存入存款,該線程等待 while(!flag) { cond.await(); } //執(zhí)行取錢操作 System.out.println(Thread.currentThread().getName() + " 取錢:" + drawAmount); balance -= drawAmount; System.out.println("賬戶余額為:" + balance); //將標識是否成功存入存款的旗標設為false flag = false; //喚醒該Lock對象對應的其他線程 cond.signalAll(); } catch (InterruptedException ex) { ex.printStackTrace(); } //使用finally塊來確保釋放鎖 finally { lock.unlock(); } } public void deposit(double depositAmount) { lock.lock(); try { //如果賬戶中已經存入了存款,該線程等待 while(flag) { cond.await(); } //執(zhí)行存款操作 System.out.println(Thread.currentThread().getName() + " 存款:" + depositAmount); balance += depositAmount; System.out.println("賬戶余額為:" + balance); //將標識是否成功存入存款的旗標設為true flag = true; //喚醒該Lock對象對應的其他線程 cond.signalAll(); } catch (InterruptedException ex) { ex.printStackTrace(); } //使用finally塊來確保釋放鎖 finally { lock.unlock(); } } public int hashCode() { return accountNo.hashCode(); } public boolean equals(Object obj) { if (obj != null && obj.getClass() == Account.class) { Account target = (Account)obj; return target.getAccountNo().equals(accountNo); } return false; } }
使用notify()/notifyAll()方法進行通知時,被通知的線程卻是JVM隨機選擇的。當notifyAll()通知所有WAITING線程,沒有選擇權,會出現相當大的效率問題。但是ReentrantLock結合Condition類可以”選擇性通知”。Condition可以實現喚醒部分指定線程,這樣有助于程序運行的效率。
- Callable和Future
從JDK1.5之后,Java提供了Callable接口,實際上就是Runnable接口的增強版。提供call方法作為線程執(zhí)行體,但是功能更加強大。
Callable接口不是Runnable接口,不能直接作為Thread的target,有返回值得call方法也不能直接運行。這里需要一個包裝器Future。
import java.util.concurrent.*; class RtnThread implements Callable<Integer> { //實現call方法,作為線程執(zhí)行體 public Integer call() { int sum = 0; int i=0; for ( ; i < 10 ; i++ ) { System.out.println(Thread.currentThread().getName()+ " 的循環(huán)變量i的值:" + i); sum+=i; } //call()方法可以有返回值 return sum; } } public class CallableTest { public static void main(String[] args) { //創(chuàng)建Callable對象 RtnThread rt = new RtnThread(); //使用FutureTask來包裝Callable對象 FutureTask<Integer> task = new FutureTask<Integer>(rt); for (int i = 0 ; i < 10 ; i++) { System.out.println(Thread.currentThread().getName() + " 的循環(huán)變量i的值:" + i); if (i == 5) { //實質還是以Callable對象來創(chuàng)建、并啟動線程 new Thread(task , "有返回值的線程").start(); } } try { //獲取線程返回值 System.out.println("子線程的返回值:" + task.get()); } catch (Exception ex) { ex.printStackTrace(); } } }
- 線程池
import java.util.concurrent.*; //實現Runnable接口來定義一個簡單的 class TestThread implements Runnable { public void run() { for (int i = 0; i < 10 ; i++ ) { System.out.println(Thread.currentThread().getName() + "的i值為:" + i); } } } public class ThreadPoolTest { public static void main(String[] args) { //創(chuàng)建一個具有固定線程數(6)的線程池 ExecutorService pool = Executors.newFixedThreadPool(6); //向線程池中提交3個線程 pool.execute(new TestThread()); Future f1=pool.submit(new TestThread()); Future f2=pool.submit(new TestThread()); Future<Integer> f3=pool.submit(new RtnThread()); try { if(f1.get()==null&&f2.get()==null&&f3.get()!=null){ System.out.println("執(zhí)行完畢!"); System.out.println(f3.get()); } //獲取線程返回值 } catch (Exception ex) { ex.printStackTrace(); } //關閉線程池 pool.shutdown(); } }
更多java相關內容感興趣的讀者可查看本站專題:《Java進程與線程操作技巧總結》、《Java數據結構與算法教程》、《Java操作DOM節(jié)點技巧總結》、《Java文件與目錄操作技巧匯總》和《Java緩存操作技巧匯總》
希望本文所述對大家java程序設計有所幫助。
相關文章
淺析Java如何優(yōu)雅的設計接口狀態(tài)碼和異常
HTTP協議里定義了一系列的狀態(tài)碼用來表明請求的狀態(tài),如常用的200表示請求正常,404表示請求的資源不存在,所以本文就來和大家討論一下如何優(yōu)雅的設計接口狀態(tài)碼和異常,感興趣的可以了解下2024-03-03詳解Java中的線程讓步yield()與線程休眠sleep()方法
Java中的線程讓步會讓線程讓出優(yōu)先級,而休眠則會讓線程進入阻塞狀態(tài)等待被喚醒,這里我們對比線程等待的wait()方法,來詳解Java中的線程讓步yield()與線程休眠sleep()方法2016-07-07MAC上IntelliJ IDEA的svn無法保存密碼解決方案
今天小編就為大家分享一篇關于MAC上IntelliJ IDEA的svn無法保存密碼解決方案,小編覺得內容挺不錯的,現在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2018-10-10SpringBoot webSocket實現發(fā)送廣播、點對點消息和Android接收
這篇文章主要介紹了SpringBoot webSocket實現發(fā)送廣播、點對點消息和Android接收,具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-03-03