您好,登錄后才能下訂單哦!
java中多線程編程的優缺點有哪些?這個問題可能是我們日常學習或工作經常見到的。希望通過這個問題能讓你收獲頗深。下面是小編給大家帶來的參考內容,讓我們一起來看看吧!
一、多線程的優缺點
多線程的優點:
1)資源利用率更好
2)程序設計在某些情況下更簡單
3)程序響應更快
多線程的代價:
1)設計更復雜
雖然有一些多線程應用程序比單線程的應用程序要簡單,但其他的一般都更復雜。在多線程訪問共享數據的時候,這部分代碼需要特別的注意。線程之間的交互往往非常復雜。不正確的線程同步產生的錯誤非常難以被發現,并且重現以修復。
2)上下文切換的開銷
當CPU從執行一個線程切換到執行另外一個線程的時候,它需要先存儲當前線程的本地的數據,程序指針等,然后載入另一個線程的本地數據,程序指針等,最后才開始執行。這種切換稱為“上下文切換”(“context switch”)。CPU會在一個上下文中執行一個線程,然后切換到另外一個上下文中執行另外一個線程。上下文切換并不廉價。如果沒有必要,應該減少上下文切換的發生。
二、創建java多線程
1、創建Thread的子類
創建Thread子類的一個實例并重寫run方法,run方法會在調用start()方法之后被執行。例子如下:
public class MyThread extends Thread { public void run(){ System.out.println("MyThread running"); } } MyThread myThread = new MyThread(); myTread.start();
也可以如下創建一個Thread的匿名子類:
Thread thread = new Thread(){ public void run(){ System.out.println("Thread Running"); } }; thread.start();
2、實現Runnable接口
第二種編寫線程執行代碼的方式是新建一個實現了java.lang.Runnable接口的類的實例,實例中的方法可以被線程調用。下面給出例子:
public class MyRunnable implements Runnable { public void run(){ System.out.println("MyRunnable running"); } } Thread thread = new Thread(new MyRunnable()); thread.start();
同樣,也可以創建一個實現了Runnable接口的匿名類,如下所示:
Runnable myRunnable = new Runnable(){ public void run(){ System.out.println("Runnable running"); } } Thread thread = new Thread(myRunnable); thread.start();
三、線程安全
在同一程序中運行多個線程本身不會導致問題,問題在于多個線程訪問了相同的資源。如同一內存區(變量,數組,或對象)、系統(數據庫,web services等)或文件。實際上,這些問題只有在一或多個線程向這些資源做了寫操作時才有可能發生,只要資源沒有發生變化,多個線程讀取相同的資源就是安全的。
當兩個線程競爭同一資源時,如果對資源的訪問順序敏感,就稱存在競態條件。導致競態條件發生的代碼區稱作臨界區。
如果一個資源的創建,使用,銷毀都在同一個線程內完成,且永遠不會脫離該線程的控制,則該資源的使用就是線程安全的。
四、java同步塊
Java中的同步塊用synchronized標記。同步塊在Java中是同步在某個對象上。所有同步在一個對象上的同步塊在同時只能被一個線程進入并執行操作。所有其他等待進入該同步塊的線程將被阻塞,直到執行該同步塊中的線程退出。
有四種不同的同步塊:
實例方法同步:
public synchronized void add(int value){ this.count += value; }
Java實例方法同步是同步在擁有該方法的對象上。這樣,每個實例其方法同步都同步在不同的對象上,即該方法所屬的實例。只有一個線程能夠在實例方法同步塊中運行。如果有多個實例存在,那么一個線程一次可以在一個實例同步塊中執行操作。一個實例一個線程。
靜態方法同步:
public static synchronized void add(int value){ count += value; }
靜態方法的同步是指同步在該方法所在的類對象上。因為在Java虛擬機中一個類只能對應一個類對象,所以同時只允許一個線程執行同一個類中的靜態同步方法。
實例方法中的同步塊:
public void add(int value){ synchronized(this){ this.count += value; } }
注意Java同步塊構造器用括號將對象括起來。在上例中,使用了“this”,即為調用add方法的實例本身。在同步構造器中用括號括起來的對象叫做監視器對象。上述代碼使用監視器對象同步,同步實例方法使用調用方法本身的實例作為監視器對象。一次只有一個線程能夠在同步于同一個監視器對象的Java方法內執行。
下面兩個例子都同步他們所調用的實例對象上,因此他們在同步的執行效果上是等效的。
public class MyClass { public synchronized void log1(String msg1, String msg2){ log.writeln(msg1); log.writeln(msg2); } public void log2(String msg1, String msg2){ synchronized(this){ log.writeln(msg1); log.writeln(msg2); } } }
靜態方法中的同步塊:
public class MyClass { public static synchronized void log1(String msg1, String msg2){ log.writeln(msg1); log.writeln(msg2); } public static void log2(String msg1, String msg2){ synchronized(MyClass.class){ log.writeln(msg1); log.writeln(msg2); } } }
這兩個方法不允許同時被線程訪問。如果第二個同步塊不是同步在MyClass.class這個對象上。那么這兩個方法可以同時被線程訪問。
五、java線程通信
線程通信的目標是使線程間能夠互相發送信號。另一方面,線程通信使線程能夠等待其他線程的信號。
Java有一個內建的等待機制來允許線程在等待信號的時候變為非運行狀態。java.lang.Object 類定義了三個方法,wait()、notify()和notifyAll()來實現這個等待機制。
一個線程一旦調用了任意對象的wait()方法,就會變為非運行狀態,直到另一個線程調用了同一個對象的notify()方法。為了調用wait()或者notify(),線程必須先獲得那個對象的鎖。也就是說,線程必須在同步塊里調用wait()或者notify()。
以下為一個使用了wait()和notify()實現的線程間通信的共享對象:
public class MyWaitNotify{ MonitorObject myMonitorObject = new MonitorObject(); boolean wasSignalled = false; public void doWait(){ synchronized(myMonitorObject){ while(!wasSignalled){ try{ myMonitorObject.wait(); } catch(InterruptedException e){...} } //clear signal and continue running. wasSignalled = false; } } public void doNotify(){ synchronized(myMonitorObject){ wasSignalled = true; myMonitorObject.notify(); } } }
注意以下幾點:
1、不管是等待線程還是喚醒線程都在同步塊里調用wait()和notify()。這是強制性的!一個線程如果沒有持有對象鎖,將不能調用wait(),notify()或者notifyAll()。否則,會拋出IllegalMonitorStateException異常。
2、一旦線程調用了wait()方法,它就釋放了所持有的監視器對象上的鎖。這將允許其他線程也可以調用wait()或者notify()。
3、為了避免丟失信號,必須把它們保存在信號類里。如上面的wasSignalled變量。
4、假喚醒:由于莫名其妙的原因,線程有可能在沒有調用過notify()和notifyAll()的情況下醒來。這就是所謂的假喚醒(spurious wakeups)。為了防止假喚醒,保存信號的成員變量將在一個while循環里接受檢查,而不是在if表達式里。這樣的一個while循環叫做自旋鎖。
5、不要在字符串常量或全局對象中調用wait()。即上面MonitorObject不能是字符串常量或是全局對象。每一個MyWaitNotify的實例都擁有一個屬于自己的監視器對象,而不是在空字符串上調用wait()/notify()。
六、java中的鎖
自Java 5開始,java.util.concurrent.locks包中包含了一些鎖的實現,因此你不用去實現自己的鎖了。
常用的一些鎖:
java.util.concurrent.locks.Lock;
java.util.concurrent.locks.ReentrantLock;
java.util.concurrent.locks.ReadWriteLock;
java.util.concurrent.locks.ReentrantReadWriteLock;
一個可重入鎖(reentrant lock)的簡單實現:
public class Lock { boolean isLocked = false; Thread lockedBy = null; int lockedCount = 0; public synchronized void lock() throws InterruptedException{ Thread callingThread = Thread.currentThread(); while(isLocked && lockedBy != callingThread){ wait(); } isLocked = true; lockedCount++; lockedBy = callingThread; } public synchronized void unlock(){ if(Thread.currentThread() == this.lockedBy){ lockedCount--; if(lockedCount == 0){ isLocked = false; notify(); } } } }
注意的一點:在finally語句中調用unlock()
lock.lock(); try{ //do critical section code, which may throw exception } finally { lock.unlock(); }
七、java中其他同步方法
信號量(Semaphore):java.util.concurrent.Semaphore
阻塞隊列(Blocking Queue):java.util.concurrent.BlockingQueue
public class BlockingQueue { private List queue = new LinkedList(); private int limit = 10; public BlockingQueue(int limit) { this.limit = limit; } public synchronized void enqueue(Object item) throws InterruptedException { while (this.queue.size() == this.limit) { wait(); } if (this.queue.size() == 0) { notifyAll(); } this.queue.add(item); } public synchronized Object dequeue() throws InterruptedException { while (this.queue.size() == 0) { wait(); } if (this.queue.size() == this.limit) { notifyAll(); } return this.queue.remove(0); } }
八、java中的線程池
Java通過Executors提供四種線程池,分別為:
newCachedThreadPool
創建一個可緩存的線程池。如果線程池的大小超過了處理任務所需要的線程,那么就會回收部分空閑(60秒不執行任務)的線程,當任務數增加時,此線程池又可以智能的添加新線程來處理任務。此線程池不會對線程池大小做限制,線程池大小完全依賴于操作系統(或者說JVM)能夠創建的最大線程大小。
newFixedThreadPool
創建固定大小的線程池。每次提交一個任務就創建一個線程,直到線程達到線程池的最大大小。線程池的大小一旦達到最大值就會保持不變,如果某個線程因為執行異常而結束,那么線程池會補充一個新線程。
newScheduledThreadPool
創建一個大小無限制的線程池。此線程池支持定時以及周期性執行任務。
newSingleThreadExecutor
創建一個單線程的線程池。此線程池支持定時以及周期性執行任務。這個線程池只有一個線程在工作,也就是相當于單線程串行執行所有任務。如果這個唯一的線程因為異常結束,那么會有一個新的線程來替代它。此線程池保證所有任務的執行順序按照任務的提交順序執行。
線程池簡單用法:
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Main { public static void main(String[] args) { ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); for (int i = 0; i < 10; i++) { final int index = i; cachedThreadPool.execute(new Runnable() { public void run() { System.out.println(index); } }); } } }
感謝各位的閱讀!看完上述內容,你們對java中多線程編程的優缺點有哪些大概了解了嗎?希望文章內容對大家有所幫助。如果想了解更多相關文章內容,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。