1. 程式人生 > >java 同步代碼塊與同步方法

java 同步代碼塊與同步方法

this AD 監視器 鎖定 數量 money acc 余額 位置

同步代碼塊

synchronized (obj) {
    // 代碼塊
}

obj 為同步監視器,以上代碼的含義為:線程開始執行同步代碼塊(中的代碼)之前,必須先獲得對同步監視器的鎖定。

代碼塊中的代碼是執行代碼,即是某個方法中的某一部分代碼,synchronized(obj){}只能出現在某個方法中。如:

    public void test() {

        synchronized (obj) {
            // 代碼塊
        }
    }

而不能出現在其他位置,如下則報錯:

public class Test {


    public void
test(String[] strs) { }
// 報錯,只能出現在某個方法中
synchronized (obj) { } }

同步代碼塊示例:

定義一個Account類,用於存儲賬戶金額

class Account {

    // 賬戶余額
    private double balance;

    public Account(double balance) {
        this.balance = balance;
    }

    // 設置余額
    public void setBalance(double
balance) { this.balance = balance; } // 取出余額 public double getBalance() { return balance; } }

定義1個線程類用於對某個賬戶進行操作(取出賬戶中的余額),該線程類不包含同步代碼塊

class DrawMoney extends Thread {

    private Account account;  // 待取賬戶
    private double amount;    // 取出金額

    public DrawMoney(Account account, double
amount) { this.account = account; this.amount = amount; } // 取出account中的余額,取出數量為amount public void run() { // 若account中的余額大於等於amount,取錢成功 if (amount <= account.getBalance()) { System.out.println(getName() + " : " + "Get money:" + amount); // 線程休眠2毫秒 try { Thread.sleep(2); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } account.setBalance(account.getBalance() - amount); System.out.println(getName() + " : " + "Balance is " + account.getBalance()); } else System.out.println("The balance is not enough"); } }

使用上述Account類及DrawMoney類定義一個賬戶,兩個用戶,兩個用戶同時對賬戶操作(取錢)。

public class SynchronizedTest {

    public static void main(String[] args) {
        Account account = new Account(100);
        DrawMoney user1 = new DrawMoney(account, 70);
        DrawMoney user2 = new DrawMoney(account, 70);
        user1.start();
        user2.start();
    }
}

運行結果:

Thread-0 : Get money:70.0
Thread-1 : Get money:70.0
Thread-0 : Balance is 30.0
Thread-1 : Balance is -40.0

由上可知,第二個用戶取錢出現錯誤,余額不應當小於0。這是由於兩個並發運行的線程(同時取錢的用戶)同時對account操作,而不是一個取錢完成,再交給下一個。用戶1還沒來得及修改余額,用戶2就開始取錢。

修改上述線程類,同步其中的取錢操作

class DrawCash extends Thread {

    private Account account;
    private double amount;

    public DrawCash(Account account, double amount) {
        this.account = account;
        this.amount = amount;
    }

    public void run() {

        // 使用account作為同步監視器,線程在執行下面的代碼之前需要先鎖定account

        synchronized (account) {
            if (amount <= account.getBalance()) {
                System.out.println(getName() + " : " + "Get money:" + amount);
                try {
                    Thread.sleep(2);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                account.setBalance(account.getBalance() - amount);
                System.out.println(getName() + " : " + "Balance is " + account.getBalance());
            }
            else
                System.out.println(getName() + " : " + "The balance is not enough");
        }
    }
}

這時

public class SynchronizedTest {

    public static void main(String[] args) {
        Account account = new Account(100);
        DrawCash user1 = new DrawCash(account, 70);
        DrawCash user2 = new DrawCash(account, 70);
        user1.start();
        user2.start();
    }
}

運行結果:

Thread-0 : Get money:70.0
Thread-0 : Balance is 30.0
Thread-1 : The balance is not enough

第一個線程執行同步代碼塊時鎖定監視器account,第二個線程執行同步代碼塊時也需要鎖定監視器account,

但此時account被線程0鎖定,故線程1只有在線程0的同步代碼塊執行完畢後才能執行其同步代碼塊。

使用DrawMoney與DrawCash各定義一個用戶,對同一個賬戶取錢。

public class SynchronizedTest {

    public static void main(String[] args) {
        Account account = new Account(100);
        DrawCash user1 = new DrawCash(account, 70);
        DrawMoney user2 = new DrawMoney(account, 70);
        user1.start();
        user2.start();
    }
}

運行結果:

Thread-0 : Get money:70.0
Thread-1 : Get money:70.0
Thread-0 : Balance is 30.0
Thread-1 : Balance is -40.0

結果依舊出錯,這是由於線程0需要鎖定監視器account,但線程1不需要,故該情況下account的訪問仍會出現線程不安全。

同步方法

被synchronized修飾的方法為同步方法,同步方法的同步監視器為this,即與該方法對應的對象(該方法所在的類生成的對象)。

    public synchronized void draw() {

    }

某個線程若要調用draw()方法,需要先鎖定draw()對應的對象。

修改Account類,添加同步方法

class Account {

    // 賬戶余額
    private double balance;

    public Account(double balance) {
        this.balance = balance;
    }
public synchronized void draw(double amount) {
        if (amount > balance)
            System.out.println(Thread.currentThread().getName() + " : " + "Balance is not enough");
        else {
            System.out.println(Thread.currentThread().getName() + " : " + amount);
            balance -= amount;
            System.out.println(Thread.currentThread().getName() + " : " + "Balance is " + balance);
        }
    }

}

修改DrawMoney類

class DrawMoney extends Thread {

    private Account account;  // 待取賬戶
    private double amount;    // 取出金額

    public DrawMoney(Account account, double amount) {
        this.account = account;
        this.amount = amount;
    }

    // 取出account中的余額,取出數量為amount
    public void run() {
        account.draw(amount);
    }
}

這時

public class SynchronizedTest {

    public static void main(String[] args) {
        Account account = new Account(100);
        DrawMoney user1 = new DrawMoney(account, 70);
        DrawMoney user2 = new DrawMoney(account, 70);
        user1.start();
        user2.start();
    }
}

運行結果:

Thread-0 : 70.0
Thread-0 : Balance is 30.0
Thread-1 : Balance is not enough

可見,線程是安全的

線程0調用draw()方法時鎖定監視器account,1線程調用draw()時也需要鎖定監視器account,

但此時account被線程0鎖定,故線程1只有在線程0的調用完畢後才能調用。

上述的同步方法也可以用同步代碼塊實現:

class Account {

    // 賬戶余額
    private double balance;

    public Account(double balance) {
        this.balance = balance;
    }

    public void draw(double amount) {

        synchronized (this) {
            if (amount > balance)
                System.out.println(Thread.currentThread().getName() + " : " + "Balance is not enough");
            else {
                System.out.println(Thread.currentThread().getName() + " : " + amount);
                balance -= amount;
                System.out.println(Thread.currentThread().getName() + " : " + "Balance is " + balance);
            }
        }
    }
}

總結:

同步代碼塊與同步方法都是表明在執行某段代碼前需要先鎖定某個對象,同步代碼塊需指定,同步方法默認為this。

java 同步代碼塊與同步方法