全面解析Synchronized

在多線程的環境下,常常存在線程安全問題,這種問題產生的緣由在於:該是原子操做的代碼段被其餘的線程切割,從而引發的數據混亂問題。在本篇博客中將講述如何使用synchronized關鍵字保證代碼段的原子操做。java

synchronized關鍵字

無論synchronized以何種方式使用,都會對一個對象加鎖,這個對象也就是所謂的監視器安全

synchronized關鍵字具備一下特徵:多線程

(1)若是對持有相同鎖的synchronized方法或者代碼塊,同步執行(即排隊,執行完一個,另外一個才能執行)異步

(2)若是對持有不一樣鎖的synchronized方法或者代碼塊,異步執行ide

(3)synchronized與非synchronized方法或者代碼塊,異步執行性能

synchronized方法

使用synchronized來修飾方法(非static方法),其實就是this對象加鎖測試

下面經過synchronized方法來看一下上述的三個特徵優化

首先定義一個操做類MyObject,在類中有三個方法init,alter,print,其中init,alter方法使用synchronized修飾,代碼以下:
this

package com.feng.example;

public class MyObject {
	
	synchronized public void init()
	{
		
		try {
			System.out.println(Thread.currentThread().getName()+"init begin");
			Thread.sleep(2000);
			System.out.println(Thread.currentThread().getName()+"init end");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	synchronized public void alter()
	{
		try {
			System.out.println(Thread.currentThread().getName()+"alter begin");
			Thread.sleep(2000);
			System.out.println(Thread.currentThread().getName()+"alter end");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	
	public void printInfo()
	{
		try {
			System.out.println(Thread.currentThread().getName()+"print begin");
			Thread.sleep(2000);
			System.out.println(Thread.currentThread().getName()+"print end");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	

}

定義三個線程,分別對MyObject進行處理:spa

package com.feng.example;

public class MyThreadA extends Thread{

	private MyObject object;
	
	public MyThreadA(MyObject object)
	{
		this.object = object;
	}
	
	@Override
	public void run() {
		
		object.init();
	}	
}

package com.feng.example;

public class MyThreadB extends Thread{

private MyObject object;
	
	public MyThreadB(MyObject object)
	{
		this.object = object;
	}
	
	@Override
	public void run() {

		object.alter();
	}
	
}

package com.feng.example;

public class MyThreadC extends Thread{

private MyObject object;
	
	public MyThreadC(MyObject object)
	{
		this.object = object;
	}
	
	@Override
	public void run() {

		object.printInfo();
	}
	
}

測試類以下:

package com.feng.example;

public class ThreadTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		MyObject object1 = new MyObject();
		MyObject object2 = new MyObject();
		
		Thread a = new MyThreadA(object1);
		Thread b = new MyThreadB(object1);
		Thread c = new MyThreadC(object1);
		a.start();
		b.start();
		c.start();
		
		
	}

}

分析:在測試類中,三個線程都是以實例化對象object1做爲參數,那麼三個類處理的都是object1對象,由於MyThreadA,MyThreadB處理的object1對象的同步方法,他們持有的監視器都是object1對象,所以二者應該是同步的,即排隊執行,MyThreadC處理的是非synchronized方法,所以和另外兩個線程是異步執行的。因此init方法,和alter方法是順序執行(即一個執行完,另外一個在執行,可是誰在前取決於誰先搶到object1的鎖),print方法和init,alter方法是異步執行,能夠在任意位置輸出。

程序運行結果以下:

Thread-2print begin
Thread-0init begin
Thread-2print end
Thread-0init end
Thread-1alter begin
Thread-1alter end

從本例中說明對持有相同鎖的多個線程在執行synchronized方法時同步執行。synchronized方法與非synchronized方法異步執行。

下面修改測試類,觀察若是線程處理的對象不是一個對象會出現什麼狀況:

package com.feng.example;

public class ThreadTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		MyObject object1 = new MyObject();
		MyObject object2 = new MyObject();
		
		Thread a = new MyThreadA(object1);
		Thread b = new MyThreadB(object2);
		
		a.start();
		b.start();	
		
	}

}

分析:線程a操做的對象是object1,線程b操做的對象是object2。另外一層意思,線程a執行init方法是獲取的是object1對象的鎖,線程b執行alter方法時獲取的是object2對象的鎖。雖然兩個方法都是synchronized方法,可是二者想要獲取的鎖不一樣,所以也就不存在同步問題。

執行結果以下:

Thread-0init begin
Thread-1alter begin
Thread-0init end
Thread-1alter end

從結果中能夠看出,線程0中init的操做被線程1的alter操做分隔了,說明二者異步執行。

從而證實了synchronized方法持有的是this對象鎖。對持有不一樣鎖對象的synchronized方法異步執行。

經過上面的實驗證實了synchronized關鍵字的3個特徵。

synchronized的可重入性

(1)同步方法中調用其餘持有相同監視器的同步方法

修改操做類,代碼以下:

package com.feng.example;

public class MyObject {
	
	synchronized public void init()
	{
		
		try {
			System.out.println(Thread.currentThread().getName()+"====init begin");
			Thread.sleep(2000);
			alter();
			System.out.println(Thread.currentThread().getName()+"====init end");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	synchronized public void alter()
	{
		try {
			System.out.println(Thread.currentThread().getName()+"====alter begin");
			Thread.sleep(2000);
			System.out.println(Thread.currentThread().getName()+"====alter end");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	
	public void printInfo()
	{
		try {
			System.out.println(Thread.currentThread().getName()+"====print begin");
			Thread.sleep(2000);
			System.out.println(Thread.currentThread().getName()+"====print end");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	

}

測試類以下:

package com.feng.example;

public class ThreadTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		try {
			MyObject object1 = new MyObject();
			
			Thread a = new MyThreadA(object1);
			Thread b = new MyThreadB(object1);
			
			a.start();
			Thread.sleep(1000);
			b.start();	
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
	}

}

分析:實例化操做類對象object1,建立兩個線程a,b,a線程啓動,執行init方法,線程a獲取了object1對象的鎖,在init方法方法中睡眠2s,執行init方法1s後啓動線程b,b企圖得到object1對象鎖,因爲被線程a佔有,因此須要等待。2s事後init方法中調用alter方法,須要獲取object1對象的鎖,如今線程a已經得到了object1對象的鎖,能夠直接進入alter方法,這就叫課重入性。執行完alter方法以後,線程a釋放鎖,線程b再執行alter方法。

輸出結果以下:

Thread-0====init begin
Thread-0====alter begin
Thread-0====alter end
Thread-0====init end
Thread-1====alter begin
Thread-1====alter end

(2)子類調用父類的同步方法

建立父類:

package com.feng.example;

public class Person {
	
	synchronized public void eat()
	{
		System.out.println("person eat");
	}

}

建立子類:

package com.feng.example;

public class Student extends Person{
	
	synchronized public void study()
	{
		System.out.println("student study");
		super.eat();
		System.out.println("study end");
	}

}

測試類:

package com.feng.example;

public class ThreadTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
	
			Student student = new Student();
			
			Thread a = new MyThread(student);
			
			a.start();
		
		
	}

}

分析:啓動線程a,a獲取了student的鎖,執行study方法,在study方法中調用父類的eat方法,不須要從新獲取鎖便可進入同步方法。

執行結果以下:

student study
person eat
study end

synchronized拋出異常,自動釋放鎖

修改操做類:

package com.feng.example;

public class MyObject {
	
	synchronized public void init()
	{
		
		try {
			System.out.println(Thread.currentThread().getName()+"====init begin");
			Thread.sleep(2000);
			throw new InterruptedException();
		} catch (InterruptedException e) {
			System.out.println("拋出異常");
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	synchronized public void alter()
	{
		try {
			System.out.println(Thread.currentThread().getName()+"====alter begin");
			Thread.sleep(2000);
			System.out.println(Thread.currentThread().getName()+"====alter end");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}	
}

線程類使用上面的MyThreadA,MyThreadB,此處再也不列出

測試類以下:

package com.feng.example;

public class ThreadTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
			
			try {
				MyObject object = new MyObject();
				
				Thread a = new MyThreadA(object);
				Thread b = new MyThreadB(object);
				
				a.start();
				Thread.sleep(1000);       //此處是要保證線程a先執行
				b.start();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} 
			
	}

}

分析:線程a執行首先獲取object對象鎖,執行init方法,執行完init時拋出異常,若是線程a不釋放鎖,那麼線程b就不會執行。只要線程b執行了,就說明拋出異常,線程鎖釋放。

運行結果以下:

Thread-0====init begin
拋出異常
java.lang.InterruptedException
	at com.feng.example.MyObject.init(MyObject.java:11)
	at com.feng.example.MyThreadA.run(MyThreadA.java:15)
Thread-1====alter begin
Thread-1====alter end

說明線程a拋出異常時,釋放了線程鎖

synchronized方法不具備繼承性

不具備繼承性主要體如今方法的重寫上,對父類的同步方法進行重寫,若是不加synchronized關鍵字就不是同步方法

經過程序驗證上述結論:

package com.feng.example;

public class Person {
	
	synchronized public void eat()
	{
		System.out.println("person eat");
	}

}

package com.feng.example;

public class Student extends Person{

	public void eat() {
		
		try {
			System.out.println("student eat...");
			Thread.sleep(4000);
			System.out.println("student eat end...");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	synchronized public void study()
	{
			
		try {
			System.out.println("student study...");
			Thread.sleep(2000);
			System.out.println("student study end...");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
}

定義兩個線程:

package com.feng.example;

public class StudentThreadA extends Thread{
	
	private Student student;
	
	public StudentThreadA(Student student)
	{
		this.student = student;
	}
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		super.run();
		student.eat();
	}

}

package com.feng.example;

public class StudentThreadB extends Thread{
	
	private Student student;
	
	public StudentThreadB(Student student)
	{
		this.student = student;
	}
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		super.run();
		student.study();
	}	

}

測試類:

package com.feng.example;

public class ThreadTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
			
		Student student = new Student();
		Thread a = new StudentThreadA(student);
		Thread b = new StudentThreadB(student);
		
		a.start();
		b.start();
			
	
	}

}

執行結果以下:

student eat...
student study...
student study end...
student eat end...

分析:從運行結果中能夠看出兩個方法並非同步輸出的,所以重寫的eat方法不是同步方法。

synchronized語句塊

synchronized方法的弊端

使用synchronized方法,存在明顯的性能問題,在一個方法中可能只有幾個語句須要同步,使用synchronized方法卻使整個方法都同步了。執行速度確定會慢,咱們能夠經過synchronized語句塊來優化程序。

舉例說明synchronized方法的性能問題。

定義操做類:

package com.feng.example;

public class MyObject {
	
	private String data1;
	private String data2;
	
	synchronized public void init()
	{
		
		try {
			System.out.println(Thread.currentThread().getName()+"====init begin");
			Thread.sleep(2000);
			data1 = "長時間的處理後從服務端獲取的值data1";   //模擬從服務端取數據
			data2 = "長時間的處理後從服務端獲取的值data2";
			System.out.println(data1);
			System.out.println(data2);
			System.out.println(Thread.currentThread().getName()+"====init end");
		} catch (InterruptedException e) {
			System.out.println("拋出異常");
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
}

定義線程類:

package com.feng.example;

public class MyThread extends Thread {
	
	private MyObject object;
	
	public MyThread(MyObject object)
	{
		this.object = object;
	}

	@Override
	public void run() {
		
		System.out.println(Thread.currentThread().getName()+":開始運行時間爲:"+System.currentTimeMillis());
		object.init();
		System.out.println(Thread.currentThread().getName()+":結束時間爲:"+System.currentTimeMillis());
	}
}

測試類:

package com.feng.example;

public class ThreadTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
			
		MyObject object = new MyObject();
		
		Thread a = new MyThread(object);
		Thread b = new MyThread(object);
		
		a.start();
		b.start();
			
	
	}

}

分析:在測試類中定義了兩個線程實例a,b  這兩個線程都以object實例做爲參數,對object進行操做。首先線程a執行,輸出線程a的開始執行時間,執行object對象的init方法,init方法是同步方法。在執行init的同時,線程b也啓動了,輸出線程b的開始執行時間,這時線程b企圖獲取object對象的鎖,因爲如今object的鎖被線程a佔用,因此必須等待線程a執行完init方法以後纔會得到。線程a休眠2秒,這2s主要是模擬長時間的操做(假設就是從服務端獲取數據),給成員變量賦值,打印兩個成員變量,運行完成,釋放鎖。輸出線程a的結束時間,線程b執行object對象的init的放,進行一些列的操做。

運行結果以下:

Thread-1:開始運行時間爲:1449471254353
Thread-1====init begin
Thread-0:開始運行時間爲:1449471254366
長時間的處理後從服務端獲取的值data1
長時間的處理後從服務端獲取的值data2
Thread-1====init end
Thread-1:結束時間爲:1449471256364
Thread-0====init begin
長時間的處理後從服務端獲取的值data1
長時間的處理後從服務端獲取的值data2
Thread-0====init end
Thread-0:結束時間爲:1449471258365

查看這兩個線程都完成操做所用的總時間就是Thread-0的結束時間減去Thread-1開始運行時間:1449471258365-1449471254353 = 4012 花費了大約4s的時間。可是想服務端獲取數據徹底能夠異步去執行,這樣就能夠減小執行時間了。

修改操做對象MyObject的代碼,使用synchronized語句塊

package com.feng.example;

public class MyObject {
	
	private String data1;
	private String data2;
	
	public void init()
	{
		
		try {
			System.out.println(Thread.currentThread().getName()+"====init begin");
			Thread.sleep(2000);
			String data1Temp = "長時間的處理後從服務端獲取的值data1"+Thread.currentThread().getName();   //模擬從服務端取數據
			String data2Temp = "長時間的處理後從服務端獲取的值data2"+Thread.currentThread().getName();
			synchronized(this)
			{
				data1 = data1Temp;
				data2 = data2Temp;
			}
			System.out.println(data1);
			System.out.println(data2);
			System.out.println(Thread.currentThread().getName()+"====init end");
		} catch (InterruptedException e) {
			System.out.println("拋出異常");
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
}

其線程類與測試類代碼不變,分析程序:

在測試類中建立兩個線程實例a,b 線程a與線程b同時執行object的init方法,因爲此方法是非synchronized方法,所以,兩隻異步執行,(二者誰先執行到synchronized語句塊不肯定)假設線程a先執行到synchronized語句塊,線程a得到了進入synchronized語句塊的鎖,當線程b執行到synchronized語句塊時,嘗試獲取鎖,但要獲得線程a執行完synchronized語句塊,線程a執行完synchronized語句塊後釋放鎖,線程b獲取鎖執行synchronized語句塊的內容。

執行結果以下:

Thread-1:開始運行時間爲:1449472151572
Thread-1====init begin
Thread-0:開始運行時間爲:1449472151577
Thread-0====init begin
長時間的處理後從服務端獲取的值data1Thread-1
長時間的處理後從服務端獲取的值data2Thread-1
Thread-1====init end
Thread-1:結束時間爲:1449472153572
長時間的處理後從服務端獲取的值data1Thread-0
長時間的處理後從服務端獲取的值data2Thread-0
Thread-0====init end
Thread-0:結束時間爲:1449472153577

從執行結果中能夠看出,執行的總時間大約爲2s,相比於使用synchronized方法快了不少。程序的性能獲得了提高

總結:synchronized語句塊就是將同步的內容最小化,使須要同步的信息放到synchronized語句塊中同步執行,不須要同步的信息放到synchronized語句塊外異步執行。

synchronized語句塊的鎖對象,this,synchronized方法,class的區別

synchronized語句塊的鎖對象能夠是任意的對象,不論是什麼對象只要記住文章開頭synchronized關機字的特徵便可。

下面比較一下this,synchronized方法,class做爲鎖的區別:

首先synchronized(this){ } 使用的鎖對象就是操做類自己,不如上例中在線程類中調用的是object.init()方法,說明init方法中的this指的就是object對象本身,所以使用this做爲鎖和synchronized方法沒有本質的區別,區別只在於synchronized(this){ }能夠將同步的範圍縮小,是同步的內容更加精確,提升程序的性能。

sychronized(MyObject.class){ },是將class對象做爲鎖,同一個類的實例都只有一個class文件,class文件是惟一的,也就是說若是使用class做爲鎖,即便調用的是此類不一樣實例對象的方法,都會呈現出同步的效果。

舉例說明:

定義操做類MyObject

package com.feng.example;

public class MyObject {
		
	public void init()
	{
	
		try {
			synchronized(this)
			{
				System.out.println(Thread.currentThread().getName()+"====init begin");
				Thread.sleep(2000);		
				System.out.println(Thread.currentThread().getName()+"====init end");
			}
		} catch (InterruptedException e) {
			System.out.println("拋出異常");
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
}

定義線程類:

package com.feng.example;

public class MyThread extends Thread {
	
	private MyObject object;
	
	public MyThread(MyObject object)
	{
		this.object = object;
	}

	@Override
	public void run() {
		
		object.init();
	}
}

測試類代碼以下:

package com.feng.example;

public class ThreadTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
			
		MyObject object1 = new MyObject();
		MyObject object2 = new MyObject();
		
		Thread a = new MyThread(object1);
		Thread b = new MyThread(object2);
		
		a.start();
		b.start();
			
	
	}

}

分析:因爲在MyObject類中使用的是this做爲鎖,在測試類中定義了兩個MyObject對象object1,object2,實例化線程時使用兩個不一樣的對象做爲參數。線程a調用的是object1.init()方法,線程b調用的是object2.init()方法。兩個線程在執行object.init()方法時,this表明的就不是一個對象,因此是異步執行。

運行結果以下:

Thread-0====init begin
Thread-1====init begin
Thread-0====init end
Thread-1====init end

下面修改MyObject操做類的代碼:將synchronized(this){ } 改成synchronized(MyObject.class){ }

package com.feng.example;

public class MyObject {
		
	public void init()
	{
	
		try {
			synchronized(MyObject.class)
			{
				System.out.println(Thread.currentThread().getName()+"====init begin");
				Thread.sleep(2000);		
				System.out.println(Thread.currentThread().getName()+"====init end");
			}
		} catch (InterruptedException e) {
			System.out.println("拋出異常");
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
}

分析:兩個線程a,b  線程a執行的是object1.init()方法,線程b執行的object2.init()方法,在執行到synchronized(MyObject.class){ }時,誰先執行到,誰就得到此類文件的class鎖,因爲object1和object2的class文件是同一個,所以兩個線程在synchronized語句塊處同步執行。

程序運行結果以下:

Thread-0====init begin
Thread-0====init end
Thread-1====init begin
Thread-1====init end

synchronized語句塊其餘對象做爲鎖

只要是對象均可以做爲鎖,修改上例中的MyObject類:

package com.feng.example;

public class MyObject {
	
	private String lock = new String();
	
	public void init()
	{
	
		try {
			synchronized(lock)
			{
				System.out.println(Thread.currentThread().getName()+"====init begin");
				Thread.sleep(2000);		
				System.out.println(Thread.currentThread().getName()+"====init end");
			}
		} catch (InterruptedException e) {
			System.out.println("拋出異常");
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
}

分析:兩個線程共用一個object對象,object中lock是object的成員變量,也就是說兩個線程共用一個鎖。

所以兩個線程在synchronized語句塊處同步執行。

運行結果以下:

Thread-0====init begin
Thread-1====init begin
長時間的處理後從服務端獲取的值data1Thread-0
長時間的處理後從服務端獲取的值data2Thread-0
Thread-0====init end
長時間的處理後從服務端獲取的值data1Thread-1
長時間的處理後從服務端獲取的值data2Thread-1
Thread-1====init end

特殊狀況,字符串做爲鎖對象

字符串通常是存放在常量池中的,若是兩個字符串表示的內容同樣,兩個字符串指向的是同一個對象。舉例說明:

修改操做類MyObject代碼

package com.feng.example;

public class MyObject {

	
	public void init()
	{
		String str = "我是鎖";
	
		try {
			synchronized(str)
			{
				System.out.println(Thread.currentThread().getName()+"====init begin");
				Thread.sleep(2000);		
				System.out.println(Thread.currentThread().getName()+"====init end");
			}
		} catch (InterruptedException e) {
			System.out.println("拋出異常");
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
}

分析:在init方法中str是一個局部變量,在方法內部局部變量都有本身的臨時空間,各個方法互不干擾,按常理來講,兩個線程a,b應該想獲取的鎖不是同一個鎖,可是這裏是String類型,String類型的內容是放在常量池中的,在本程序中,str雖然是方法內的局部變量,但在不一樣的方法中局部變量都是指向的同一個對象,所以表示的是同一個鎖。

所以不建議使用字符串做爲鎖對象

程序運行結果以下:

Thread-0====init begin
Thread-0====init end
Thread-1====init begin
Thread-1====init end

靜態同步方法

synchronized方法對應synchronized(this){ }語句塊,一樣的synchronized static 方法對應synchronized(class){ }語句塊

靜態同步方法的使用

舉例說明:修改上述程序的操做類代碼:

package com.feng.example;

public class MyObject {

	
	synchronized static public void init()
	{
	
		try {
			
			System.out.println(Thread.currentThread().getName()+"====init begin");
			Thread.sleep(2000);		
			System.out.println(Thread.currentThread().getName()+"====init end");
		} catch (InterruptedException e) {
			System.out.println("拋出異常");
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
}

分析:兩個線程雖然執行的是不一樣對象的init方法,可是init方法是static的synchronized方法,兩個對象的class文件是同一個,所以兩個線程同步執行(與synchronized(MyObject.class){ }一個效果),運行結果以下

Thread-0====init begin
Thread-0====init end
Thread-1====init begin
Thread-1====init end

class鎖與對象鎖異步執行

定義操做類MyObject

package com.feng.example;

public class MyObject {

	synchronized static public void init() {

		try {

			System.out.println(Thread.currentThread().getName()
					+ "====init begin");
			Thread.sleep(2000);
			System.out.println(Thread.currentThread().getName()
					+ "====init end");
		} catch (InterruptedException e) {
			System.out.println("拋出異常");
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	synchronized public void print() {
		try {

			System.out.println(Thread.currentThread().getName()
					+ "====print begin");
			Thread.sleep(2000);
			System.out.println(Thread.currentThread().getName()
					+ "====print end");
		} catch (InterruptedException e) {
			System.out.println("拋出異常");
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

定義兩個線程類:

package com.feng.example;

public class MyThreadA extends Thread{

	private MyObject object;
	
	public MyThreadA(MyObject object)
	{
		this.object = object;
	}
	
	@Override
	public void run() {
		
		object.init();
	}	
}

package com.feng.example;

public class MyThreadB extends Thread{

private MyObject object;
	
	public MyThreadB(MyObject object)
	{
		this.object = object;
	}
	
	@Override
	public void run() {

		object.print();
	}
	
}

測試類以下:

package com.feng.example;

public class ThreadTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
			
		MyObject object = new MyObject();
		
		Thread a = new MyThreadA(object);
		Thread b = new MyThreadB(object);
		
		a.start();
		b.start();
			
	
	}

}

運行結果以下:

Thread-1====print begin
Thread-0====init begin
Thread-1====print end
Thread-0====init end

從運行結果能夠看出,class鎖與對象鎖不是同一個鎖,二者異步執行

調用多個synchronized方法的髒讀問題

若是在程序中連續調用幾個synchronized方法,其中存在分支判斷就會出現髒讀的問題,下面舉例說明:

定義一個只能存一個數據的對象MyObject,判斷當數據的個數<1時添加元素,其中獲取元素個數,添加元素都爲synchronized方法。

定義操做類:

package com.feng.example;

import java.util.ArrayList;
import java.util.List;

public class MyObject {

	private List<String> list = new ArrayList<String>();
	
	synchronized  public void add() {
		
		list.add("xu");

	}

	synchronized  public int size() {
		
		return list.size();

	}
}

定義線程類:

package com.feng.example;

public class MyThread extends Thread {
	
	private MyObject object;
	
	public MyThread(MyObject object)
	{
		this.object = object;
	}

	@Override
	public void run() {
		
		try {
			//保證object中的list中只有一個數據
			if(object.size() < 1)
			{
				
				Thread.sleep(2000);	
				object.add();
			}
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

測試類以下:

package com.feng.example;

public class ThreadTest {

	/**
	 * @param args
	 */
	
	public static void main(String[] args) {
			
		MyObject object = new MyObject();
		
		Thread a = new MyThread(object);
		Thread b = new MyThread(object);
		
		a.start();
		b.start();
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(object.size());
			
	
	}

}

分析:兩個線程a,b在執行時,線程a執行完size( )執行add()方法前釋放鎖,此時,線程b獲取鎖執行了size()方法,兩個線程同時進入了if(object.size()<1)的語句塊,兩個線程均可以往object對象中添加數據,因此添加了兩條數據,然而咱們定義的確是只能保存一個數據的對象。所以程序出現了問題。

程序運行結果以下:

2

修改程序將線程類的run方法中使用synchronized語句塊。

修改線程類:

package com.feng.example;

public class MyThread extends Thread {
	
	private MyObject object;
	
	public MyThread(MyObject object)
	{
		this.object = object;
	}

	@Override
	public void run() {
		
		try {
			synchronized(object)
			{
				//保證object中的list中只有一個數據
				if(object.size() < 1)
				{
					
					Thread.sleep(2000);	
					object.add();
				}
			}
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

程序運行結果:

1

鎖對象的改變

在程序的運行過程當中,鎖對象有可能會發生變化,當鎖對象發生變化時會出現什麼狀況呢?

定義操做類:

package com.feng.example;

public class MyObject {

	private String str = "hahah";
	 public void init() {

		try {

			synchronized(str)
			{
				System.out.println(Thread.currentThread().getName()
					+ "====init begin");
				str = "hehhe";    //鎖對象發生了變化
				Thread.sleep(2000);
				System.out.println(Thread.currentThread().getName()
					+ "====init end");
			}
		} catch (InterruptedException e) {
			System.out.println("拋出異常");
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}
}

定義線程類:

package com.feng.example;

public class MyThread extends Thread {
	
	private MyObject object;
	
	public MyThread(MyObject object)
	{
		this.object = object;
	}

	@Override
	public void run() {
		
		object.init();
	}
}

定義測試類:

package com.feng.example;

public class ThreadTest {

	/**
	 * @param args
	 */
	
	public static void main(String[] args) {
			
		MyObject object = new MyObject();
		
		Thread a = new MyThread(object);
		Thread b = new MyThread(object);
		
		a.start();
		b.start();
	
	}

}

分析:線程a,b執行同一個對象的object.init( )方法,二者誰先執行到synchronized(str)是不肯定的,假設線程a先到,那麼a獲取到str對象鎖,執行synchronized語句塊,接着修改了鎖對象str = "hehhe",此時線程b執行到語句塊synchronized(str),這裏str執行的是hehhe了,不在是hahah,二者不是同一個鎖,所以會異步執行

執行結果:

Thread-1====init begin
Thread-0====init begin
Thread-1====init end
Thread-0====init end

修改操做類MyObject對象的代碼:

package com.feng.example;

public class MyObject {

	private String str = "hahah";
	 public void init() {

		try {

			synchronized(str)
			{
				System.out.println(Thread.currentThread().getName()
					+ "====init begin");
				Thread.sleep(2000);
				str = "hehhe";    //鎖對象發生了變化
				System.out.println(Thread.currentThread().getName()
					+ "====init end");
			}
		} catch (InterruptedException e) {
			System.out.println("拋出異常");
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}
}

分析:線程a,b執行同一個對象的object.init( )方法,二者誰先執行到synchronized(str)是不肯定的,假設線程a先到,線程a獲取了str的鎖,而後休眠2s,在執行str="hehhe"以前,線程b執行到synchronized(str),二者搶的是同一個對象的鎖,所以會同步執行。

運行結果以下;

Thread-0====init begin
Thread-0====init end
Thread-1====init begin
Thread-1====init end

總結:當線程運行到synchronized語句塊是,鎖對象是哪個對象,就會保持哪個對象的鎖。

相關文章
相關標籤/搜索