1. 程式人生 > >Synchronized關鍵字淺析

Synchronized關鍵字淺析

總結的話寫在前面:

Synchronized(A){

   //程式碼塊

 。。。。。

}

A可以表示普通成員變數,靜態成員變數,類例項,類等。

持有A所在的記憶體區域的鎖時才能執行該段程式碼塊。

所以對於多個執行緒執行的同步程式碼塊是否需要獲取同一把鎖,關鍵是看Synchronized修飾詞A所指向的是否為同一個記憶體地址。

來看程式碼:

public class Count {
    int num = 200;
 
    public void count_num() {
        this.num--;
    }
 
    public static void main(String args[]) {
        Count c = new Count();
        ThreadNum t1 = new ThreadNum(c);
        ThreadNum t2 = new ThreadNum(c);
        ThreadNum t3 = new ThreadNum(c);
        ThreadNum t4 = new ThreadNum(c);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}
 
class ThreadNum extends Thread {
    Count t_;
    String name;
 
    public ThreadNum(Count t) {
        this.t_ = t;
    }
 
    public void run() {
       while (true) {
            synchronized (t_) {
                this.name = Thread.currentThread().getName();
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                if(t_.num > 0){
                    t_.count_num();
                     
                }else{
                    break;
                }
                System.out.println(this.name + " " + t_.num);               
            }
        }
    }
}

t1,t2,t3,t4四個執行緒的同步程式碼塊共用的時同一個物件例項c的鎖,記憶體中都指向同一個Count引用,所以實現了count有序遞減。

再來看Synchronized修飾類成員變數:

public class ThreadTestffd {
 
 
	private Integer a = 0;
	private Integer b = 0;
	public static void main(String[] args) {
		ThreadTestffd test = new ThreadTestffd();
		test.A();
		test.B();
	}
 
 
	public void A() {
		new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("A執行緒準備");
 
 
				synchronized (a) {
					System.out.println("A執行緒開始");
					for (int i = 0; i < 10; i++) {
						System.out.println("a" + i);
						try {
							Thread.sleep(500);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
				}
			}
		}).start();
	}
 
 
	public void B() {
		new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("B執行緒準備");
				synchronized (b) {
					System.out.println("B執行緒開始");
					for (int i = 0; i < 10; i++) {
						System.out.println("b" + i);
						try {
							Thread.sleep(500);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
				}
			}
		}).start();
	}
}

A方法Synchronized修飾成員變數a,B方法修飾成員變數b,因為a和b都是同一個物件(Integer)且記憶體中指向的地址一致(test.a==test.b)為true,故最後的結果是先等a方法執行完之後,才打印出來b執行緒開始。

如果把程式碼改成

    private  Integer a = 0;
	private  Integer b = 0;
	public static void main(String[] args) {
		ThreadTestffd test = new ThreadTestffd();
		test.A();
		ThreadTestffd test2 = new ThreadTestffd();
		test2.B();
	}

同樣的分析過程:

(test.a==test2.b)為true,故最後的結果是先等a方法執行完之後,才打印出來b執行緒開始。

如果把程式碼改成

    private  Integer a = 0;
	private  Integer b = 1;
	public static void main(String[] args) {
		ThreadTestffd test = new ThreadTestffd();
		test.A();
		test.B();
	}

那麼,a和b記憶體地址指向不一致(test.a==test.b)為false,故最後的結果是a和b執行緒交替列印。

程式碼改成

	private Person a=new Person();
	private Person b=new Person();
	public static void main(String[] args) {
		ThreadTestffd test = new ThreadTestffd();
		test.A();
		test.B();
	}

那麼,a和b記憶體地址指向兩個不同的Person引用(test.a==test.b)為false,故最後的結果是a和b執行緒交替列印。

程式碼改成

    private Integer[] a = new Integer[]{0,1};
	private Integer[] b = new Integer[]{2,1};
	public static void main(String[] args) {
		ThreadTestffd test = new ThreadTestffd();
		test.A();
		test.B();
		System.out.println(test.a[1]==test.b[1]);
	}

且方法A和方法B中的Synchronized關鍵字修飾變數由a,b分別改為a[1],b[1],因為(test.a[1]==test.b[1])為true,所以a,b雖然是不同的陣列,但在a[1]和b[1]記憶體中指向的地址是一致的,所以最後的結果是先等a方法執行完之後,才打印出來b執行緒開始。

 

同理,不難理解Synchronized修飾靜態方法(public static synchronized void test(){})和程式碼塊Synchronized(A.Class){

}

都是指向記憶體中方法區內某個類的鎖,所以對於同一個類來說,他們是同一把鎖