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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章