Java併發程式設計(一):執行緒基礎知識以及synchronized關鍵字
1.執行緒與多執行緒的概念:在一個程式中,能夠獨立執行的程式片段叫作“執行緒”(Thread)。多執行緒(multithreading)是指從軟體或者硬體上實現多個執行緒併發執行的技術。
2.多執行緒的意義:多執行緒可以在時間片裡被cpu快速切換,資源能更好被呼叫、程式設計在某些情況下更簡單、程式響應更快、執行更加流暢。
2.如何啟動一個執行緒:繼承Thread類、實現Runnable介面、實現Callable介面
3.為什麼要保證執行緒的同步?:java允許多執行緒併發控制,當多個執行緒同時操作一個可共享的資源變數時(如資料的增刪改查),將會導致資料不準確,相互之間產生衝突,因此加入同步鎖以避免在該執行緒沒有完成操作之前,被其他執行緒的呼叫,從而保證了該變數的唯一性和準確性。
4.基本的執行緒同步:使用synchronized關鍵字、特殊域變數volatile、wait和notify方法等。
public class T { private int count=10; private Object o=new Object(); public void m(){ synchronized (o){ //任何執行緒要執行下面的程式碼,必須先拿到o的鎖 count--; System.out.println(Thread.currentThread().getName()+"count="+count); } } }
假設這段程式碼中有多個執行緒,當第一個執行緒執行到m方法來的時候,sync鎖住了堆記憶體中的o物件,這個時候第二個執行緒是進不來的,它必須等第一個執行緒執行完,鎖釋放掉才可以接著執行。這裡有個鎖的概念叫做互斥鎖,sync是一種互斥鎖。
public class T1 { private static int count =10; public synchronized static void m(){ //這裡等同於synchronized(T3.class) count--; System.out.println(Thread.currentThread().getName()+"count="+count); } public static void mm(){ synchronized (T1.class){ //思考:這裡寫成synchronized(this)是否可以? count--; } } }
思考這裡,注意sync修飾的是一個靜態方法和靜態的屬性,靜態修飾的方法和屬性是不需要new出物件來就可以訪問的,所以這裡沒有new出物件,sync鎖定的是T3.class物件。
public class T2 implements Runnable{ private int count =10; @Override public /*synchronized*/ void run(){ count--; System.out.println(Thread.currentThread().getName()+"count="+count); } public static void main(String[] args) { T2 t=new T2(); for (int i=0;i<5;i++){ new Thread(t,"THREAD"+i).start(); } } }
上面程式碼中開啟了五個執行緒,執行run方法對count進行減一的操作,這裡會出現執行緒搶佔資源的問題。當第一個執行緒在執行run方法時,減減的過程中,第二個執行緒也進入了方法,同樣也在執行減減,可能會出現第二個執行緒減完的時候,第一個執行緒才輸出count,這時候就出現了執行緒重入。處理方法可以加上synchronized關鍵字,只有等第一個執行緒執行完畢,第二個執行緒才可以進入,即同一時刻只能有一個執行緒來對它進行操作,也就是原子性。
public class Account { /** * 賬號持有人和賬號餘額 */ String name; double balance; /** * 設定賬號餘額,執行緒睡眠目的是先讓它讀資料 * @param name * @param balance */ public synchronized void set(String name,double balance){ this.name=name; try { Thread.sleep(2000); }catch (InterruptedException e){ e.printStackTrace(); } this.balance=balance; } public double getBalance(String name){ return this.balance; } public static void main(String[] args) { Account a=new Account(); new Thread(()->a.set("zhangsan",100.0)).start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(a.getBalance("zhangsan")); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(a.getBalance("zhangsan")); } }
這是一個小demo,程式碼中只對set方法加了鎖,沒有對get方法加鎖,這個時候會出現髒讀現象。解決方法是讀和寫的方法都加鎖。
public class T3 { synchronized void m1(){ System.out.println("m1 start"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } m2(); } synchronized void m2() { try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("m2"); } }
這個案例中,m1和m2方法都已經加上了鎖,當執行m1的時候再去執行m2,這樣是可以的。一個同步方法可以呼叫另外一個同步方法,也就是說sync獲得的鎖是可重入鎖。還有個概念是死鎖,死鎖是指多個執行緒搶佔資源而造成的一種互相等待。舉個例子,一個箱子需要兩把鑰匙才可以開啟,鑰匙分別在兩個人手中,這兩個人互相搶佔另外一個人的鑰匙,導致箱子打不