1. 程式人生 > >Java 多線程詳解(三)------線程的同步

Java 多線程詳解(三)------線程的同步

alt 來看 監聽 介紹 創建進程 java 多線程 system ima 關鍵字

Java 多線程詳解(一)------概念的引入:http://www.cnblogs.com/ysocean/p/6882988.html

Java 多線程詳解(二)------如何創建進程和線程:http://www.cnblogs.com/ysocean/p/6883491.html

介紹完如何創建進程以及線程了,那麽我們接著來看一個實例:

  利用多線程模擬 3 個窗口賣票

第一種方法:繼承 Thread 類

 創建窗口類 TicketSell 

package com.ys.thread;

public class TicketSell extends Thread{
	//定義一共有 50 張票,註意聲明為 static,表示幾個窗口共享
	private static int num = 50;
	
	//調用父類構造方法,給線程命名
	public TicketSell(String string) {
		super(string);
	}
	@Override
	public void run() {
		//票分 50 次賣完
		for(int i = 0 ; i < 50 ;i ++){
			if(num > 0){
				try {
					sleep(10);//模擬賣票需要一定的時間
				} catch (InterruptedException e) {
					// 由於父類的 run()方法沒有拋出任何異常,根據繼承的原則,子類拋出的異常不能大於父類, 故我們這裏也不能拋出異常
					e.printStackTrace();
				}
				System.out.println(this.currentThread().getName()+"賣出一張票,剩余"+(--num)+"張");
			}
		}
	}
	

}

  創建主線程測試:

package com.ys.thread;

public class TestTicket {

	public static void main(String[] args) {
		//創建 3 個窗口
		TicketSell t1 = new TicketSell("A窗口");
		TicketSell t2 = new TicketSell("B窗口");
		TicketSell t3 = new TicketSell("C窗口");
		
		//啟動 3 個窗口進行買票
		t1.start();
		t2.start();
		t3.start();
	}
}

  結果:這裏我們省略了一些,根據電腦配置,結果會隨機出現不同

B窗口賣出一張票,剩余48張
A窗口賣出一張票,剩余47張
C窗口賣出一張票,剩余49張
C窗口賣出一張票,剩余46張
B窗口賣出一張票,剩余44張
A窗口賣出一張票,剩余45張
A窗口賣出一張票,剩余43張
...
C窗口賣出一張票,剩余5張
A窗口賣出一張票,剩余4張
B窗口賣出一張票,剩余3張
A窗口賣出一張票,剩余2張
C窗口賣出一張票,剩余3張
B窗口賣出一張票,剩余1張
C窗口賣出一張票,剩余0張
A窗口賣出一張票,剩余-1張

  

第二種方法:實現 Runnable 接口

  創建窗口類 TicketSellRunnable

package com.ys.thread;

public class TicketSellRunnable implements Runnable{

	//定義一共有 50 張票,繼承機制開啟線程,資源是共享的,所以不用加 static
	private int num = 50;
	
	@Override
	public void run() {
		//票分 50 次賣完
		for(int i = 0 ; i < 50 ;i ++){
			if(num > 0){
				try {
					//模擬賣一次票所需時間
					Thread.sleep(10);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+"賣出一張票,剩余"+(--num)+"張");
			}
		}
	}

}

  創建主線程測試:

package com.ys.thread;

public class TicketSellRunnableTest {
	public static void main(String[] args) {
		TicketSellRunnable t = new TicketSellRunnable();
		
		Thread t1 = new Thread(t,"A窗口");
		Thread t2 = new Thread(t,"B窗口");
		Thread t3 = new Thread(t,"C窗口");
		
		t1.start();
		t2.start();
		t3.start();
	}

}

  結果:同理為了篇幅我們也省略了中間的一些結果

B窗口賣出一張票,剩余49張
C窗口賣出一張票,剩余48張
A窗口賣出一張票,剩余49張
B窗口賣出一張票,剩余47張
A窗口賣出一張票,剩余45張
......
A窗口賣出一張票,剩余4張
C窗口賣出一張票,剩余5張
A窗口賣出一張票,剩余3張
B窗口賣出一張票,剩余2張
C窗口賣出一張票,剩余1張
B窗口賣出一張票,剩余0張
A窗口賣出一張票,剩余-2張
C窗口賣出一張票,剩余-1張

  

結果分析:這裏出現了票數為 負數的情況,這在現實生活中肯定是不存在的,那麽為什麽會出現這樣的情況呢?

  技術分享

解決辦法分析:即我們不能同時讓超過兩個以上的線程進入到 if(num>0)的代碼塊中,不然就會出現上述的錯誤。我們可以通過以下三個辦法來解決:

1、使用 同步代碼塊

2、使用 同步方法

3、使用 鎖機制

①、使用同步代碼塊

語法:
synchronized (同步鎖) {
    //需要同步操作的代碼          
}

同步鎖:為了保證每個線程都能正常的執行原子操作,Java 線程引進了同步機制;同步鎖也叫同步監聽對象、同步監聽器、互斥鎖;
Java程序運行使用的任何對象都可以作為同步監聽對象,但是一般我們把當前並發訪問的共同資源作為同步監聽對象

註意:同步鎖一定要保證是確定的,不能相對於線程是變化的對象;任何時候,最多允許一個線程拿到同步鎖,誰拿到鎖誰進入代碼塊,而其他的線程只能在外面等著

  實例:

public void run() {
		//票分 50 次賣完
		for(int i = 0 ; i < 50 ;i ++){
			//這裏我們使用當前對象的字節碼對象作為同步鎖
			synchronized (this.getClass()) {
				if(num > 0){
					try {
						//模擬賣一次票所需時間
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()+"賣出一張票,剩余"+(--num)+"張");
				}
			}
			
		}
	}

  

2、使用 同步方法

語法:即用 synchronized 關鍵字修飾方法

@Override
	public void run() {
		//票分 50 次賣完
		for(int i = 0 ; i < 50 ;i ++){
			sell();
			
		}
	}
	private synchronized void sell(){
		if(num > 0){
			try {
				//模擬賣一次票所需時間
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"賣出一張票,剩余"+(--num)+"張");
		}
	}

  註意:不能直接用 synchronized 來修飾 run() 方法,因為如果這樣做,那麽就會總是第一個線程進入其中,而這個線程執行完所有操作,即賣完所有票了才會出來。

3、使用 鎖機制

  

public interface Lock

  主要方法:

技術分享

  常用實現類:

public class ReentrantLock
extends Object
implements Lock, Serializable
//一個可重入互斥Lock具有與使用synchronized方法和語句訪問的隱式監視鎖相同的基本行為和語義,但具有擴展功能。

  例子:

package com.ys.thread;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TicketSellRunnable implements Runnable{

	//定義一共有 50 張票,繼承機制開啟線程,資源是共享的,所以不用加 static
	private int num = 50;
	//創建一個鎖對象
	Lock l = new ReentrantLock();
	
	@Override
	public void run() {
		//票分 50 次賣完
		for(int i = 0 ; i < 50 ;i ++){
			//獲取鎖
			l.lock();
			try {
				if(num > 0){
				//模擬賣一次票所需時間
				Thread.sleep(10);
				System.out.println(Thread.currentThread().getName()+"賣出一張票,剩余"+(--num)+"張");
				}
			} catch (Exception e) {
				e.printStackTrace();
			}finally{
				//釋放鎖
				l.unlock();
			}
			
			
		}
	}
	private void sell(){
		
	}

}

  

  

Java 多線程詳解(三)------線程的同步