分類
Thread

java static field synchronization in Multi-Thread

這是對在 multi-thread 中 static field 同步化的紀錄。
以下是 TestThread,在 run 函式結束後,我們希望 mNumber 不會成為負數。
重點放在第3行的 mNumber 。目前它不是 static field。

public class TestThread extends Thread
{
    private int mNumber = 100;
    public void run()
    {
        while (mNumber >= 10) {
            delayRandomMillSeconds();
            mNumber -= 10;
            System.out.println("thread name:"+getName()+" in while number:"+mNumber);
        }
        System.out.println("thread name:"+getName()+" final number:"+mNumber);
    }
    private void delayRandomMillSeconds()
    {
        try {
            Thread.sleep(new Random().nextInt(500));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在非共享呼叫的情況(個別產生實體)下,mNumber不會成為負數。

    private static void runWithoutSharedResource()
    {
        new TestThread().start();
        new TestThread().start();
        new TestThread().start();
    }

Output:

thread name:Thread-2 in while number:90
thread name:Thread-0 in while number:90
thread name:Thread-1 in while number:90
thread name:Thread-2 in while number:80
thread name:Thread-1 in while number:80
thread name:Thread-0 in while number:80
thread name:Thread-0 in while number:70
thread name:Thread-1 in while number:70
thread name:Thread-2 in while number:70
thread name:Thread-2 in while number:60
thread name:Thread-2 in while number:50
thread name:Thread-2 in while number:40
thread name:Thread-0 in while number:60
thread name:Thread-0 in while number:50
thread name:Thread-1 in while number:60
thread name:Thread-1 in while number:50
thread name:Thread-2 in while number:30
thread name:Thread-0 in while number:40
thread name:Thread-1 in while number:40
thread name:Thread-2 in while number:20
thread name:Thread-0 in while number:30
thread name:Thread-2 in while number:10
thread name:Thread-1 in while number:30
thread name:Thread-0 in while number:20
thread name:Thread-2 in while number:0
thread name:Thread-2 final number:0
thread name:Thread-1 in while number:20
thread name:Thread-0 in while number:10
thread name:Thread-0 in while number:0
thread name:Thread-0 final number:0
thread name:Thread-1 in while number:10
thread name:Thread-1 in while number:0
thread name:Thread-1 final number:0

但若把 mNumber 改為 static 。

public class TestThread extends Thread
{
    private static int mNumber = 100;
    public void run()
    {
        while (mNumber >= 10) {
            delayRandomMillSeconds();
            mNumber -= 10;
            System.out.println("thread name:"+getName()+" in while number:"+mNumber);
        }
        System.out.println("thread name:"+getName()+" final number:"+mNumber);
    }
    private void delayRandomMillSeconds()
    {
        try {
            Thread.sleep(new Random().nextInt(500));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

這時候 mNumber 就成為共享資源(Shared Resource)。
一樣的呼叫方式(個別產生實體)

    private static void runWithoutSharedResource()
    {
        new TestThread().start();
        new TestThread().start();
        new TestThread().start();
    }

output:

thread name:Thread-2 in while number:90
thread name:Thread-0 in while number:80
thread name:Thread-1 in while number:70
thread name:Thread-0 in while number:60
thread name:Thread-2 in while number:50
thread name:Thread-1 in while number:40
thread name:Thread-1 in while number:30
thread name:Thread-2 in while number:20
thread name:Thread-1 in while number:10
thread name:Thread-0 in while number:0
thread name:Thread-0 final number:0
thread name:Thread-1 in while number:-10
thread name:Thread-1 final number:-10
thread name:Thread-2 in while number:-20
thread name:Thread-2 final number:-20

雖然呼叫 TestThread 的方式為個別產生實體,但因為 mNumber 為 static field ,只佔一塊記憶體空間,屬於所有實體共享。
即使是不同實體也會存取到同一個mNumber,對 static field 的同步化必須特別注意才行
有三種方式可以達到 static field 的同步化。
1. static synchronized method,缺點是 static method 內無法呼叫 non-static method。

    public void run()
    {
        decreaseNumber();
    }
    private synchronized static void decreaseNumber()
    {
        while (mNumber >= 10) {
            delayRandomMillSeconds();
            mNumber -= 10;
            System.out.println("thread name:"+getName()+" in while number:"+mNumber);
        }
        System.out.println("thread name:"+getName()+" final number:"+mNumber);
    }

2. synchronized block with class object

    public void run()
    {
        synchronized(TestThread.class){
            while (mNumber >= 10) {
                delayRandomMillSeconds();
                mNumber -= 10;
                System.out.println("thread name:"+getName()+" in while number:"+mNumber);
            }
            System.out.println("thread name:"+getName()+" final number:"+mNumber);
        }
    }

3. synchronized block with lock object

    private static Object sLock = new Object();
    public void run()
    {
        synchronized(sLock){
            while (mNumber >= 10) {
                delayRandomMillSeconds();
                mNumber -= 10;
                System.out.println("thread name:"+getName()+" in while number:"+mNumber);
            }
            System.out.println("thread name:"+getName()+" final number:"+mNumber);
        }
    }

 
 
 
 

分類
Thread

Single Threaded Execution Pattern(單線程執行模式)

Description :

Single Threaded Execution 代表以一個線程執行的意思,也稱critical section(臨界區)
該 Pattern 用來限制只能讓一個線程進行,也是多線程的基礎。
 

Role

Shared Resource (SR)(共享資源):
SR是會被多線程共同存取的類別,該類別的方法可以分為2類:
1. Safe method : 多線程同時存取也不會出錯的方法。
2. UnSafe method : 多線程同時存取會出錯的方法,需要加上防護(synchronized)限制單線程去存取。e.g.,

        BankRunnable bankRunnable = new BankRunnable();
        Thread thread1 = new Thread(bankRunnable,"1");
        Thread thread2 = new Thread(bankRunnable,"2");
        thread1.start();
        thread2.start();

bankRunnable 就是 SR 的角色,因為它會被 thread1 thread2 共同存取。
 

public class BankRunnable implements Runnable{
    private Bank mBank;
    public BankRunnable(Bank bank) {
        mBank = bank;
    }
    public BankRunnable(){
        mBank = new Bank("in BankRunnable",1000);
    }
    @Override
    public void run() {
            makeDraw();
    }
  private synchronized void makeDraw() {
        System.out.println("Thread in makeDraw:"+Thread.currentThread().getName());
        while (mBank.getMoney() >= 100) {
            try {
                Thread.sleep(new Random().nextInt(500));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            mBank.withDraw(100);
            System.out.println("Thread:"+Thread.currentThread().getName()+" "+mBank.getName() + " " + mBank.getMoney());
            if (mBank.getMoney() < 0) {
                System.out.println("over draw");
            }
        }
    }
}

BankRunnable 的方法中,makeDraw 即為 unsafe method , 其他的方法即是 safe method
對於 unsafe method 必須加上 synchronized 來保證同步化。
 

When to use??

滿足以下 個條件
1. 多線程。
2. 出現 shared resource
3. shared resource 的狀態(屬性)會發生變化,若狀態(屬性)不會發生變化,不需要使用synchronized
 
 

Note :

1.使用 synchronized 需要注意死鎖(deadlock),死鎖為2個線程分別獲取鎖定,互相等待對方解鎖的情況。
發生死鎖,程序會無法進行下去。
但要達到死鎖也必須滿足下列其一:
        1.具有多個shared resource
        2.線程鎖定一個shared resource,還沒解鎖前就去鎖定另一個shared resource
        3.取得 shared resource 的參與者順序不固定。
只要破壞上述條件其一就可避免死鎖。
 
2.unsafe method 複寫問題,若有 subclass 繼承 BankRunnable 並複寫了 makeDraw() 方法,但沒有加上
synchronized來修飾,同步化的問題又會出現,可以使用 final 來修飾superclass避免繼承。
 
3. 原子操作:
synchronized 的方法同時只有一個線程可以操作,因此這個方法為原子的操作。
java的基本類型(除了longdouble以外)和物件的賦值和引用也都是原子的。
在宣告屬性時加上volatile 代表所有對該屬性的操作都是不可分割的。
 
4.因此 single threaded execution pattern 可以簡單的描述為以下步驟
1.找出多線程的 shared resource
2.找出shared resource unsafe method 方法。
3.unsafe method 方法加上 synchronized
4.考慮死鎖或其他效率問題。