2017年3月6日 星期一

synchronized、volatile、wait、notify、notifyAll (Thread 四)

synchronized 是同步的意思,其中一個 Thread 執行後,其他的 Thread 也要知道,所以在聲明的區塊裡,一次只能有一個 Thread 執行 (static 除外) ,可以寫在方法、區塊、static method 裡,有四種方式

volatile

synchronized 只能寫在方法,而 volatile 只能寫在變數
使用 volatile 宣告的變數,會在記憶體裡取値;沒宣告會在 cache 取值,所以宣告 volatile 後會比較慢
但沒宣告 volatile 有可能記憶體裡的值已被更改,造成資料不同步,所以非必要不要加
在兩個以上 (含) 的 Thread 會使用到的成員變數可以加 volatile,但如果成員變數已在 synchronized 裡或有 final 關鍵字,就不必加了


※synchronized

class ATM implements Runnable {
    private Account a;
    private Integer money;
    
    public ATM(Account a, Integer money) {
        this.a = a;
        this.money = money;
    }
    
    @Override
    public void run() {
        a.executeWithDraw(a, money);
    }
}
    
class Account {
    private int totalMoney;
    
    public Account(int money) {
        this.totalMoney = money;
    }
    
    public int getTotalMoney() {
        return totalMoney;
    }
    
    public void executeWithDraw(Account a, int money) {
        synchronized (this) {
            System.out.print("帳號:" + Thread.currentThread().getName());
            int tmpTotalMoney = totalMoney;
            for (int i = 1; i <= 99999; i++);
    
            tmpTotalMoney -= money;
            if (tmpTotalMoney < 0) {
                System.out.println("$不夠");
            } else {
                totalMoney = tmpTotalMoney;
            }
    
            System.out.println("領" + money + "元, 剩" + a.getTotalMoney() + "元");
        }
    }
}

※for 迴圈模擬提款的時間

※synchronized 寫在方法和 synchronized() 差在,一個是整個方法都 synchronized;另一個是synchronized 區塊裡才是 synchronized,所以外面可以加一些和 synchronized 無關的程式碼,效能會比較好

※另外兩個synchronized 是 static 的,其中一個和寫在方法一樣,另一個是synchronized(Xxx.class)

※測試

Account a = new Account(10000);
System.out.println("目前共" + a.getTotalMoney() + "元");
Thread t1 = new Thread(new ATM(a, 5000));
Thread t2 = new Thread(new ATM(a, 2000));
Thread t3 = new Thread(new ATM(a, 4000));
t1.start();
t2.start();
t3.start();





※wait、notify、notifyAll


這三個方法都是 Object 的,都必需寫在 synchronized 裡,否則會出「java.lang.IllegalMonitorStateException」的錯
使用 wait 後,必需用 notify 或 notifyAll 喚醒,兩者差在一個呼叫一個 Thread;另一個呼叫全部的 Thread,呼叫哪一個或呼叫的 Thread 順序,是以 JVM 的算法為準,不是優先權值

wait 會釋放鎖,所以一執行完這一行,其他的 Thread 可以呼叫


public class ThreadTest extends Thread {
    @Override
    public void run() {
        synchronized (this) {
            System.out.println("call notify:" + Thread.currentThread());
            notify();
        }
    }
    
    public static void main(String... a) {
        ThreadTest t = new ThreadTest();
        t.start();
    
        synchronized (t) {
            System.out.println("call wail:" + Thread.currentThread());
            try {
                t.wait();
                System.out.println("...");
            } catch (InterruptedException e) {
                System.out.println("wait interrupted!");
            }
            System.out.println("......");
        }
    }
}

※結果:
call wail:Thread[main,5,main]
call notify:Thread[Thread-0,5,main]
...
......

※Thread.currentThread 印出的是「Thread 名稱、優先權值、Thread 群組名稱」


※以喝完水要加水為例

class Water {
    private static boolean empty = true;
    
    public synchronized void put(int i) {
        if (!empty) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("加第" + i + "次水");
        empty = false;
        notify();
    }
    
    public synchronized void eat(int i) {
        if (empty) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("喝第" + i + "次水");
        empty = true;
        notify();
    }
}
    
class Drink implements Runnable {
    Water w;
    
    Drink(Water c) {
        this.w = c;
    }
    
    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            w.eat(i);
        }
    }
}
    
class Add implements Runnable {
    Water w;
    
    Add(Water c) {
        this.w = c;
    }
    
    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            w.put(i);
        }
    }
}




※測試

Water c = new Water();
Thread p = new Thread(new Add(c));
Thread e = new Thread(new Drink(c));
e.start();
p.start();



沒有留言:

張貼留言