java中synchronized關鍵字實現線程同步互斥(一)

java多線程程序現在很常見,和數據庫操作系統一樣,多個線程會共享一個堆內存,如果不加以控制,不進行線程之間的同步,會造成數據混亂等。

先看看下面這個程序:

public class TestSynchronized implements Runnable {
	Timer timer = new Timer();

	public static void main(String args[]) {
		TestSynchronized test = new TestSynchronized();
		Thread t1 = new Thread(test);
		Thread t2 = new Thread(test);
		t1.setName("t1");
		t2.setName("t2");
		t1.start();
		t2.start();
	}

	public void run() {
		timer.add(Thread.currentThread().getName());
	}
}

class Timer {
	private static int num = 0;

	public void add(String name) {
		// synchronized(this){ //沒有同步,將導致num的數目不正常
		num++;
		try {
			Thread.sleep(1);
		} catch (Exception e) {
		}
		System.out.println(name + ", 你是第" + num + "個使用timer的線程");
		// }
	}
}
輸出的結果:

t1, 你是第2個使用timer的線程
t2, 你是第2個使用timer的線程

num兩次都是2。這裏很明顯是兩個線程在訪問同一個Timer對象的num變量時交替進行,所以最後打印的時候都是2;

如何實現不同線程之間互斥訪問這個num呢,使用synchronized同步代碼塊即可解決,用synchronized(this){}將需要同步代碼塊圈起來。

輸出正常結果:

t2, 你是第1個使用timer的線程
t1, 你是第2個使用timer的線程


synchronized的另一個用法是同步方法,就是在方法之前加上修飾符synchronized。那麼不同線程在訪問同一個對象的synchronized方法時就會互斥訪問,從而達到同步

下面是一個經典的生產消費者模型代碼:

public class TestSynchronized {
	public static void main(String[] args) {
		SyncStack ss = new SyncStack();
		Producer p = new Producer(ss);
		Consumer c = new Consumer(ss);
		Thread tp = new Thread(p);
		Thread tc = new Thread(c);
		tp.start();
		tc.start();
	}
}

class Bread {
	int id;

	Bread(int id) {
		this.id = id;
	}

	public String toString() {
		return "Bread: " + id;
	}
}

class SyncStack {
	int index = 0;
	Bread[] breads = new Bread[6];

	public synchronized void push(Bread b) {
		if (index == breads.length) {// 注意只能用while不能用if;因爲wait會異常
			try {
				this.wait();// 訪問當前對象的線程wait()
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		this.notify();
		breads[index] = b;
		index++;
		System.out.println("生產了:" + b);
	}

	public synchronized Bread pop() {
		if (index == 0) {
			try {
				this.wait();// wait的時候鎖已經不歸該線程所有,但是sleep還有鎖
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		this.notify();// 叫醒一個正在該對象上wait的一個線程
		index--;
		System.out.println("消費了:" + breads[index]);
		return breads[index];
	}
}

class Producer implements Runnable {
	SyncStack ss = null;

	Producer(SyncStack ss) {
		this.ss = ss;
	}

	public void run() {
		for (int i = 0; i < 20; i++) {
			Bread b = new Bread(i);
			ss.push(b);
			// System.out.println("生產了:"+b);
			try {
				Thread.sleep((int) Math.random() * 1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

class Consumer implements Runnable {
	SyncStack ss = null;

	Consumer(SyncStack ss) {
		this.ss = ss;
	}

	public void run() {
		for (int i = 0; i < 20; i++) {
			Bread b = null;
			b = ss.pop();
			// System.out.println("消費了:"+breads[index]);//放到這裏用if的話會出問題,先打出消費0,再打出生產0
			try {
				Thread.sleep((int) Math.random() * 1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

同步棧對象SyncStack的push和pop方法就是同步方法。生產者線程和消費者線程將會互斥訪問pop和push方法。



下面說一下這種同步機制的實現。java爲每個類對象分配一個獨一無二的對象鎖,每個線程要訪問這個對象的成員就需要獲得這個鎖(當然這裏是指需要同步的線程)。因爲一個對象只有一個對象鎖,所以一個線程在拿到鎖之後,另一個訪問相同對象的線程必須等待前者執行完成後釋放掉對象鎖,以此實現互斥。也就是說synchronized是針對對象的,不是針對類的。看看上面的圖形。ObjectA和ObjectB是同一個類的不同實例,所以ThreadAn和ThreadBn沒有關係。只有ThreadA1234同步,ThreadB1234亦是如此。並且對於非synchronized的方法和代碼塊,並沒有影響,沒有互斥

那有沒有實現類級別上的同步呢?有的。在 Java 中,不光是類實例,每一個類也對應一把鎖,這樣我們也可將類的靜態成員函數聲明爲 synchronized ,以控制其對類的靜態成員變量的訪問。  使用同步代碼塊是synchronized(classname.class){}

最後幾點總結來自http://www.cnblogs.com/devinzhang/archive/2011/12/14/2287675.html:

1)是某個對象實例內,synchronized aMethod(){}可以防止多個線程同時訪問這個對象的synchronized方法(如果一個對象有多個synchronized方法,只要一個線程訪問了其中的一個synchronized方法,其它線程不能同時訪問這個對象中任何一個synchronized方法)。這時,不同的對象實例的synchronized方法是不相干擾的。也就是說,其它線程照樣可以同時訪問相同類的另一個對象實例中的synchronized方法; 
2)是某個類的範圍,synchronized static aStaticMethod{}防止多個線程同時訪問這個類中的synchronized static 方法。它可以對類的所有對象實例起作用。
2、除了方法前用synchronized關鍵字,synchronized關鍵字還可以用於方法中的某個區塊中,表示只對這個區塊的資源實行互斥訪問。用法是: synchronized(this){/*區塊*/},它的作用域是當前對象;
3、synchronized關鍵字是不能繼承的,也就是說,基類的方法synchronized f(){} 在繼承類中並不自動是synchronized f(){},而是變成了f(){}。繼承類需要你顯式的指定它的某個方法爲synchronized方法;

對synchronized(this)的一些理解 

一、當兩個併發線程訪問同一個對象object中的這個synchronized(this)同步代碼塊時,一個時間內只能有一個線程得到執行。另一個線程必須等待當前線程執      行完這個代碼塊以後才能執行該代碼塊。  

二、然而,當一個線程訪問object的一個synchronized(this)同步代碼塊時,另一個線程仍然可以訪問該object中的非synchronized(this)同步代碼塊。  

三、尤其關鍵的是,當一個線程訪問object的一個synchronized(this)同步代碼塊時,其他線程對object中所有其它synchronized(this)同步代碼塊的訪問將被      阻塞。  

四、第三個例子同樣適用其它同步代碼塊。也就是說,當一個線程訪問object的一個synchronized(this)同步代碼塊時,它就獲得了這個object的對象鎖。結           果,其它線程對該object對象所有同步代碼部分的訪問都被暫時阻塞。  

五、以上規則對其它對象鎖同樣適用


發佈了39 篇原創文章 · 獲贊 10 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章