1. 程式人生 > >執行緒安全與併發安全探究(一)

執行緒安全與併發安全探究(一)

執行緒安全也可是說是併發安全。

在多執行緒環境下能正確執行的程式碼就是執行緒安全的程式碼。安全的意思就是說能正確執行,否則後面就是程式執行錯誤或者出現各種異常情況。

執行緒安全是指多執行緒訪問同一程式碼或者同一共享資料時,不會產生不確定的結果。編寫執行緒安全的程式碼依靠的是執行緒同步。

執行緒安全就是多執行緒訪問時,採用了加鎖機制,當一個執行緒訪問該類的某個資料時,進行保護,其他執行緒不能進行訪問直到該執行緒讀取完,其他執行緒才可使用。不會出現資料不一致或者資料汙染。執行緒不安全就是不提供資料訪問保護,有可能出現多個執行緒先後更改資料造成所得到的資料是髒資料

非執行緒安全是指多執行緒操作同一個物件可能會出現問題,而執行緒安全則是多執行緒操作同一個物件不會出現問題。

如果你的程式碼所在的程序中有多個執行緒在同時執行,而這些執行緒可能會同時執行這段程式碼。如果每次執行結果和單執行緒執行的結果是一樣的,而且其他的變數的值也和預期的是一樣的,就是執行緒安全的。或者說:一個類或者程式所提供的介面對於執行緒來說是原子操作或者多個執行緒之間的切換不會導致該介面的執行結果存在二義性,也就是說我們不用考慮同步的問題。執行緒安全問題都是由全域性變數靜態變數引起的。其實準確的說應該是共享變數引起的。

若每個執行緒中對全域性變數靜態變數只有讀操作,而無寫操作,一般來說,這個全域性變數是執行緒安全的;若有多個執行緒同時執行寫操作,一般都需要考慮執行緒同步,否則的話就可能影響執行緒安全。

執行緒安全問題主要出現在訪問臨界資源的時候,就是訪問同一個物件的時候,可能會出現無法挽回的損失,特別是在關於資金安全方面的時候,當然還有資料庫事務方面的問題。他們很類似,都是要保證資料的原子性。那麼在java中如何保證執行緒安全呢?對與共同使用的物件進行加鎖,意思是使用的時候,那麼你就必須等待,等我用完之後你再用,反之依然。就像上廁所,你去的時候我是不能去的。

下面看一個執行緒非安全具體例項:


/**
 * 非執行緒安全舉例
 * @author zhou
 *
 */
public class ThreadSecurityTest {  
	  //Outerput outerput2 = new Outerput();   is ok
	 public static void main(String[] args) {  
	  new ThreadSecurityTest().sartThread();
	 }  
	 
	 public void sartThread(){  
		 final  Outerput outerput = new Outerput();  
	 
	  new Thread(new Runnable(){  
	   @Override 
	   public void run() {  
	    while(true){  
	     try {  
	      Thread.sleep(5);  
	     } catch (InterruptedException e) {  
	      e.printStackTrace();  
	     }  
	 /*   synchronized(outerput){
	    	 outerput.print("zhangsan");  
	     }*/
	     new Outerput().print("zhangsan");
	    }  
	   }  
	     
	  }).start();  
	  new Thread(new Runnable(){  
	   @Override 
	   public void run() {  
	    while(true){  
	     try {  
	      Thread.sleep(5);  
	     } catch (InterruptedException e) {  
	      e.printStackTrace();  
	     }  
	 /*    synchronized (outerput) {
				     outerput.print("lisi");  
		}*/
	     new Outerput().print("lisi");
	    }  
	   }  
	     
	  }).start();  
	 }  
	 
	 public  class Outerput{  
	/*  public synchronized void  print(String name){   //is ok 
	   for(int i = 0;i < name.length(); i++){  
	    System.out.print(name.charAt(i));  
	    printToFile(name.charAt(i));
	   }
	   printToFile('\r');
	   printToFile('\n');
	   System.out.println();  
	  }*/
		  public   void  print(String name){  
			   for(int i = 0;i < name.length(); i++){  
			    System.out.print(name.charAt(i));  
			    printToFile(name.charAt(i));
			   }
			   printToFile('\r');
			   printToFile('\n');
			   System.out.println();  
			  }
	  public synchronized void printToFile(char ch)  {
		  try {
			 
			  File file = new File("outputChar_ok.txt");
			  //	  if(!file.exists()){
			FileWriter fw = new FileWriter(file, true);
			 
			FileOutputStream fos = new FileOutputStream("outputChar2_ok.txt", true);
			fos.write(ch);
			fos.flush();
			//  fw.write(ch);
			  fw.append(ch);
			  fw.flush();
			  
			  fw.close();
			  fos.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	  }
	 }
	 
	 
	} 

分析:

輸出的結果中可能出現這種情況:

lizshiang

san

lisi

zhangsan

lisi

zhangsan

lisi

zhangsalnisi

...

原因:以上程式碼沒有對共同持有的物件outerput加鎖,所以會出現執行緒安全問題

1、對程式碼塊加鎖

對共同持有的物件加鎖可以把內部類寫成這樣的

1.  public class Outerput{  
2.    public void print(String name){  
3.     synchronized (this) {  
4.      for(int i = 0;i < name.length(); i++){  
5.       System.out.print(name.charAt(i));  
6.      }  
7.      System.out.println();  
8.     }  
9.    }  
10.  } 

2、對非靜態方法加鎖,加鎖的物件是this

1.  public class Outerput{  
2.    public synchronized void print(String name){  
3.     for(int i = 0;i < name.length(); i++){  
4.      System.out.print(name.charAt(i));  
5.     }  
6.     System.out.println();  
7.    }  
8.   }  

3、對靜態方法加鎖的物件到底是誰?

1.  public static synchronized  void print2(String name){  
2.     for(int i = 0;i < name.length(); i++){  
3.      System.out.print(name.charAt(i));  
4.     }  
5.     System.out.println();  
6.    } 
4、線上程中的run方法中進行處理加鎖 如

  synchronized (outerput) {

                                 outerput.print("lisi"); 

              }

其實加鎖的物件是位元組碼物件,Outerput.class

如果和非靜態方法同時持有同一個物件時,可以持有同一個位元組碼物件。