1. 程式人生 > >java多執行緒物件鎖、類鎖、同步機制詳解

java多執行緒物件鎖、類鎖、同步機制詳解

1.在java多執行緒程式設計中物件鎖、類鎖、同步機制synchronized詳解:

    物件鎖:在java中每個物件都有一個唯一的鎖,物件鎖用於物件例項方法或者一個物件例項上面的。

    類鎖:是用於一個類靜態方法或者class物件的,一個類的例項物件可以有多個,但是隻有一個class物件。

    同步機制synchronizedsynchronized關鍵字用於修飾方法或者單獨的synchronized程式碼塊,當一個執行緒想執行synchronized中的內容時,必須先獲取到物件鎖,當物件鎖沒有執行緒佔用時,進入synchronized方法會自動獲取到物件鎖,執行完畢後會自動釋放鎖,

如果物件鎖被A執行緒佔用,B執行緒想執行synchronized的程式碼只能等待A個執行緒執行完畢後,釋放物件鎖,B執行緒才能獲取到物件鎖進入方法執行。一個執行緒獲得物件A的鎖,也可以獲得物件B的鎖,兩個不同類的物件鎖沒有關聯。

舉例說明包含synchronized的方法和synchronized程式碼塊

package com.test;

public class TestThread {

	public void test1() {
		synchronized (this) {
			int i = 0;
			while (i++<5) {
				System.out.println(Thread.currentThread().getName() + " : " + i);
				try {
					Thread.sleep(500);
				} catch (InterruptedException ie) {
				}
			}
		}
	}

	public synchronized void test2() {
		int i = 0;
				while (i++<5) {
			System.out.println(Thread.currentThread().getName() + " : " + i);
			try {
				Thread.sleep(500);
			} catch (InterruptedException ie) {
			}
		}
	}

	public static void main(String[] args) {
		final TestThread test = new TestThread();
		Thread test1 = new Thread(new Runnable() {
			public void run() {
				test.test1();
			}
		}, "test1");
		Thread test2 = new Thread(new Runnable() {
			public void run() {
				test.test2();
			}
		}, "test2");
		test1.start();
		test2.start();
	}
}


以上程式碼執行結果如下:

以上程式碼是因為執行同一個物件,所以另一個執行緒要等前一個物件,執行完成後釋放掉物件鎖,拿到物件鎖才能繼續執行synchronized裡面的東西。

如果我們去掉synchronized修飾的方法或者synchronized程式碼塊,將會打印出以下的結果:


如何要是去掉一個synchronized後,輸出的語句是交叉執行的。這就說明,對於同一個物件,如果執行緒A得到了物件鎖,執行緒B可以訪問物件沒有同步的方法和程式碼。進行同步的程式碼和沒有同步的程式碼是互不影響的。

舉例類鎖:

package com.test;
public class MyThreadClass {
	public static void main(String[] args) {
		final MyThreadClass my=new MyThreadClass();
		Thread thread1=new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					my.test1();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		},"test1");
		Thread thread2=new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					MyThreadClass.test2();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		},"test2");
		thread1.start();
		thread2.start();
	}
	
	
	public void test1() throws InterruptedException{
		synchronized (MyThreadClass.class) {
			int i=0;
			while(i++<5){
				System.out.println(Thread.currentThread().getName()+":"+i);
				Thread.sleep(500);
			}
		}
	}
	
	public static synchronized void test2() throws InterruptedException{
			int i=0;
			while(i++<5){
				System.out.println(Thread.currentThread().getName()+":"+i);
				Thread.sleep(500);
			}
	}
}	
執行結果:



類鎖和物件鎖其實是一樣的,只是針對於不同的物件。

如果兩個方法一個被synchronized修飾,一個靜態方法被synchronized修飾(體現類鎖和物件鎖的區別),程式碼如下:

package com.test;
public class MyThreadClass2 {
	public static void main(String[] args) {
		final MyThreadClass2 my=new MyThreadClass2();
		Thread thread1=new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					my.test1();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		},"test1");
		Thread thread2=new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					MyThreadClass2.test2();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		},"test2");
		thread1.start();
		thread2.start();
	}
	
	public synchronized void test1() throws InterruptedException{
			int i=0;
			while(i++<5){
				System.out.println(Thread.currentThread().getName()+":"+i);
				Thread.sleep(500);
			}
	}
	
	public static synchronized void test2() throws InterruptedException{
			int i=0;
			while(i++<5){
				System.out.println(Thread.currentThread().getName()+":"+i);
				Thread.sleep(500);
			}
	}
}	
程式碼執行結果如下:


結果也是交叉輸出的,雖然都是被synchronized修飾,但是一個是屬於物件的,一個是屬於class。所以類鎖和物件鎖是不同的,他們控制著不同的區域,互不干擾。

疑問:java中既然synchronized修飾的方法和synchronized程式碼塊作用是一樣的,為什麼還需要synchronized程式碼塊呢?

使用synchronized修飾方式是直接在方法上面加鎖,synchronized方法塊是在方法裡面加鎖,一個範圍大,一個範圍小。還有一個最主要的在應用場景如下:在Class中建立一個物件,在Class中要執行這個物件的某個方法,為了防止多個執行緒同時執行,採用同步加鎖,但是如果這個方法出現死迴圈或者執行時間很長,其他執行緒也不能執行物件的其他同步方法,需要等待這個執行緒執行完畢,影響系統性能。如果採用synchronized修飾方法和synchronized程式碼塊可能都會出現這種情況,我們來模擬一下這種狀況:

建立一個java物件類,裡面有兩個方法:

package com.test;

public class Test {
	public void test1(){
	}
	public void test2(){
	}
}
建立測試類程式碼如下:

package com.test;

public class MyThreadClass4 {
	static Test test=new Test();
	public static void main(String[] args) {
		final MyThreadClass4 my=new MyThreadClass4();
		Thread thread1=new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					my.test1();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		},"test1");
		Thread thread2=new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					my.test2();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		},"test2");
		thread1.start();
		thread2.start();
	}
	long startTime;
	public synchronized void test1() throws InterruptedException{
			test.test1();
			System.out.println(Thread.currentThread().getName());
			startTime = System.currentTimeMillis();//獲取當前時間
			Thread.sleep(5000);
	}
	
	public synchronized void test2() throws InterruptedException{
		long endTime = System.currentTimeMillis();
		test.test2();
		System.out.println(Thread.currentThread().getName());
		System.out.println("程式執行時間:"+(endTime-startTime)+"ms");
	}
}	

在class裡面建立物件例項,然後將兩個方法分別呼叫,在呼叫方法上面都加上synchronized修飾方法Thread.sleep(5000)來模擬方法執行時間過長或者死迴圈,但是可以看到時間為我們設定的時間,執行結果不理想執行結果如下:


如果我們採用synchronized程式碼塊的方式如下:

package com.test;

public class MyThreadClass3 {
	static Test test=new Test();
	public static void main(String[] args) {
		final MyThreadClass3 my=new MyThreadClass3();
		Thread thread1=new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					my.test1();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		},"test1");
		Thread thread2=new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					my.test2();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		},"test2");
		thread1.start();
		thread2.start();
	}
	long startTime;
	public void test1() throws InterruptedException{
		synchronized (test) {
			test.test1();
			System.out.println(Thread.currentThread().getName());
			startTime = System.currentTimeMillis();//獲取當前時間
			Thread.sleep(5000);
		}
	}
	
	public synchronized void test2() throws InterruptedException{
		long endTime = System.currentTimeMillis();
		test.test2();
		System.out.println(Thread.currentThread().getName());
		System.out.println("程式執行時間:"+(endTime-startTime)+"ms");
	}
}	
synchronized程式碼塊中我們只是對當前test物件加鎖,和執行這塊程式碼的物件沒有任何關係。執行test1的同時,我照樣可以執行其他的synchronized同步方法,增強系統性能。執行結果如下: