1. 程式人生 > >(java)多執行緒(二)

(java)多執行緒(二)

【同步程式碼塊】-- 解決執行緒安全問題

/*
 * 同步程式碼塊解決執行緒安全問題
 *   同步程式碼塊公式:
 *    synchronized(任意物件){
 *        執行緒要操作的共享資料
 *    }
 *    任意物件又稱:同步鎖  、物件監視器 obj
 *       作用:保證同步安全性,沒有鎖的執行緒不能執行同步程式碼塊,只能等
 *    原理:執行緒遇到同步程式碼塊時,判斷程式碼塊的同步鎖還有沒有
 *           1. 若有鎖,獲取鎖,進入同步程式碼塊執行程式     執行完畢後 出了程式碼塊   執行緒再將鎖還回去
 *           2. 若沒有鎖,執行緒只能等待,不能進入同步程式碼塊
 */
/*
 *   同步程式碼塊解決執行緒安全問題
 *   同步程式碼塊公式:
 *    synchronized(任意物件){
 *        執行緒要操作的共享資料
 *    }
 */
public class Tickets implements Runnable{
	// 定義出售票的數目
	private int tickets = 100;
	private Object obj = new Object();
	
	public void run(){
		while(true){
			// 為了保證共享資料的安全,加入同步程式碼塊
			synchronized (obj) {
				// 對票數進行判斷,大於0可以出售,進行變數--操作
				if(tickets >0){
					
					try{
						Thread.sleep(100); // 此處執行緒休眠後,會出現執行緒安全問題。售票數會出現負數
					}catch(Exception ex){}
					
					System.out.println(Thread.currentThread().getName()+"出售第"+tickets--);
				}
			}
			
		}

	}
	
}
/*
 *  多執行緒售票案例:多執行緒併發訪問同一資料資源 
 *     本場電影只有100張票,使用多執行緒模擬視窗同時賣票 
 *     即三個執行緒,出售一個票資源
 */
public class ThreadDemo1 {
	public static void main(String[] args) {
		
		// 建立Runnable介面實現類物件
		Tickets t = new Tickets();
		// 建立3個Thread類物件,傳遞Runnable介面實現類
		Thread t0 = new Thread(t);
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		
		t0.start();t1.start();t2.start();
		
	}
}

【同步方法】

/*
 *   採用同步方法形式,解決執行緒的安全問題    好處:程式碼簡潔
 *     步驟:
 *       1. 將執行緒共享資料和同步,抽取到一個方法中去
 *       2. 在方法宣告上加上同步關鍵字   synchronized()
 *     問題:
 *       同步方法中的物件鎖,是本類引用物件this
 *       如果方法是靜態的,物件鎖是   本類.class (Tickets2.class)
 */
public class Tickets2 implements Runnable{
	// 定義出售票的數目
	private int tickets = 100;
	
	public void run(){
		while(true){
			payTicket();
		}
	
	}
	
	public synchronized void payTicket(){
		// 對票數進行判斷,大於0可以出售,進行變數--操作
		if(tickets >0){					
			try{
				Thread.sleep(100);          // 此處執行緒休眠後,會出現執行緒安全問題。售票數會出現負數
			}catch(Exception ex){}		
				 System.out.println(Thread.currentThread().getName()+"出售第"+tickets--);
		} 				
	}
		
}

【使用Lock介面】

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

/*
 *  使用Lock介面,解決執行緒安全問題
 *    Lock介面方法:
 *        1. lock()獲取鎖
 *        2. unlock()釋放鎖
 *    介面實現類ReentrantLock();
 */
public class TicketsLock1 implements Runnable {
	
	private int tickets = 100;
	// 建立Lock介面的實現類物件 ,宣告在成員變數位置
	private Lock l = new ReentrantLock();
	@Override
	public void run() {
		while(true){
			// 呼叫Lock介面方法lock()獲取鎖
			l.lock();
			if(tickets >0){
				try{
					Thread.sleep(10);
					System.out.println(Thread.currentThread().getName()+"出售第"+tickets--);
				}catch(Exception e){
					
				}finally{
					// 呼叫方法unlock() 釋放鎖
					l.unlock();
				}
				
			}
			
		}
	}
}

【死鎖】

// 類LockA 物件充當鎖
public class LockA {
	// 私有構造方法,類外不能new
	private  LockA(){}
	// 一個靜態成員變數  是自己的物件  外界可以通過類名呼叫靜態變數,拿到這個物件
	public static final LockA locka = new LockA();
	
}
//類LockB 物件充當鎖
public class LockB {
	// 私有構造方法,類外不能new
	private  LockB(){}
	// 一個靜態成員變數  是自己的物件  外界可以通過類名呼叫靜態變數,拿到這個物件
	public static final LockB lockb = new LockB();
		
}
//  Runnable 介面實現類
//    類LockA、LockB  建立物件鎖
public class DeadLock1 implements Runnable {
	
	private int i = 0;
	public void run(){
		while(true){
			if(i % 2 == 0){
				// 先進A同步,再進B同步
				synchronized (LockA.locka) {
					System.out.println("if...Locka");
					synchronized (LockB.lockb) {
						System.out.println("if...Lockb");
					}
				}
			}else{
				// 先進B同步,再進A同步
				synchronized (LockB.lockb) {
					System.out.println("else...Lockb");
					synchronized (LockA.locka) {
						System.out.println("else...Locka");
					}
					
				}
			}
			i++;
		}
	}

}
/*
 *  執行緒的死鎖程式碼實現:
 *     LockA、LockB類,構造方法私有了不能new,需要通過類名呼叫靜態方法建立物件
 */
public class ThreadDemo1 {
	public static void main(String[] args) {
		//
		DeadLock1 dead = new DeadLock1();
		Thread t0 = new Thread(dead);
		Thread t1 = new Thread(dead);
		
		t0.start(); 
		t1.start();
		
		/* 
		  if...Locka
          if...Lockb
          else...Lockb
          if...Locka
                              執行後,程式進入死鎖
		 */
	}

}

【等待與喚醒機制】

           執行緒之間的通訊:多個執行緒在處理同一個資源,處理的任務卻不相同。而通過一定的手段使各個執行緒能夠有效的利用資源,這種手段即 ---- 等待喚醒機制。

          wait():等待。將正執行的執行緒釋放其執行資格和執行權,並存儲到執行緒池中。

          notify():喚醒。喚醒執行緒池中被wait()的執行緒。一次喚醒一個,而且是任一的。

          notifyAll():喚醒全部。可以將執行緒池中的所有wait()執行緒都喚醒。

/*
 *   輸入執行緒:對Resource資源物件中的成員變數進行賦值
 *        張三   男     wang  w    進行輪流賦值      
 */
public class InpputDemo1 implements Runnable {
	private Resource r ;
	public InpputDemo1(Resource r){
		this.r = r;
	}
	public void run(){
		int i =0;
		while(true){
			synchronized (r) {
				// 若flag為true,等待
				if(r.flag == true){
					try{r.wait();}catch(Exception e){}
				}
				if(i%2 == 0){
					r.name = "張三";
					r.sex = "男";	
				}else{
					r.name = "wang";
					r.sex = "w";
				}
				// 喚醒輸出執行緒,標記改為true
				r.flag = true;
				r.notify();	
			}
			i++;
		}
	}

}
/*
 *  輸出執行緒:對Resource資源物件中的成員變數name  sex 進行輸出
 */
public class OutputDemo1 implements Runnable{
	private Resource r ;
	public OutputDemo1(Resource r){
		this.r = r;
	}
	public void run(){
		while(true){
			synchronized (r) {
				// 若標記flag 為false,等待
				if(!r.flag){
					try{r.wait();}catch(Exception e){}
				}
				System.out.println(r.name+"  "+r.sex);
				// 將flag改為false,喚醒輸入執行緒
				r.flag = false;
				r.notify();
			}
		}
	}
}
/*
 *  定義資源類Resource 
 *     成員變數name sex
 */
public class Resource {
	public String name;
	public String sex;
	// 標記:flag為true 說明:賦值完成 ;flag為false 說明:獲取值完成 
	//    執行緒輸入:需不需賦值看flag狀態。若flag為true,等待;  若flag為false,進行賦值,然後將flag改為true
	//    執行緒輸出:需不需要輸出看flag狀態。若flag為true,進行輸出,然後將flag改為false; 若flag為false,等待
	public boolean flag = false;
	
}
/*
 *   使用等待與喚醒完成以下任務:
 *      輸入執行緒向Resource中輸入name,sex 
 *      輸出執行緒從Resource中輸出name,sex
 *      
 *      1. 若InpputDemo1發現Resource中沒有資料時,開始輸入, 輸入完成喚醒notify() OutputDemo1來輸出;若發現有資料就wait()
 *      2. 若OutputDemo1發現Resource中沒有資料時,就wait();當發現有資料時,就輸出,輸出完成後喚醒notify() InpputDemo1來輸入資料
 */
public class ThreadTestDemo1 {
	public static void main(String[] args) {
		
		Resource r = new Resource();
		//建立 Runnable介面實現類物件
		InpputDemo1 in = new InpputDemo1(r);
		OutputDemo1 out = new OutputDemo1(r);
		
		Thread tin = new Thread(in);
		Thread tout = new Thread(out);
		tin.start();
		tout.start();
		/*
		         張三  男
           wang  w
                                張三  男
           wang  w
                               張三  男
           wang  w
                               張三  男
           wang  w
		  */
	}

}