天天看點

淺談多線程之間實作同步(線程安全)

一、為什麼有線程安全問題存在?

當多個線程同時共享,同一個全局變量或靜态變量,做寫的操作時,可能會發生資料沖突問題,也就是線程安全問題,但是做讀的操作是不會發生資料沖突問題。

二、線程不安全問題怎麼解決?

使用synchronized、jdk1.5并發包lock

三、synchronizd的使用方法

  1. 使用同步代碼塊

    就是将可能發生線程安全問題的代碼,給包裹起來。

    //“同一個資料”部分相當于一把鎖,可以使用空的Object對象作為入參傳入

    synchronized(同一個資料){

    可能會發生線程沖突問題

    }

    注意:synchronized包裹的部分一定要是可能會發生線程安全問題的代碼,而且synchrozied關鍵字隻适合單個jvm,分布式叢集環境下不能使用,在分布式叢集環境下要使用分布式鎖zookeeper。

    package chauncy.threadtrain;
    
    class ThreadTrain implements Runnable {
    	// 火車票總數
    	private int count = 100;
    	//兩個線程一定要用同一把鎖
    	private Object obj=new Object();
    
    	@Override
    	public void run() {
    		while (count > 0) {
    			try {
    				Thread.sleep(40);
    			} catch (InterruptedException e) {
    			}
    			synchronized(obj){
    				// 100總數減去現有數量count加1為目前出售的第幾張票
    				System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "張票");
    				count--;
    			}
    		}
    	}
    }
    
    /**
     * 什麼是線程不安全 
     * 當多個線程同時操作同一個共享的全局變量,可能會受到其他線程的幹擾。會發生資料沖突的問題。
     * 線程不安全問題怎麼解決?
     * 使用synchronize、jdk1.5的并發包lock
     * 使用synchronized關鍵字包裹起來的代碼
     * 每次隻能讓目前一個線程進行執行
     * @classDesc: 功能描述(模拟線程不安全問題)
     * @author: ChauncyWang
     * @version: 1.0
     */
    public class ThreadDemo1 {
    	public static void main(String[] args) {
    		//線程類一定要用一個執行個體,因為要重制變量共享的問題
    		ThreadTrain threadTrain = new ThreadTrain();
    		// 1.建立兩個線程
    		Thread thread1 = new Thread(threadTrain);
    		Thread thread2 = new Thread(threadTrain);
    		thread1.start();
    		thread2.start();
    	}
    }
               
  2. 使用同步函數(同步方法)

    什麼是同步函數?

    在方法上修飾synchronized稱為同步函數。

    同步函數用的是什麼鎖?

    同步函數使用this鎖。

    package chauncy.threadtrain;
    
    class ThreadTrain2 implements Runnable {
    	// 火車票總數
    	private int count = 100;
    	//兩個線程一定要用同一把鎖
    	private Object obj = new Object();
    
    	@Override
    	public void run() {
    		while (count > 0) {
    			show();
    		}
    	}
    
    	public synchronized void show() {
    		if (count > 0) {
    			try {
    				Thread.sleep(4);
    			} catch (InterruptedException e) {
    			}
    			// 100總數減去現有數量count加1為目前出售的第幾張票
    			System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "張票");
    			count--;
    		}
    	}
    }
    
    /**
     * @classDesc: 功能描述(使用同步函數(同步方法))
     * @author: ChauncyWang
     * @version: 1.0
     */
    public class ThreadDemo2 {
    	public static void main(String[] args) {
    		//線程類一定要用一個執行個體,因為要重制變量共享的問題
    		ThreadTrain2 threadTrain2 = new ThreadTrain2();
    		// 1.建立兩個線程
    		Thread thread1 = new Thread(threadTrain2);
    		Thread thread2 = new Thread(threadTrain2);
    		thread1.start();
    		thread2.start();
    	}
    }
               
    package chauncy.threadtrain;
    
    class ThreadTrain3 implements Runnable {
    	// 火車票總數
    	private int count = 100;
    	//兩個線程一定要用同一把鎖
    	private Object obj = new Object();
    	public boolean flag=true;
    
    	@Override
    	public void run() {
    		//線程1 flag為true 線程2 flag為false
    		if(flag){
    			while (count > 0) {
    				//入參為obj情況下線程不安全,為this情況線程安全,證明同步函數使用的是this鎖。
    				synchronized(this){
    					if (count > 0) {
    						try {
    							Thread.sleep(4);
    						} catch (InterruptedException e) {
    						}
    						// 100總數減去現有數量count加1為目前出售的第幾張票
    						System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "張票");
    						count--;
    					}
    				}
    			}
    		}else{
    			while (count > 0) {
    				show();
    			}
    		}
    	}
    
    	public synchronized void show() {
    		if (count > 0) {
    			try {
    				Thread.sleep(4);
    			} catch (InterruptedException e) {
    			}
    			// 100總數減去現有數量count加1為目前出售的第幾張票
    			System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "張票");
    			count--;
    		}
    	}
    }
    
    /**
     * @classDesc: 功能描述(證明同步函數是this鎖)
     * @author: ChauncyWang
     * @version: 1.0
     */
    public class ThreadDemo3 {
    	public static void main(String[] args) throws InterruptedException {
    		//線程類一定要用一個執行個體,因為要重制變量共享的問題
    		ThreadTrain3 threadTrain3 = new ThreadTrain3();
    		// 1.建立兩個線程
    		Thread thread1 = new Thread(threadTrain3);
    		Thread thread2 = new Thread(threadTrain3);
    		thread1.start();
    		Thread.sleep(10);
    		threadTrain3.flag=false;
    		thread2.start();
    	}
    }
               
  3. 使用靜态同步函數

    什麼是靜态同步函數?

    方法上加上static關鍵字,并且使用synchronized關鍵字修飾 。

    靜态同步函數使用的鎖就不是this鎖,因為this鎖是屬于對象級别的,靜态同步函數應該使用類鎖,即該類的位元組碼檔案。

    可以使用getClass方法擷取,也可以使用目前線程類的類名.class 擷取該線程類的位元組碼檔案。

    package chauncy.threadtrain;
    
    class ThreadTrain4 implements Runnable {
    	// 火車票總數
    	private static int count = 100;
    	//兩個線程一定要用同一把鎖
    	private Object obj = new Object();
    	public boolean flag=true;
    
    	@Override
    	public void run() {
    		//線程1 flag為true 線程2 flag為false
    		if(flag){
    			while (count > 0) {
    				//入參為obj情況下線程不安全,為this情況線程安全,證明同步函數使用的是this鎖。
    				synchronized(ThreadDemo4.class){
    					if (count > 0) {
    						try {
    							Thread.sleep(4);
    						} catch (InterruptedException e) {
    						}
    						// 100總數減去現有數量count加1為目前出售的第幾張票
    						System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "張票");
    						count--;
    					}
    				}
    			}
    		}else{
    			while (count > 0) {
    				show();
    			}
    		}
    	}
    
    	/**
    	 * static 如果修飾的方法,直接通過類名.方法名進行調用   當class檔案也是位元組碼檔案被加載時,才會被初始化。 static存放在方法區 永久區
    	 * this關鍵字表示目前對象的鎖,static使用類.class的鎖
    	 * @methodDesc: 功能描述()  
    	 * @author: ChauncyWang
    	 * @param:    
    	 * @returnType: void
    	 */
    	public static synchronized void show() {
    		if (count > 0) {
    			try {
    				Thread.sleep(4);
    			} catch (InterruptedException e) {
    			}
    			// 100總數減去現有數量count加1為目前出售的第幾張票
    			System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "張票");
    			count--;
    		}
    	}
    }
    
    /**
     * @classDesc: 功能描述(靜态同步函數)
     * @author: ChauncyWang
     * @version: 1.0
     */
    public class ThreadDemo4 {
    	public static void main(String[] args) throws InterruptedException {
    		//線程類一定要用一個執行個體,因為要重制變量共享的問題
    		ThreadTrain4 threadTrain4 = new ThreadTrain4();
    		// 1.建立兩個線程
    		Thread thread1 = new Thread(threadTrain4);
    		Thread thread2 = new Thread(threadTrain4);
    		thread1.start();
    		Thread.sleep(10);
    		threadTrain4.flag=false;
    		thread2.start();
    	}
    }
               

四、多線程死鎖

  1. 死鎖的類型:資料庫死鎖、線程死鎖、行鎖、表鎖等。
  2. 什麼是多線程死鎖?

    在同步中嵌套同步,導緻鎖無法釋放。

  3. 鎖在什麼時候釋放?

    鎖一般在代碼執行完畢之後自動釋放,讓其它線程拿到鎖執行。

  4. 怎麼避免死鎖?

    同步中盡量不要嵌套同步。

  5. 死鎖的代碼實作:
    package chauncy.threadtrain;
    
    class ThreadTrain5 implements Runnable {
    	// 火車票總數
    	private static int count = 100;
    	//兩個線程一定要用同一把鎖
    	private Object obj = new Object();
    	public boolean flag=true;
    
    	@Override
    	public void run() {
    		//線程1 flag為true 線程2 flag為false
    		if(flag){
    			while (count > 0) {
    				//鎖一般是在代碼執行完畢之後自動釋放,讓其它線程拿到鎖執行
    				//如果flag為true 先拿到obj這把鎖,再拿到this鎖,才能執行代碼
    				//如果flag為false 先拿到this這把鎖,再拿到obj鎖,才能執行代碼
    				synchronized(obj){
    					show();
    				}
    			}
    		}else{
    			while (count > 0) {
    				show();
    			}
    		}
    	}
    
    
    	public synchronized void show() {
    		synchronized(obj){
    			if (count > 0) {
    				try {
    					Thread.sleep(4);
    				} catch (InterruptedException e) {
    				}
    				// 100總數減去現有數量count加1為目前出售的第幾張票
    				System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "張票");
    				count--;
    			}
    		}
    	}
    }
    
    /**
     * @classDesc: 功能描述(死鎖實作)
     * @author: ChauncyWang
     * @version: 1.0
     */
    public class ThreadDemo5 {
    	public static void main(String[] args) throws InterruptedException {
    		//線程類一定要用一個執行個體,因為要重制變量共享的問題
    		ThreadTrain5 threadTrain5 = new ThreadTrain5();
    		// 1.建立兩個線程
    		Thread thread1 = new Thread(threadTrain5);
    		Thread thread2 = new Thread(threadTrain5);
    		thread1.start();
    		Thread.sleep(40);
    		threadTrain5.flag=false;
    		thread2.start();
    	}
    }
               

五、線程間實作同步(線程安全)的問題總結

  1. 什麼是多線程安全?

    多個線程同時共享,同一個全局變量或靜态變量,做寫的操作時,可能會發生資料沖突問題,也就是線程安全問題。做讀操作是不會發生資料沖突問題。

  2. 如何解決多線程之間線程安全問題?

    使用多線程之間同步或使用鎖(lock)。

  3. 為什麼使用線程同步或使用鎖能解決線程安全問題呢?

    将可能會發生資料沖突問題(線程不安全問題),隻能讓目前一個線程進行執行。被包裹的代碼執行完成後釋放鎖,讓後才能讓其他線程進行執行。這樣的話就可以解決線程不安全問題。

  4. 什麼是多線程之間同步?

    當多個線程共享同一個資源,不會受到其他線程的幹擾。

  5. 什麼是同步代碼塊?

    是将可能會發生線程安全問題的代碼,給包括起來。隻能讓目前一個線程進行執行,被包裹的代碼執行完成之後才能釋放所,讓後才能讓其他線程進行執行。

  6. 同步代碼塊與同步函數差別?

    同步代碼使用自定鎖(明鎖),同步函數使用this鎖。

  7. 同步函數與靜态同步函數差別(一個靜态方法和一個非靜态靜态怎麼實作同步?)?

    同步函數使用this鎖,靜态同步函數使用位元組碼檔案,也就是類.class。

  8. 什麼是多線程死鎖?

    同步中嵌套同步,解決辦法:同步中盡量不要嵌套同步。