1、多線程的併發與並行:html
併發:多個線程同時都處在運行中的狀態。線程之間相互干擾,存在競爭,(CPU,緩衝區),每一個線程輪流使用CPU,當一個線程佔有CPU時,其餘線程處於掛起狀態,各線程斷續推動。java
並行:多個線程同時執行,可是每一個線程各自有本身的CPU,不存在CPU資源的競爭,他們之間也可能存在資源的競爭。安全
併發發生在同一段時間間隔內,並行發生在同一時刻內。併發執行的總時間是每一個任務的時間和,而並行則取決於最長任務的時間。多線程
下面看一下A,B兩個任務在並行和併發狀況下是怎麼執行的:[不考慮其餘資源競爭,只考慮CPU競爭]併發
A任務:a+b+c,性能
A任務有三步:a=1+1,b=2+2,c=3+3this
B任務:x+y+z,atom
A任務有三步:x=1*1,y=2*2,z=3*3spa
A,B並行操做:多核CPU,多任務並行。操作系統
CPU1:
CPU2:
圖中能夠看到A,B操做相互不受影響。
當A任務開始的時候B任務也開始,他們能夠同一時刻開始,若是每個小任務耗時相同,那麼他們可能同時結束。
A,B併發操做:單核cpu,多任務併發。
圖中能夠看出A,B同時執行的時候,必定有一個先,有一個後,由於CPU只有一個執行了A就不能執行B,可是爲了讓兩個任務可以「同時完成「,CPU先執行A的一部分,在執行B的一部分,當這個間隔時間很是短的時候咱們看到的就是A,B都在運行。
舉個簡單的例子:
左右手同時握住兩支筆,並排點一個點,點出一條線來,這就是並行。只有一隻手握住一支筆,左點一下,右點一下知道畫出兩條線,這兩條線看似同時開始同時結束,實際是有時間差的,這就是併發。
實際操做中並非嚴格的併發或者是並行,由於CPU有限,而任務是無限的。任務數量超過CPU核心數量的時候,就是併發並行共同存在的時候,不過這不是咱們關注的重點,CPU資源的分配問題,是操做系統關心的重點。咱們關心的重點是任務之間發生的資源競爭問題。
當多個線程對同一個資源進行訪問和操做的時候就會出現數據一致性問題。一致性問題得不到解決多個任務的操做永遠得不到正確的結果,解決一致性問題的方法就是同步機制。
2、synchonrized實現同步:
java每一個對象都有一個內置鎖,當用synchonrized修飾方法或者代碼塊的時候就會調用這個鎖來保護方法和代碼塊。
同步方法:同步靜態方法,同步非靜態方法。
public synchronized void addMeethod2(){ num2++; }
public static synchronized void addMeethod2(){ num2++; }
同步代碼塊:
synchronized(Object){...}:Object表示一個對象,synchronized獲取這個對象的同步鎖,保證線程獲取object的同步鎖以後其餘線程不能訪問object的任何同步方法。但其餘線程能夠訪問非同步的部分。
public void addMeethod3(){ synchronized(this){ num3++; } }
public void addMeethod4(){ synchronized(num4){ num4++; } }
public void addMeethod5(){ synchronized(Learnlocks.class){ num5++; } }
同步當前對象this:public void addMeethod3(){synchronized(this){num3++;}}和同步非靜態方法:public synchronized void addMeethod2(){ num2++;}他們的效果是同樣的都是做用與當前對象,即獲取的object都是當前對象。那麼只有這個線程能夠操做這個對象全部synchronized修飾的部分,執行完這部份內容纔會釋放鎖,其餘線程才能訪問。
下面看那一下示例:模擬三個線程分別對不一樣的同步機制保護的數據進行操做,看他們的具體表現。
import java.util.concurrent.atomic.AtomicInteger; public class NumAction { private Integer num1=0; private Integer num2=0; private Integer num3=0; private Integer num4=0; private Integer num5=0; private volatile Integer num6=0; private AtomicInteger num7=new AtomicInteger(0); public NumAction() { } //省略get/set方法public void Initializers(NumAction lk){ lk.num1=0; lk.num2=0; lk.num3=0; lk.num4=0; lk.num5=0; lk.num6=0; lk.num7=new AtomicInteger(0); }
//----------------------------------------------------------重點部分------------------------------------------------------ public void addMeethod1(){ num1++; } public synchronized void addMeethod2(){ num2++; } public void addMeethod3(){ synchronized(this){ num3++; } } public void addMeethod4(){ synchronized(num4){ num4++; } } public void addMeethod5(){ synchronized(NumAction.class){ num5++; } } public void addMeethod6(){ num6++; } public void addMeethod7(){ num7.incrementAndGet(); } public void Add100() { for (int i = 0; i < 100; i++) { addMeethod1(); addMeethod2(); addMeethod3(); addMeethod4(); addMeethod5(); addMeethod6(); addMeethod7(); } } }
NumAction類:有七個屬性,八個方法,前七個方法分別給七個屬性自增一次。第八個方法是調用這七個方法100次。下面用多個線程來執行這個方法。
package com.eshore.ljcx.locks; public class Learnlocks2 extends Thread{ private static NumAction numaction=new NumAction(); public void run() { numaction.Add100(); } public static void testRun(){ Learnlocks2 l1 = new Learnlocks2(); Learnlocks2 l2 = new Learnlocks2(); Learnlocks2 l3 = new Learnlocks2(); new Thread(l1).start(); new Thread(l2).start(); new Thread(l3).start(); try { Thread.sleep(7000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(numaction.getNum1()+","+numaction.getNum2()+","+numaction.getNum3()+","+numaction.getNum4()+","+numaction.getNum5()+","+numaction.getNum6()+","+numaction.getNum7()); } public static void main(String[] args) { for (int i = 0; i < 10; i++) { testRun(); numaction.Initializers(numaction); } } }
咱們啓動了三個線程去執行給每一個數據自增一百次的操做,理想狀態下最終每一個數據應該是300,再將這個步驟重複10次,看一下是否是同樣的結果。
結果以下:num2,num3,num5,num7是300,其餘的數據都不正確。說明這四個數據的同步起到了做用,他們分別是:(同步方法,同步當前對象,同步類對象,Atom原子對象。)而volatitle關鍵字被稱爲輕量級的同步機制並無起到應有的效果。同步屬性對象num4也並無做用。
299,300,300,300,300,299,300
297,300,300,300,300,300,300
292,300,300,297,300,297,300
275,300,300,296,300,294,300
298,300,300,300,300,300,300
283,300,300,300,300,297,300
297,300,300,300,300,300,300
297,300,300,300,300,297,300
299,300,300,300,300,300,300
296,300,300,299,300,298,300
同步num4爲何沒有起到做用:由於實際上咱們上鎖的是對象自己,並非對象的引用。每次給對象自增,對象已經修改了,那麼咱們每次獲取的鎖也不是同一個鎖。天然沒有辦法保持同步。若是咱們添加一個不會被修改的屬性對象num0。
private Integer num0 =0;
修改方法四:
public void addMeethod4(){ synchronized(this.num0){ num4++; } }
結果以下:發現num4的輸出結果也是預期的300.
296,300,300,300,300,300,300
297,300,300,300,300,300,300
248,300,300,300,300,281,300
255,300,300,300,300,289,300
297,300,300,300,300,300,300
289,300,300,300,300,298,300
298,300,300,300,300,300,300
290,300,300,300,300,300,300
263,300,300,300,300,299,300
296,300,300,300,300,300,300
從num4能夠看出來咱們獲取num0的對象鎖,可是num4卻能夠保持同步。這是由於num0這個對象鎖的代碼塊是num0對象鎖的做用域,要對num4操做,必須獲取num0對象鎖。不少時候誤區可能在於咱們要對那個數據保持同步就要獲取那個數據的鎖,這是很錯誤的理解。首先synchonrized獲取的是對象鎖,數據不是對象就知足不了條件(我的看法,僅供參考)。
總結下:(synchonrized的用法在示例代碼部分已經作了展現。)
//-------------------------------------------------------update:2017/3/23-------------------------------------------------
對於同步方法:
一、全部靜態方法M0,M1。。:鎖定的對象是類
多線程用類使用M0,M1會阻塞:M0,M1屬於類,類加鎖,鎖相同
二、全部非靜態方法M2,M3:鎖定的對象是類的實例
多線程用不一樣的對象使用M2不會阻塞:對象不一樣,鎖不一樣
多線程用相同的對象使用M2會阻塞:對象相同,鎖相同
多線程用相同的對象使用M2,M3會阻塞:對象相同,鎖相同
多線程用不一樣的對象使用M2,M3不會阻塞:對象不一樣,鎖不一樣
*&*:
多線程訪問M1(靜態方法)和M2(非靜態方法)不會阻塞:M1的鎖是類,M2的鎖是對象,鎖不一樣
對於同步代碼塊:
一、非靜態方法代碼塊:鎖定的是指定的對象Object
同步代碼塊當Object==this的時候,和同步方法效果相同,可是若是隻做用了整個方法的一部分代碼塊,那麼效率會更高。【做用內容大小帶來的差別】
同步代碼塊當Object==X(X==指定一個不變的對象 final Integer X=0)的時候,和同步方法效果不一樣,不一樣點在於此時做用域是全部以X爲鎖的代碼塊,而同步方法會做用於對象下全部的同步方法。這也是同步代碼塊相較於同步方法效率會更高的緣由。【做用方法的數量帶來的差別】
二、靜態方法代碼塊:鎖定的是指定的對象Object
同步代碼塊當Object==this的時候,很少說,仍是類(靜態方法類調用,怎麼也扯不到實例對象上去)
同步代碼塊當Object==X(X==指定一個不變的對象 static final Integer X=0,X必須是靜態的,由於靜態方法只能使用靜態屬性)的時候,鎖的做用域是全部使用X做爲鎖的代碼塊。而同步靜態方法做用域是全部的同步方法。
//-------------------------------------------------------------------------------------------------------------------------
併發機制提升任務效率,而同步機制是保證併發的安全,可是同時也會破壞併發效率,因此同步的鎖的粒度把握是個關鍵問題,在保證同步的狀況下儘可能保證性能纔是關鍵。
對於非靜態字段中可更改的數據,一般使用非靜態方法訪問.
對於靜態字段中可更改的數據,一般使用靜態方法訪問,也可用非靜態訪問。不過是操做類。