1. 程式人生 > >馬士兵併發程式設計學習筆記1

馬士兵併發程式設計學習筆記1


(一) public class Demo01 {
private int count = 10; private Object object = new Object(); @Test public void test(){ synchronized (object) { //任何執行緒要執行下面的程式碼,必須先拿到object物件的鎖 count --; System.out.println(Thread.currentThread
().getName() + " count = " + count);
} }
public static void main(String[] args) { Demo01 demo01=new Demo01();
new Thread(()->demo01.test()).start(); new Thread(()->demo01.test()).start();
} 1.synchronized關鍵字鎖定的是物件不是程式碼塊,demo中鎖的是object物件的例項(堆記憶體中)
2.鎖定的物件有兩種情況:①類的例項 ②類的位元組碼(.class) 3.關於執行緒安全:加synchronized關鍵字之後不一定能實現執行緒安全,具體還要看 鎖定的物件是否唯一。 (二) public class Demo02 {
private int count = 10; @Test public void test(){ synchronized (this) { //任何執行緒要執行下面的程式碼,必須先拿到Demo02物件例項的鎖
count --; System.out.println(Thread.currentThread().getName() + " count = " + count); } } } 1.synchronized(this)鎖定的是當前類的例項,demo中鎖定的是Demo02類的例項 2.此demo中如果Demo02類是單例的話可以保證在多執行緒訪問時是執行緒安全的, 如果存在有多個Demo02的例項的話在多執行緒中不能保證執行緒安全,因為方法中的鎖不唯一了。(堆記憶體中的地址不一樣) (三) public class Demo03 {
private int count = 10; public synchronized void test(){//等同於synchronized(this),鎖定的是Demo03物件的例項 count --; System.out.println(Thread.currentThread().getName() + " count =" + count); } } 1.synchronized關鍵字修飾普通方法等同於synchronized(this) (四) public class Demo04 {
private static int count = 10; public synchronized static void test1(){ //這裡等同於synchronized(Demo04.class) count --; System.out.println(Thread.currentThread().getName() + " count = " + count); } public static void test2(){ //考慮一下這裡寫synchronize(this)是否可以 synchronized (Demo04.class) { count --; } } } 1.synchronize關鍵字修飾靜態方法鎖定的是類的.class檔案 2.靜態方法中synchronize鎖定程式碼塊,鎖定的物件不能是類的例項,只能是類的.class檔案。 原理如同在靜態方法中不能直接呼叫非靜態方法 3.類的.class檔案是唯一的,所以說synchronize修飾靜態方法或者鎖定的物件是類的.class檔案的時候 在多執行緒中是可以實現執行緒安全的 (五) public class Demo05 implements Runnable{
private int count = 10; @Override public /* synchronized*/ void run(){ count --; System.out.println(Thread.currentThread().getName() + " count = " + count); } public static void main(String[] args) { Demo05 demo05 = new Demo05(); for (int i = 0; i < 5; i++) { new Thread(demo05,"THREAD" + i).start(); } } } 1.run()方法沒加synchronized關鍵字時,多個執行緒同時訪問count,執行緒是不安全的 2.run()方法加上synchronized關鍵字後,鎖定的是Demo05物件的例項,因為只建立了 一個Demo05的例項,多個執行緒訪問時都要拿到Demo05的鎖標記才能執行,在多個執行緒同時訪問時也是執行緒安全的 (六) public class Demo06 implements Runnable{
private int count = 10;
@Override public synchronized void run() { count --; System.out.println(Thread.currentThread().getName() + " count = " + count); }
public static void main(String[] args) { for (int i = 0; i < 5; i++) { Demo06 demo06 = new Demo06();//注意這裡 new Thread(demo06,"THREAD" + i).start(); } } } 1.執行可以知道,demo中雖然加上了synchronized關鍵字來修飾方法,但是執行緒是不安全的。為什麼呢?? 分析一下:synchronized修飾的是普通方法,鎖定的是Demo06例項,從Main方法中可以看到,在for迴圈中 建立了多個Demo06的例項,也就是說每個執行緒對應都拿到各自的鎖標記,可以同時執行。 例子: 多人同時上廁所,廁所門只有一把鎖的時候是一個人上完之後把鑰匙(鎖標記)給到下一個人才可以開門上廁所 如果廁所門的鎖有多個鑰匙的情況下,就是每個人都有鎖的鑰匙了,大家可以一起去開啟門來上廁所。(歸根結底還是堆記憶體上的地址) demo中就如同廁所門的鎖有多把鑰匙(鎖標記),不能實現執行緒安全 (七) public class Demo07 {
public synchronized void test1(){ System.out.println(Thread.currentThread().getName() + " test1 start.........."); try { Thread.sleep(10 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " test1 end........"); } public void test2(){ try { Thread.sleep(5 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "test2 execute......"); } public static void main(String[] args) { Demo07 demo07 = new Demo07(); new Thread(demo07 :: test1,"t1").start(); //JDK1.8新特性 new Thread(demo07 :: test2,"t2").start(); //JDK1.8新特性 } } 執行結果: t1 test1 start.......... t2test2 execute...... t1 test1 end........ 1.同步方法和非同步方法是可以同時呼叫的 (八) package thread.demo_008;
import java.util.concurrent.TimeUnit;
/** * 對業務寫方法加鎖 * 對業務讀方法不加鎖 * 容易產生髒讀問題 * @author Jcon * */ public class Demo08 {
String name; double balance; public synchronized void set(String name, double balance){ this.name = name; try { Thread.sleep(2 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } this.balance = balance; } public synchronized double getBalance(String name){ return this.balance; }
public static void main(String[] args) { Demo08 demo08 = new Demo08(); new Thread(()->demo08.set("zhangsan",100.0)).start(); //JDK1.8新特性 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(demo08.getBalance("zhangsan")); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(demo08.getBalance("zhangsan")); } }
2.對業務寫方法加鎖,同時也要對業務讀方法加鎖,否則容易產生髒讀問題 (九) /** * 一個同步方法可以呼叫另一個同步方法,一個執行緒已經擁有某個物件的鎖,再次申請的時候 * 仍然會得到該物件的鎖 * 也就是說synchronized獲得的鎖是可重入的 * @author Jcon * */ public class Demo09 {
synchronized void test1(){ System.out.println("test1 start........."); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } test2(); } synchronized void test2(){ try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("test2 start......."); } public static void main(String[] args) { Demo09 demo09 = new Demo09(); demo09.test1(); } } 1.一個同步方法可以呼叫另一個同步方法,一個執行緒已經擁有某個物件的鎖, 再次申請的時候仍然會得到該物件的鎖 也就是說synchronized獲得的鎖是可重入的 (十) package thread.demo_010;
import java.util.concurrent.TimeUnit;
/** * 一個同步方法可以呼叫另外一個同步方法,一個執行緒已經擁有某個物件的鎖, * 再次申請的時候仍然會得到該物件的鎖,也就是說synchronize獲得的鎖是可重入的 * 這裡是繼承中有可能發生的情形,子類呼叫父類的同步方法 * @author Jcon * */ public class Demo10 {
synchronized void test(){ System.out.println("test start........"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("test end........"); } public static void main(String[] args) { new Demo100().test(); } }
class Demo100 extends Demo10{ @Override synchronized void test() { System.out.println("child test start......."); super.test(); System.out.println("child test end......."); } } 2.一個同步方法可以呼叫另一個同步方法,一個執行緒已經擁有某個物件的鎖, 再次申請的時候仍然會得到該物件的鎖 也就是說synchronized獲得的鎖是可重入的( 這裡是繼承中有可能發生的情形,子類呼叫父類的同步方法
(十一) /** * 程式在執行過程中,如果出現異常,預設情況鎖會被釋放 * 所以,在併發處理的過程中,有異常要多加小心,不然可能會發生不一致的情況 * 比如,在一個web app處理過程中,多個servlet執行緒共同訪問通一個資源,這是如果異常處理不合適 * 在第一個執行緒中丟擲異常,其他執行緒就會進入同步程式碼去,有可能訪問到異常產生是的資料 * 因此要非常小心的處理同步業務邏輯中的異常 * @author Jcon * */ public class Demo11 {
int count = 0; synchronized void test(){ System.out.println(Thread.currentThread().getName() + " start......"); while (true) { count ++; System.out.println(Thread.currentThread().getName() + " count = " + count); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } if (count == 5) { int i = 1/0; //此處丟擲異常,鎖將被釋放,要想不被釋放,可以在這裡進行catch處理,然後讓迴圈繼續 } } } public static void main(String[] args) { Demo11 demo11 = new Demo11(); Runnable r = new Runnable() { @Override public void run() { demo11.test(); } }; new Thread(r, "t1").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(r, "t2").start(); } } 1.程式在執行過程中,如果出現異常,預設情況鎖會被釋放 (十二) public class Demo12 {
volatile boolean running = true; public void test(){ System.out.println("test start......."); while (running) { /*try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }*/ } System.out.println("test end........"); } public static void main(String[] args) { Demo12 demo12 = new Demo12(); new Thread(demo12 :: test, "t1").start(); //JDK1.8新特性 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } demo12.running = false; } } * volatile 關鍵字,使一個變數在多個執行緒間可見 * A B執行緒都用到一個變數,java預設是A執行緒中保留一份copy,這樣如果B執行緒修改了該變數,則A執行緒未必知道 * 使用volatile關鍵字,會讓所有執行緒都會讀到變數的修改值 * * 在下面的程式碼中,running是存在於堆記憶體的t物件中 * 當執行緒t1開始執行的時候,會把running值從記憶體中讀到t1執行緒的工作區,在執行過程中直接使用這個copy,並不會每次都去 * 讀取堆記憶體,這樣,當主執行緒修改running的值之後,t1執行緒感知不到,所以不會停止執行 * * 使用volatile,將會強制所有執行緒都去堆記憶體中讀取running的值 * * 可以閱讀這篇文章進行更深入的理解 * http://www.cnblogs.com/nexiyi/p/java_memory_model_and_thread.html *
(十三) ** * volatile並不能保證多個執行緒共同修改running變數時所帶來的不一致問題, * 也就是說volatile不能替代synchronize * 執行下面的程式,並分析結果 * @author Jcon * */ public class Demo13 {
volatile int count = 0; public void test(){ for (int i = 0; i < 10000; i++) { count ++; } } public static void main(String[] args) { Demo13 demo13 = new Demo13(); List<Thread> threads = new ArrayList<Thread>(); for (int i = 0; i < 10; i++) { threads.add(new Thread(demo13::test, "thread-" + i)); } threads.forEach((o)->o.start()); //JDK1.8新特性 threads.forEach((o)->{ //JDK1.8新特性 try { o.join(); //等執行緒執行完畢之後才執行主執行緒main } catch (Exception e) { e.printStackTrace(); } }); System.out.println(demo13.count); } } * volatile並不能保證多個執行緒共同修改running變數時所帶來的不一致問題,也就是說volatile不能替代synchronized (十四) public class Demo14 {
int count = 0; public synchronized void test(){ for (int i = 0; i < 10000; i++) { count ++; } } public static void main(String[] args) { Demo14 demo14 = new Demo14(); List<Thread> threads = new ArrayList<Thread>(); for (int i = 0; i < 10; i++) { threads.add(new Thread(demo14::test, "thread-" + i)); } threads.forEach((o)->o.start()); //JDK1.8新特性 threads.forEach((o)->{ //JDK1.8新特性 try { o.join(); //等執行緒執行完畢之後才執行主執行緒main } catch (Exception e) { e.printStackTrace(); } }); System.out.println(demo14.count); } }
* 對比上一個程式,可以用synchronized解決,synchronize可以保證可見性和原子性,volatile只能保證可見性 (十五) public class Demo15 {
//int count = 0; AtomicInteger count = new AtomicInteger(0); public /*synchronized*/ void test(){ for (int i = 0; i < 10000; i++) { //count ++; count.incrementAndGet(); //count++ // 注意下面則不構成原子性,因為在get時,執行緒a進行判斷後,但是不執行下面程式碼 // 執行緒b進行判斷,執行完程式碼,此時程式碼是1000,然後執行緒a執行,此時結果是1001 // if (count.get() > 1000) { // count.incrementAndGet(); // } } } public static void main(String[] args) { Demo15 demo15 = new Demo15(); List<Thread> threads = new ArrayList<Thread>(); for (int i = 0; i < 10; i++) { threads.add(new Thread(demo15::test, "thread-" + i)); } threads.forEach((o)->o.start()); //JDK1.8新特性 threads.forEach((o)->{ //JDK1.8新特性 try { o.join(); //等執行緒執行完畢之後才執行主執行緒main } catch (Exception e) { e.printStackTrace(); } }); System.out.println(demo15.count); }

} * 解決同樣的問題的更高效的方法,使用AtomXXX類 * AtomXXX類本身方法都是原子性的,但不能保證多個方法連續呼叫是原子性 (十六) /** * synchronize優化 * 同步程式碼快中的語句越少越好 * 比較test1和test2 * @author Jcon * */ public class Demo16 {
int count = 0; public synchronized void test1(){ try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } //業務邏輯中只有下面這句需要sync,這時不應該給整個方法上鎖 count ++; try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } } public void test2(){ try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } //業務邏輯中只有下面這句需要sync,這時不應該給整個方法上鎖 //採用細粒度的鎖,可以是執行緒爭用時間變短,從而提高效率 synchronized (this) { count ++; } try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } } } 1.業務邏輯中只有下面這句需要sync,這時不應該給整個方法上鎖 2.採用細粒度的鎖,可以是執行緒爭用時間變短,從而提高效率 (十七) /** * 鎖定某物件o,如果o的屬性發生改變,不影響鎖的使用 * 但是如果o變成另外一個物件,則鎖定的物件發生改變 * 應該避免將鎖定物件的引用變成另外一個物件 * @author Jcon * */ public class Demo17 {
Object o = new Object(); public void test(){ synchronized (o) { while (true) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()); } } } public static void main(String[] args) { Demo17 demo17 = new Demo17(); //啟動第一個執行緒 new Thread(demo17 :: test, "t1").start(); //JDK1.8新特性 try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } //啟動第二個執行緒 Thread t2 = new Thread(demo17 :: test, "t2"); demo17.o = new Object(); //鎖物件發生改變,所以t2執行緒得以執行,如果註釋掉這句話,執行緒t2將永遠得不到執行機會 t2.start(); } } * 鎖定某物件o,如果o的屬性發生改變,不影響鎖的使用 * 但是如果o變成另外一個物件,則鎖定的物件發生改變 * 應該避免將鎖定物件的引用變成另外一個物件 (十八) /** * 不要以字串常量作為鎖定的物件 * 在下面的例子中,test1和test2其實鎖定的是同一個物件 * 這種情況還會發生比較詭異的現象,比如你用到了一個類庫,在該類庫中程式碼鎖定了字串"hello", * 但是你讀不到原始碼,所以你在自己的程式碼中也鎖定了"hello",這時候就有可能發生非常詭異的死鎖阻塞, * 因為你的程式和你用的的類庫不經意間使用了同一把鎖 * @author Jcon * */ public class Demo18 {
String s1 = "hello"; String s2 = "hello"; public void test1(){ synchronized (s1) { } } public void test2(){ synchronized (s2) { } } } 1.不要以字串常量作為鎖定的物件