天天看點

線程中的資源共享和同步

<div class="iteye-blog-content-contain" style="font-size: 14px"></div>

 這個總結将對線程中的資源共享和線程同步問題進行簡單的分析。

線程:是程序中的一個單一的連續控制流程。

一個程序可以含有多個線程,那下面就談談多線程。

java中多線程的實作有兩種手段:1 繼承Thread類  2實作Runnable接口

這裡以買火車票為例。

class Ticket extends Thread {
	public int tickets = 10;
	public String name;
	public Ticket(String name){
		this.name = name;
	}
	public void run(){
		for(int i=0;i<10;i++){
			System.out.println(this.name+"買票"+this.tickets--);
		}
	}
}

public class Test1 {
	public static void main(String []arg){
		Ticket t1 = new Ticket("線程1");
		Ticket t2 = new Ticket("線程2");
		Ticket t3 = new Ticket("線程3");
		
		t1.run();
		t2.run();
		t3.run();
	}
}
           

 看到運作結果了吧,它們都是順序執行的,這說明我們的調用方法不對,當我們把run方法改為start方法後才出現我們想要的結果.那我們來看看run方法和start方法的差別。

run()方法:在本線程内調用該Runnable對象的run()方法,可以重複多次調用;

start()方法:啟動一個線程,調用該Runnable對象的run()方法,不能多次啟動一個線程;

run()方法隻是一個普通的方法,如果直接調用run方法的話那程式中還是隻有一個主線程,并沒有達到多線程的目的,主程式中還是會順序執行,執行完一個run方法後才會繼續執行下一個方法。而start()方法是負責啟動一個線程,不用等待執行完其中的run方法後再執行下面的方法,而是直接執行下面的方法。簡單的說就是你在run方法中寫入要執行的代碼,然後你隻要調用start方法就可以了,至于什麼時候執行run方法,那就不是你負責的了。start()方法的功能其實是向cpu申請另一個線程空間來執行run()方法的,它和目前的線程空間是相對獨立的。就是說如果你直接調用run方法的話它仍然會執行,但是在目前的線程空間,是以它會按順序執行,而調用start方法後,run方法就會和目前的代碼并行的執行,進而達到多線程。

另一個我們要考慮的問題就是資源共享問題。這個問題也可以用來差別Thread和Runnable,而多線程在很多情況下都是處理資源共享的問題。下面我們仍以買火車票為例來看看資源共享的問題。

class Ticket extends Thread {
	public int tickets = 10;
	public String name;

	public void run() {
		while (this.tickets > 0) {
			System.out.println(this.name + " 賣票:" + this.tickets--);
		}
	}

	public Ticket(String name) {
		this.name = name;
	}
}

public class Test1 {
	public static void main(String[] arg) {
		Ticket t1 = new Ticket("線程1");
		Ticket t2 = new Ticket("線程2");
		Ticket t3 = new Ticket("線程3");

		t1.start();
		t2.start();
		t3.start();

	}
}
           

 對于這個的運作結果我們之前已經試過了,從結果上看,它雖然實作了多線程,但它好像并不符合實際,我們定義一共有10張票,分三個地方來買,那就是說三個地方共享這10張票,可結果出現了30個數,顯然是不對的,那如果把代碼改一下:

class Ticket implements Runnable {
	public int tickets = 10;
	public String name;

	public void run() {
		while (this.tickets > 0) {
			System.out.println("賣票:" + this.tickets--);
		}
	}
}

public class Test1 {
	public static void main(String args[]) {
		// 準備四個售票點
		Demo d = new Demo();

		Thread t1 = new Thread(d);
		Thread t2 = new Thread(d);
		Thread t3 = new Thread(d);
		
		t1.start();
		t2.start();
		t3.start();
	}
}
           

 再運作,結果:

賣票:10

賣票:9

賣票:8

賣票:7

賣票:6

賣票:5

賣票:4

賣票:3

賣票:2

賣票:1

資源共享的問題也算是解決了。

還有一個問題就是線程同步的問題,這也是值得我們注意的問題,把上面的代碼再進行修改:

class Demo implements Runnable {
	private int ticket = 10;

	public void run() {
		while (this.ticket > 0) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("賣票:" + this.ticket--);
		}
	}
};

public class Test1 {
	public static void main(String args[]) {
		// 準備四個售票點
		Demo d = new Demo();

		Thread t1 = new Thread(d);
		Thread t2 = new Thread(d);
		Thread t3 = new Thread(d);
		
		t1.start();
		t2.start();
		t3.start();
	}
};
           

 運作結果:

賣票:10

賣票:9

賣票:8

賣票:7

賣票:6

賣票:5

賣票:4

賣票:3

賣票:2

賣票:1

賣票:0

賣票:-1

出現了兩個不該出現的結果,那我們來分析一下出現這個結果的原因。

我們分三個售票點共同買10張票,當票數隻剩一張的時候,假如被A拿走了,然後進入sleep中,而在這個時候,B也将最後一張票拿走了,而此時的tickets還等于1,當A把tickets放回的時候,tickets--變為了0,當B再放回的時候則出現了-1的情況。這就是不用步造成的。

那我們就要解決這個不同步的問題

下面介紹一下synchronized關鍵字:

    當它用來修飾一個方法或者一個代碼塊的時候,能夠保證在同一時刻最多隻有一個線程執行該段代碼。

    當兩個并行的線程同時通路object的一個synchronized修飾的同步塊是,同一時間隻能有一個線程可以對其通路,隻有當他通路完之後,另一個才能對其通路。

建立一個同步方法:

class Demo implements Runnable {
	private int ticket = 10;

	public synchronized void method(){
		while (this.ticket > 0) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+" 賣票:" + this.ticket--);
		}
	}
	public void run() {
		method();
	}
};

public class Test1 {
	public static void main(String args[]) {
		// 準備四個售票點
		Demo d = new Demo();

		Thread t1 = new Thread(d,"a");
		Thread t2 = new Thread(d,"b");
		Thread t3 = new Thread(d,"c");
		
		t1.start();
		t2.start();
		t3.start();
	}
};
           

 運作結果:

b 賣票:10

b 賣票:9

b 賣票:8

b 賣票:7

b 賣票:6

b 賣票:5

b 賣票:4

b 賣票:3

b 賣票:2

b 賣票:1

這個結果雖然對票數同步了,但似乎又出現了其他的問題,一個售票點就把所有的票賣完了,回頭看看我們的代碼是哪裡出了問題。

做如下改正:

class Demo implements Runnable {
	private int ticket = 10;

	public synchronized void method() {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + " 賣票:"
					+ this.ticket--);
	}

	public void run() {
		while (this.ticket > 0) {
			method();
		}
	}
};
           

 運作結果:

a 賣票:10

a 賣票:9

a 賣票:8

a 賣票:7

a 賣票:6

a 賣票:5

c 賣票:4

c 賣票:3

b 賣票:2

b 賣票:1

c 賣票:0

a 賣票:-1

又出問題了(無奈),看看又是什麼情況,我們在同步方法中加一個if(this.tickets>0)保護,在運作,看看結果吧... ...終于沒問題了。

繼續閱讀