1. 程式人生 > >Java併發程式設計(一):執行緒基礎知識以及synchronized關鍵字

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獲得的鎖是可重入鎖。還有個概念是死鎖,死鎖是指多個執行緒搶佔資源而造成的一種互相等待。舉個例子,一個箱子需要兩把鑰匙才可以開啟,鑰匙分別在兩個人手中,這兩個人互相搶佔另外一個人的鑰匙,導致箱子打不