天天看點

Java同步之synchronized

Java中可以使用關鍵字synchronized進行線程同步控制,實作關鍵資源順序通路,避免由于多線程并發執行導緻的資料不一緻性等問題。synchronized的原理是對象螢幕(鎖),隻有擷取到螢幕的線程才能繼續執行,否則線程會等待擷取螢幕。Java中每個對象或者類都有一把鎖與之相關聯,對于對象來說,監視的是這個對象的執行個體變量,對于類來說,監視的是類變量(一個類本身是類Class的對象,是以與類關聯的鎖也是對象鎖)。synchronized關鍵字使用方式有兩種:synchronized方法和synchronized塊。這兩種監視區域都和一個引入對象相關聯,當到達這個監視區域時,JVM就會鎖住這個引用對象,當離開時會釋放這個引用對象上的鎖(有異常退出時,JVM會釋放鎖)。對象鎖是JVM内部機制,隻需要編寫同步方法或者同步塊即可,操作監視區域時JVM會自動擷取鎖或者釋放鎖。

首先來看同步方法的例子:

public class SynchronizedTest1 extends Thread
{
	private synchronized void testSynchronizedMethod()
	{
		for (int i = 0; i < 10; i++)
		{
			System.out.println(Thread.currentThread().getName()
					+ " testSynchronizedMethod:" + i);

			try
			{
				Thread.sleep(100);
			}
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}
		}
	}

	@Override
	public void run()
	{
		testSynchronizedMethod();
	}

	public static void main(String[] args)
	{

                SynchronizedTest1 t = new SynchronizedTest1();
		t.start();
		t.testSynchronizedMethod();
	}
}
           

運作該程式輸出結果為:

main testSynchronizedMethod:0
main testSynchronizedMethod:1
main testSynchronizedMethod:2
main testSynchronizedMethod:3
main testSynchronizedMethod:4
main testSynchronizedMethod:5
main testSynchronizedMethod:6
main testSynchronizedMethod:7
main testSynchronizedMethod:8
main testSynchronizedMethod:9
Thread-0 testSynchronizedMethod:0
Thread-0 testSynchronizedMethod:1
Thread-0 testSynchronizedMethod:2
Thread-0 testSynchronizedMethod:3
Thread-0 testSynchronizedMethod:4
Thread-0 testSynchronizedMethod:5
Thread-0 testSynchronizedMethod:6
Thread-0 testSynchronizedMethod:7
Thread-0 testSynchronizedMethod:8
Thread-0 testSynchronizedMethod:9
           

可以看到testSynchronizedMethod方法在兩個線程之間同步執行。

如果此時将main方法修改為如下所示,則兩個線程并不能同步執行,因為此時兩個線程的同步螢幕不是同一個對象,不能起到同步的作用。

public static void main(String[] args)
	{
		Thread t = new SynchronizedTest1();
		t.start();
		
		Thread t1 = new SynchronizedTest1();
		t1.start();
	}
           

此時輸出結果如下所示:

Thread-0 testSynchronizedMethod:0
Thread-1 testSynchronizedMethod:0
Thread-0 testSynchronizedMethod:1
Thread-1 testSynchronizedMethod:1
Thread-0 testSynchronizedMethod:2
Thread-1 testSynchronizedMethod:2
Thread-0 testSynchronizedMethod:3
Thread-1 testSynchronizedMethod:3
Thread-0 testSynchronizedMethod:4
Thread-1 testSynchronizedMethod:4
Thread-0 testSynchronizedMethod:5
Thread-1 testSynchronizedMethod:5
Thread-0 testSynchronizedMethod:6
Thread-1 testSynchronizedMethod:6
Thread-0 testSynchronizedMethod:7
Thread-1 testSynchronizedMethod:7
Thread-0 testSynchronizedMethod:8
Thread-1 testSynchronizedMethod:8
Thread-0 testSynchronizedMethod:9
Thread-1 testSynchronizedMethod:9
           

若想修改後的main方法能夠在兩個線程之間同步運作,需要将testSynchronizedMethod方法聲明為靜态方法,這樣兩個線程的螢幕是同一個對象(類對象),能夠同步執行。修改後的代碼如下所示:

public class SynchronizedTest1 extends Thread
{
	private static synchronized void testSynchronizedMethod()
	{
		for (int i = 0; i < 10; i++)
		{
			System.out.println(Thread.currentThread().getName()
					+ " testSynchronizedMethod:" + i);

			try
			{
				Thread.sleep(100);
			}
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}
		}
	}

	@Override
	public void run()
	{
		testSynchronizedMethod();
	}

	public static void main(String[] args)
	{
		Thread t = new SynchronizedTest1();
		t.start();
		
		Thread t1 = new SynchronizedTest1();
		t1.start();
	}
}
           

輸出結果如下:

Thread-0 testSynchronizedMethod:0
Thread-0 testSynchronizedMethod:1
Thread-0 testSynchronizedMethod:2
Thread-0 testSynchronizedMethod:3
Thread-0 testSynchronizedMethod:4
Thread-0 testSynchronizedMethod:5
Thread-0 testSynchronizedMethod:6
Thread-0 testSynchronizedMethod:7
Thread-0 testSynchronizedMethod:8
Thread-0 testSynchronizedMethod:9
Thread-1 testSynchronizedMethod:0
Thread-1 testSynchronizedMethod:1
Thread-1 testSynchronizedMethod:2
Thread-1 testSynchronizedMethod:3
Thread-1 testSynchronizedMethod:4
Thread-1 testSynchronizedMethod:5
Thread-1 testSynchronizedMethod:6
Thread-1 testSynchronizedMethod:7
Thread-1 testSynchronizedMethod:8
Thread-1 testSynchronizedMethod:9
           

同步塊的情況與同步方法類似,隻是同步塊将同步控制的粒度縮小,這樣能夠更好的發揮多線程并行執行的效率。

使用this對象控制同一對象執行個體之間的同步:

public class SynchronizedTest2 extends Thread
{
	private void testSynchronizedBlock()
	{
		synchronized (this)
		{
			for (int i = 0; i < 10; i++)
			{
				System.out.println(Thread.currentThread().getName()
						+ " testSynchronizedBlock:" + i);

				try
				{
					Thread.sleep(100);
				}
				catch (InterruptedException e)
				{
					e.printStackTrace();
				}
			}
		}
	}

	@Override
	public void run()
	{
		testSynchronizedBlock();
	}

	public static void main(String[] args)
	{
		SynchronizedTest2 t = new SynchronizedTest2();
		t.start();

		t.testSynchronizedBlock();
	}
}
           

輸出結果:

main testSynchronizedBlock:0
main testSynchronizedBlock:1
main testSynchronizedBlock:2
main testSynchronizedBlock:3
main testSynchronizedBlock:4
main testSynchronizedBlock:5
main testSynchronizedBlock:6
main testSynchronizedBlock:7
main testSynchronizedBlock:8
main testSynchronizedBlock:9
Thread-0 testSynchronizedBlock:0
Thread-0 testSynchronizedBlock:1
Thread-0 testSynchronizedBlock:2
Thread-0 testSynchronizedBlock:3
Thread-0 testSynchronizedBlock:4
Thread-0 testSynchronizedBlock:5
Thread-0 testSynchronizedBlock:6
Thread-0 testSynchronizedBlock:7
Thread-0 testSynchronizedBlock:8
Thread-0 testSynchronizedBlock:9
           

使用class對象控制不同執行個體之間的同步:

public class SynchronizedTest2 extends Thread
{
	private void testSynchronizedBlock()
	{
		synchronized (SynchronizedTest2.class)
		{
			for (int i = 0; i < 10; i++)
			{
				System.out.println(Thread.currentThread().getName()
						+ " testSynchronizedBlock:" + i);

				try
				{
					Thread.sleep(100);
				}
				catch (InterruptedException e)
				{
					e.printStackTrace();
				}
			}
		}
	}

	@Override
	public void run()
	{
		testSynchronizedBlock();
	}

	public static void main(String[] args)
	{
		Thread t = new SynchronizedTest2();
		t.start();

		Thread t2 = new SynchronizedTest2();
		t2.start();
	}
}
           

輸出結果:

Thread-0 testSynchronizedBlock:0
Thread-0 testSynchronizedBlock:1
Thread-0 testSynchronizedBlock:2
Thread-0 testSynchronizedBlock:3
Thread-0 testSynchronizedBlock:4
Thread-0 testSynchronizedBlock:5
Thread-0 testSynchronizedBlock:6
Thread-0 testSynchronizedBlock:7
Thread-0 testSynchronizedBlock:8
Thread-0 testSynchronizedBlock:9
Thread-1 testSynchronizedBlock:0
Thread-1 testSynchronizedBlock:1
Thread-1 testSynchronizedBlock:2
Thread-1 testSynchronizedBlock:3
Thread-1 testSynchronizedBlock:4
Thread-1 testSynchronizedBlock:5
Thread-1 testSynchronizedBlock:6
Thread-1 testSynchronizedBlock:7
Thread-1 testSynchronizedBlock:8
Thread-1 testSynchronizedBlock:9
           

使用synchronized關鍵字進行同步控制時,一定要把握好對象螢幕,隻有獲得螢幕的程序可以運作,其它都需要等待擷取螢幕。任何一個非null的對象都可以作為對象螢幕,當synchronized作用在方法上時,鎖住的便是對象執行個體(this);當作用在靜态方法時鎖住的便是對象對應的Class執行個體。

總結:

synchronized是通過軟體(JVM)實作的,簡單易用,即使在JDK5之後有了Lock,仍然被廣泛地使用。

synchronized實際上是非公平的,新來的線程有可能立即獲得螢幕,而在等待區中等候已久的線程可能再次等待,不過這種搶占的方式可以預防饑餓。

synchronized隻有鎖隻與一個條件(是否擷取鎖)相關聯,不靈活,後來Condition與Lock的結合解決了這個問題。

多線程競争一個鎖時,其餘未得到鎖的線程隻能不停的嘗試獲得鎖,而不能中斷。高并發的情況下會導緻性能下降。ReentrantLock的lockInterruptibly()方法可以優先考慮響應中斷。 一個線程等待時間過長,它可以中斷自己,然後ReentrantLock響應這個中斷,不再讓這個線程繼續等待。有了這個機制,使用ReentrantLock時就不會像synchronized那樣産生死鎖了。

參考資料:

JAVA并發程式設計學習筆記之synchronized

深入JVM鎖機制1-synchronized

java 對象鎖