本實驗主要考察多線程對單例模式的操作,和多線程對同一資源的讀取,兩個知識。實驗涉及到三個類:
1)一個pojo類Student,包括set/get方法。
2)一個線程類,設置student的成員變量age和name的值為111和111
3)另一個線程類,設置student的成員變量age和name的值為222和2222
4)main類,for循環200次,分別創建200個線程1和線程2對同一資源訪問。(共400個線程)
1.第一種情況:餓漢式單例模式保證多線程操控的是同一對象
//餓漢式單例模式pojo類
public class Student { private String age = "12"; private String name = "Tome"; private static Student student = new Student();//類加載時候創建對象 public String getNameAndAge() { return name+":"+age; } public void setNameAndAge(String name,String age) { this.name = name; this.age = age; } private Student() //構造函數私有化 { } public static Student GetInstace() { //方法區函數,靜態函數 return student; } }
線程2類:
public class MyThread extends Thread { @Override public void run() { // TODO Auto-generated method stub System.out.println(Student.GetInstace().hashCode()); } }
測試類,創建並啟動400個線程:
public class AppMain implements Runnable{ public static void main(String[] args) { AppMain appMain = new AppMain(); for(int i =0;i<200;i++) { Thread thread1 = new Thread(appMain);//線程1 MyThread thread2 = new MyThread();//線程2 thread1.start(); thread2.start(); } } @Override public void run() { // TODO Auto-generated method stub System.out.println(Student.GetInstace().hashCode()); } }
結果:
2.第二種情況:共享資源的寫方法不設置任何同步,多個線程可以交叉寫數據
public String getNameAndAge() { return name+":"+age; } public void setNameAndAge(String name,String age) { //沒有設置任何寫同步 this.name = name; this.age = age; }
倆線程操控類:
public class MyThread extends Thread { @Override public void run() { // TODO Auto-generated method stub Student.GetInstace().setNameAndAge("111", "111");//設置name和age值為1
System.out.println(Student.GetInstace().getNameAndAge();); } }
線程2
public class AppMain implements Runnable{
public static void main(String[] args) { AppMain appMain = new AppMain(); for(int i =0;i<200;i++) { Thread thread1 = new Thread(appMain); MyThread thread2 = new MyThread(); thread1.start(); thread2.start(); } } @Override public void run() { // TODO Auto-generated method stub Student.GetInstace().setNameAndAge("222", "2222");//設置name和age為2
System.out.println(Student.GetInstace().getNameAndAge();); } }
執行結果:
3.第三種情況:共享資源的寫方法設置同步synchronized,保證同一時刻只有一個線程才能執行寫,執行完后才釋放鎖。
public String getNameAndAge() { return name+":"+age; } synchronized public void setNameAndAge(String name,String age) { //寫方法設置synchronized了 this.name = name; this.age = age; }
測試類添加打印:
public static void main(String[] args) { AppMain appMain = new AppMain(); for(int i =0;i<200;i++) { Thread thread1 = new Thread(appMain); MyThread thread2 = new MyThread(); thread1.start(); thread2.start(); System.out.println(Student.GetInstace().getNameAndAge());//添加打印,顯示name和age值 } }
這樣就能多個線程按序設置name和set值了。但為什么測試結果依然有臟數據呢?比如111:222這種臟數據呢?
答案:因為沒設置單例對象讀get方法的鎖,這樣讀方法可以隨時獲取值,即使set線程還沒執行完,因為沒有synchronized限制可以隨時訪問。
4.第四種情況,共享資源的讀方法不同步不synchronized,方便隨時讀取不受鎖的限制。但就像之前說的,會讀到寫線程還沒執行完時的數據,造成數據混亂。因為讀線程可以隨時讀,沒有鎖的限制。
public String getNameAndAge() { //讀方法沒有做同步synchronized處理,可以隨時讀取,就可以讀出寫線程未執行完的中間數據 return name+":"+age; } synchronized public void setNameAndAge(String name,String age) { this.name = name; this.age = age; }
操作結果:
5.第五種情況,讀方法也設置synchronized,鎖的對象也是this。保證寫的時候不能讀,保證讀的時候不能寫。即讀寫用同一個鎖。
synchronized public String getNameAndAge() { return name+":"+age; } synchronized public void setNameAndAge(String name,String age) { this.name = name; this.age = age; }
測試結果:
這樣數據就全部准確了,但是這樣效率很低,因為讀寫共同設置一個鎖。讀的時候不能寫,寫的時候不能讀。全部都是按序來訪問。
結論:當多線程共同訪問同一資源時候,此共享對象的讀寫方法,要都設置同一個鎖,保證寫的時候不能讀,讀的時候不能寫,且讀寫都是按序執行。才能保證數據的准確性。
同時,也說明了,沒有設置鎖的方法可以隨時執行,隨時執行,隨時可能被cpu調度以至打斷線程的執行,以至讀到線程執行一半產生的臟數據。