根據運行的環境,操作系統可以分為桌面操作系統,手機操作系統,服務器操作系統,嵌入式操作系統等。 在Java內存模型上 不同的平臺,內存模型不同,但是jvm內存模型規范是統一的. 實際上,Java的多線程并發問題最終將反映在Java的內存模型中. 所謂的線程安全無非是控制多個線程對多個資源的有序訪問或修改. 總結Java的內存模型,必須解決兩個主要問題: 可見性和順序. 我們都知道計算機具有緩存,并且處理器在每次處理數據時都不會占用內存. JVM定義了自己的內存模型,從而屏蔽了底層平臺內存管理的細節. 對于Java開發人員,如果您解決了多線程的可見性和有序性,則必須根據jvm內存模型進行明確說明. 那么,可見度是什么?多個線程無法相互通信,它們之間的通信只能通過共享變量來完成. Java內存模型(JMM)指定jvm具有主內存,該內存由多個線程共享. 當對象是新對象時,它也會分配到主存儲器中. 每個線程都有其自己的工作內存. 工作存儲器將一些對象的副本存儲在主存儲器中. 當然,線程的工作內存大小是有限的. 當線程對對象進行操作時,執行順序如下: (1)將變量從主內存復制到當前工作內存(讀取和加載) (2)執行代碼并更改共享變量值(使用和分配) (3)使用工作存儲器數據刷新與主存儲器有關的內容(存儲和寫入) JVM規范定義了到主內存的線程的操作指令: 讀取線程安全問題代碼,加載,使用,分配,存儲,寫入. 當一個共享變量在多個線程的工作內存中具有一個副本時,如果一個線程修改了共享變量,則其他線程應該能夠看到修改后的值,這就是多線程可見性的問題. 那么,什么是訂單?當線程引用變量時,無法直接從主內存中引用該變量. 如果線程在工作內存中沒有該變量,則會將副本從主內存復制到工作內存中. 加載,線程將在完成后引用該副本. . 當同一線程再次引用該字段時,可以從主內存中獲取變量的副本(讀取加載使用),也可以直接引用原始副本(使用),即順序讀取,加載和使用可以由JVM實施系統決定來確定. 線程無法直接將值分配給主內存中的字段. 它將值分配給工作存儲器中的變量副本(分配). 完成后,變量副本將同步到主存儲區(store-write). 過去根據JVM實現系統的決定進行同步. 如果存在此字段,它將從主存儲器分配給工作存儲器. 此過程為讀取加載. 完成后,線程將引用變量副本. 當同一線程重復多次時為字段分配值,例如: Java代碼 for(int i=0;i<10;i++) a++; 線程可能僅向工作內存中的副本分配值,并且僅在最后一次分配后才同步到主存儲區域,因此JVM實現系統可以確定分配,存儲和更改的順序. 假設有一個共享變量x,線程a執行x = x + 1. 從上面的描述可以知道,x = x + 1不是原子操作,其執行過程如下: 1從主存儲器到工作存儲器讀取變量x的副本 2將1加到x 3將x加1的值寫回主存儲器 如果另一個線程b執行x = x-1,則執行過程如下: 1從主存儲器到工作存儲器讀取變量x的副本 2從x減去1 3將x減1的值寫回到主存儲器中 因此,顯然,x的最終值不可靠. 假設x現在為10,則線程a增加1,線程b減少1. 從表面上看,最終的x仍為10,但是在多線程情況下會發生這種情況: 1: 線程a從主內存到工作內存讀取x的副本,并且工作內存中x的值為10 2: 線程b從主內存讀取x的副本到工作內存,并且工作內存中的x值為10 3: 線程a將工作內存中的x加1,而工作內存中的x為11 4: 線程a將x提交到主內存,其中x為11 5: 線程b將工作內存中的x值減1,而工作內存中的x值為9 6: 線程b將x提交到主內存,其中x為9 類似地,x可能為11,如果x是一個銀行帳戶,線程存款,線程b扣除,顯然這是一個嚴重的問題,要解決此問題,必須確保線程a和線程b順序正確執行后,每個線程執行的遞增或遞減1是原子操作. 看下面的代碼: Java代碼 public class Account { private int balance; public Account(int balance) { this.balance = balance; } public int getBalance() { return balance; } public void add(int num) { balance = balance + num; } public void withdraw(int num) { balance = balance - num; } public static void main(String[] args) throws InterruptedException { Account account = new Account(1000); Thread a = new Thread(new AddThread(account, 20), "add"); Thread b = new Thread(new WithdrawThread(account, 20), "withdraw"); a.start(); b.start(); a.join(); b.join(); System.out.println(account.getBalance()); } static class AddThread implements Runnable { Account account; int amount; public AddThread(Account account, int amount) { this.account = account; this.amount = amount; } public void run() { for (int i = 0; i < 200000; i++) { account.add(amount); } } } static class WithdrawThread implements Runnable { Account account; int amount; public WithdrawThread(Account account, int amount) { this.account = account; this.amount = amount; } public void run() { for (int i = 0; i < 100000; i++) { account.withdraw(amount); } } } } 第一個執行結果是10200,第二個執行結果是1060. 每個執行的結果都是不確定的,因為線程的執行順序是不可預測的. 這是Java同步的根本原因. sync關鍵字可確保多個線程對于同步塊是互斥的. 作為同步方法進行同步解決了Java多線程的執行順序和內存可見性. volatile關鍵字“解決多線程內存可見性問題”. 稍后將詳細描述. 同步關鍵字 如上所述,java使用synced關鍵字作為多線程并發環境的執行順序的保證之一. 當一段代碼將修改共享變量時,這段代碼將成為一個互斥或關鍵的部分. 為了確保共享變量的正確性,synchronized指示關鍵部分. 典型用法如下: Java代碼 synchronized(鎖){ 臨界區代碼 } 為了確保銀行帳戶的安全,該帳戶的操作方法如下: Java代碼 public synchronized void add(int num) { balance = balance + num; } public synchronized void withdraw(int num) { balance = balance - num; } 您只是說過sync的用法是這樣的: Java代碼 synchronized(鎖){ 臨界區代碼 } 那么這對于公共同步的void add(int num)意味著什么?實際上,在這種情況下,鎖定是此方法的對象. 同樣,如果該方法是公共靜態同步的void add(int num),則鎖是該方法所在的類. 理論上,每個對象都可以用作鎖,但是當一個對象用作鎖時線程安全問題代碼,應該由多個線程共享,這樣才有意義. 在并發環境中,未共享的對象不是鎖. 有意義的. 如果有這樣的代碼: Java代碼 public class ThreadTest{ public void test(){ Object lock=new Object(); synchronized (lock){ //do something } } } 將lock變量作為鎖存在根本沒有任何意義,因為它根本不是共享對象,并且每個線程都將執行Object lock = new Object();. 每個線程都有自己的鎖,并且沒有鎖競爭. 每個鎖對象都有兩個隊列,一個是就緒隊列,另一個是阻塞隊列. 就緒隊列存儲將獲取鎖的線程. 阻塞隊列存儲被阻塞的線程. 喚醒線程(通知)后,它將進入就緒隊列并等待CPU調度. 當線程a首次執行account.add方法時,jvm將檢查鎖定對象帳戶的就緒隊列是否已經在等待線程. 如果存在,則表明帳戶鎖已被占用. 由于這是第一次運行,因此該帳戶的就緒隊列為空,因此線程a獲取鎖并執行account.add方法. 如果這時發生,線程b必須執行account.withdraw方法,因為線程a已經獲得了鎖并且還沒有釋放它,所以線程b必須進入帳戶的就緒隊列并等待鎖被執行. 線程執行關鍵部分代碼的過程如下: 1獲取同步鎖 2清除工作記憶 3將變量副本從主內存復制到工作內存 4計算這些變量 5將變量從工作存儲器寫入主存儲器 6解除鎖定 可以看出,同步不僅可以保證多個線程的并發排序,而且可以確保多個線程的內存可見性. 生產者/消費者模型 生產者/消費者模型實際上是一個非常經典的線程同步模型. 在許多情況下,僅在多個線程之間保證共享資源操作上多個線程的相互排斥是不夠的. 有合作. 假設存在這樣一種情況,即桌子上有一塊盤子,桌子上只能放一個雞蛋. 擅長將雞蛋放在盤子上. 如果盤子里有雞蛋,請等到沒有雞蛋,B專門從盤子里取出雞蛋. 如果板上沒有雞蛋,請等待直到板上有雞蛋. 實際上,印版是相互排斥的區域. 每次將雞蛋放在盤子上時,它應該是互斥的. A的等待實際上是在主動放棄鎖,B也必須提醒A在等待時放雞蛋. 如何讓線程主動釋放鎖 很簡單,只需調用鎖的wait()方法即可. wait方法來自Object,因此任何對象都具有此方法. 看下面的代碼片段: Java代碼 Object lock=new Object();//聲明了一個對象作為鎖 synchronized (lock) { balance = balance - num; //這里放棄了同步鎖,好不容易得到,又放棄了 lock.wait(); } 如果線程獲取鎖,進入同步塊并執行lock.wait(),則該線程將進入鎖的阻塞隊列. 如果調用lock.notify(),則將通知阻塞隊列中的線程進入就緒隊列.
|
溫馨提示:喜歡本站的話,請收藏一下本站!