Java之執行緒安全問題的3種處理方式(通過執行緒同步)

鄭清發表於2018-08-28

出現執行緒安全問題我們如何處理??  ==》同步原理
    1.同步方法synchronized 修飾的方法      ex:public synchronized void test(){}
              弊端:方法中的所有程式碼,都只允許一個執行緒訪問。
(有一種情況:一個方法中,有一個部分程式碼不會涉及到執行緒安全問題,可以允許多個執行緒同時訪問 == 》即下面的2.同步程式碼塊)
    2.同步程式碼塊synchronized(被加鎖的物件){ 程式碼 }
    3.鎖機制Lock
        ①建立ReentrantLock物件
        ②呼叫lock方法:加鎖
                {程式碼....}
        ③呼叫unlock方法:解鎖

         注意:可把解鎖的unlock方法的呼叫放在finally{}程式碼塊中,保證一定能解鎖

提醒:在同步的時候,其他程式碼都可以多個執行緒同時執行!只是被同步的程式碼不能同時執行!


ex1:  同步方法  使用synchronized 修飾方法  ==》 解決執行緒安全問題

/**
 * 執行緒安全問題
 * @author 鄭清
 */
public class Demo {
	public static void main(String[] args) {
		A a = new A();
		MyThread t1 = new MyThread(a);
		MyThread t2 = new MyThread(a);
		t1.start();
		t2.start();
	}
}
class A{
	private int tickets = 20;//記錄車票的數量
	/*
	 * 在方法定義時,新增一個synchronized修飾符
	 * 效果: 
 	 * 使用synchronized修飾的方法就是同步方法
	 * 	特點: 1.效率會變低
	 * 		  2.沒有執行緒安全問題了
	 *  原理:在方法新增synchronized之後,就相等於給當前物件(當前場景是a)加了一把鎖.
	 *  鎖的作用:當一個執行緒進入該方法時,先看鎖還在不在,鎖在:把鎖取走
	 *  在第一個執行緒還沒有結束時,第二個執行緒訪問該方法,先看鎖還在不在?       鎖不在==》等待鎖回來
	 *  第一個執行緒執行結束,把鎖還回去,第二個執行緒發現鎖回來了,把鎖取走,開始執行方法
	 */
	public synchronized void getTicket(){
		if(tickets>0){
			try {
				Thread.sleep(50);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			/*
			 * 列印出來的結果有可能有重複的車票數量
			 * 甚至有負數,就是發生了執行緒安全問題
			 * 原因是:兩個執行緒同時進入了此物件的getBean方法,讀取的車票數量是另一個執行緒修改之前的值
			 */
			System.out.println("車票還剩: "+ (--tickets) + "張 !");
		}
	}
}
class MyThread extends Thread{
	private A a;
	public MyThread(A a) {
		this.a = a;
	}
	public void run() {
		while(true){
			a.getTicket();
		}	
	}
}

執行結果圖:(如果沒有使用synchronized 修飾方法 getTicket( )   ==》 可能就會出現下圖2執行緒安全問題)

                       


ex2 :  同步程式碼塊  synchronized(被加鎖的物件){ 程式碼 }  ==》 解決執行緒安全問題

/**
 * 同步程式碼塊 :synchronized(被加鎖的物件){ 程式碼 }
 * @author 鄭清
 */
public class Demo2 {
	public static void main(String[] args) {
		WC wc = new WC();
		
		Person person1 = new Person(wc);
		Thread t1 = new Thread(person1);
		t1.setName("張三");
		
		Person person2 = new Person(wc);
		Thread t2 = new Thread(person2);
		t2.setName("李四");
		
		t1.start();
		t2.start();
	}
}
//上廁所
class WC{
	//定義一個方法實現上廁所
	public void test(){
		try{
			System.out.println(Thread.currentThread().getName()+" : 拿紙...");
			Thread.sleep(1000);
			/*
			 * 把上廁所的程式碼 放在 同步程式碼塊中
			 * 該程式碼塊中的程式碼在一個時間點只能被一個執行緒訪問,其他執行緒需要排隊等待
			 */
			synchronized (this) {
				System.out.println(Thread.currentThread().getName()+" : 上廁所...");
				Thread.sleep(2000);
				System.out.println(Thread.currentThread().getName()+" : 出廁所");
			}
			System.out.println(Thread.currentThread().getName()+" : 上完廁所,真舒服,洗個手 ... end");
		}catch (Exception e) {
			// TODO: handle exception
		}
	}
}
class Person implements Runnable{
	private WC wc;
	public Person(WC wc) {
		super();
		this.wc = wc;
	}
	public void run() {
		wc.test();
	}
}

執行結果圖: (如果不處理執行緒安全問題可能就會出現下圖2情況:張三和李四同時在一個廁所上廁所 很噁心...哈哈)

     


ex3 : 鎖機制Lock  ==》 解決執行緒安全問題   (這個例子在ex2上修改而成的)

/**
 * 使用Lock的步驟:
 * 1.建立Lock實現類的物件
 * 2.使用Lock物件的lock方法加鎖
 * 3.使用Lock物件的unlock方法解鎖
 * 注意:可把unlock方法的呼叫放在finally程式碼塊中,保證一定能解鎖
 * @author 鄭清
 */
public class Demo3 {
	public static void main(String[] args) {
		WC wc = new WC();
		//自定義執行緒步驟③:建立自定義類物件
		Person person1 = new Person(wc);
		Person person2 = new Person(wc);
		//自定義執行緒步驟④:建立Thread類物件
		Thread t1 = new Thread(person1);
		Thread t2 = new Thread(person2);
		//給執行緒名稱賦值
		t1.setName("張三");
		t2.setName("李四");
		//自定義執行緒步驟⑤:啟動執行緒
		t1.start();
		t2.start();
	}
}
// 上廁所
class WC {
	// 1.建立Lock實現類的物件  注意:需在test()方法外執行,如果在test()方法裡執行,在test()方法被呼叫的時候就會被建立很多ReentrantLock物件,即出現很多鎖
	ReentrantLock lock = new ReentrantLock();
	// 定義一個方法實現上廁所
	public void test() {
		System.out.println(Thread.currentThread().getName() + " : 拿紙...");
		// 2.使用Lock物件的lock方法加鎖
		lock.lock();
		try {
			System.out.println(Thread.currentThread().getName() + " : 上廁所...");
			System.out.println(Thread.currentThread().getName() + " : 出廁所");
			System.out.println(Thread.currentThread().getName() + " : 上完廁所,真舒服,洗個手 ... end");
		} finally {
			// 3.使用Lock物件的unlock方法解鎖
			lock.unlock();
		}
	}
}
//自定義執行緒步驟①:建立自定義類 實現 Runnable介面
class Person implements Runnable {
	private WC wc;
	public Person(WC wc) {
		super();
		this.wc = wc;
	}
	//自定義執行緒步驟②:覆寫run方法
	public void run() {
		wc.test();
	}
}

執行結果圖:(這裡注意:在lock鎖的地方  比如這個例子,圖2中 張三上廁所的同時,李四可以去拿紙,但是不能去廁所!!)

  

相關文章