public class Synchronized { public static void main(String[] args) { new Synchronized().xxx(); } private void xxx() { TestSynchronized t = new TestSynchronized();// 1.7 要加 final new Thread(() -> { while (true) { try { TimeUnit.MILLISECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } t.test5("0123456789"); } }, "t1").start(); new Thread(() -> { while (true) { try { TimeUnit.MILLISECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } t.test5("abcdefghij"); } }, "t2").start(); } } class TestSynchronized { public void test1(String data) { try { TimeUnit.SECONDS.sleep(1); synchronized (this) { for (int i = 0; i < data.length(); i++) { System.out.print(data.charAt(i)); } System.out.println(); } } catch (InterruptedException e) { e.printStackTrace(); } } public synchronized void test2(String data) { try { TimeUnit.SECONDS.sleep(1); for (int i = 0; i < data.length(); i++) { System.out.print(data.charAt(i)); } System.out.println(); } catch (InterruptedException e) { e.printStackTrace(); } } public void test3(String data) { try { TimeUnit.SECONDS.sleep(1); synchronized (TestSynchronized.class) { for (int i = 0; i < data.length(); i++) { System.out.print(data.charAt(i)); } System.out.println(); } } catch (InterruptedException e) { e.printStackTrace(); } } public static synchronized void test4(String data) { try { TimeUnit.SECONDS.sleep(1); for (int i = 0; i < data.length(); i++) { System.out.print(data.charAt(i)); } System.out.println(); } catch (InterruptedException e) { e.printStackTrace(); } } public void test5(String data) { try { String x = ""; TimeUnit.SECONDS.sleep(1); synchronized (x) { for (int i = 0; i < data.length(); i++) { System.out.print(data.charAt(i)); x = "ooo" + i; // 修改不影響,因為每個執行緒進來的 x 是一樣的,原子性不用加鎖 } System.out.println(); } } catch (InterruptedException e) { e.printStackTrace(); } } }
※Thread 要使用區域變數在 1.8 可以不加 final,但如果改值還是會編譯錯誤
※synchronized(this) 和寫在普通方法上一樣,只差在鎖的範圍 (wait()只能使用在這兩種);
synchronized(類.class) 和寫在靜態方法上一樣,只差在鎖的範圍
P.S 將 wait 寫在 synchronized 靜態方法裡會編譯錯誤;但如果寫在 synchronized(類.class) 會是執行期錯誤
※兩個線程的鎖必需是相同的鎖,如 xxx 方法的 t.test5() 改成 new TestSynchronized().test5(),因為有 new 了,所以就不是一樣的鎖了
但 test4 是靜態方法,所以 new 幾次都一樣
※test5 的鎖是自己寫的,內容不能改,但因為有字串池的關係,所以改也 ok;如果將「//修改不影響」這行改成 new String 也沒關係(因為每個執行緒進來是一樣的);String x 改成用 new 的方式就不行
.如果改成用 data,那傳進去的字串內容一樣也是 ok 的,因為有字串池;又如果傳進去的值是用 new String("") ,就沒有字串池了,所以就不行了
.Byte、Short、Integer、Long、Character 有 XxxCache 做快取,會在 -128~127,所以這個範圍內的鎖也像字串池一樣;我試了給 127,然後 一直+1 (或者 new Integer()),雖然已超過這個範圍但也是 ok 的(因為每個執行緒進來是一樣的);又如果一開始就超過這個範圍就不行了
※synchronized(this) 如果是鎖整個方法,那就和 synchronized 方法一樣
※synchronized(類別名稱) 如果是鎖整個方法,那就和 synchronized 靜態方法一樣
※練習一
主線程執行 3 次、子線程執行 10 次、主線程再執行 3 次、子線程再執行 10 次,如此循環 5 次※第一步
public class Outer5Main3Sub10 { private void xxx() { new Thread(() -> { try { TimeUnit.SECONDS.sleep(1); for (int i = 1; i <= 5; i++) { synchronized (Outer5Main3Sub10.class) { for (int j = 1; j <= 3; j++) { System.out.println(Thread.currentThread().getName() + j + ",i=" + i); } } } } catch (InterruptedException e) { e.printStackTrace(); } }, "子").start(); new Thread(() -> { try { TimeUnit.SECONDS.sleep(1); for (int i = 1; i <= 5; i++) { synchronized (Outer5Main3Sub10.class) { for (int j = 1; j <= 10; j++) { System.out.println(Thread.currentThread().getName() + j + ",i=" + i); } } } } catch (InterruptedException e) { e.printStackTrace(); } }, "主").start(); } public static void main(String[] args) { new Outer5Main3Sub10().xxx(); } }
※先寫個互斥
※第二步
public class Outer5Main3Sub10 { private MainSub ms = new MainSub(); private void xxx() { new Thread(() -> { try { TimeUnit.SECONDS.sleep(1); for (int i = 1; i <= 5; i++) { ms.sub(i); } } catch (InterruptedException e) { e.printStackTrace(); } }, "子").start(); new Thread(() -> { try { TimeUnit.SECONDS.sleep(1); for (int i = 1; i <= 5; i++) { ms.main(i); } } catch (InterruptedException e) { e.printStackTrace(); } }, "主").start(); } public static void main(String[] args) { new Outer5Main3Sub10().xxx(); } } class MainSub { public synchronized void sub(int i) { for (int j = 1; j <= 3; j++) { System.out.println(Thread.currentThread().getName() + j + ",i=" + i); } } public synchronized void main(int i) { for (int j = 1; j <= 10; j++) { System.out.println(Thread.currentThread().getName() + j + ",i=" + i); } } }
※將同步鎖放到新的類別
※第三步
public class Outer5Main3Sub10 { private MainSub ms = new MainSub(); private void xxx() { new Thread(() -> { try { TimeUnit.SECONDS.sleep(1); for (int i = 1; i <= 5; i++) { ms.sub(i); } } catch (InterruptedException e) { e.printStackTrace(); } }, "子").start(); new Thread(() -> { try { TimeUnit.SECONDS.sleep(1); for (int i = 1; i <= 5; i++) { ms.main(i); } } catch (InterruptedException e) { e.printStackTrace(); } }, "主").start(); } public static void main(String[] args) { new Outer5Main3Sub10().xxx(); } } class MainSub { private boolean sub = true; public synchronized void sub(int i) { while(!sub) { // 注意 spurious wake up,所以不要用 if try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } for (int j = 1; j <= 3; j++) { System.out.println(Thread.currentThread().getName() + j + ",i=" + i); } sub = false; this.notify(); } public synchronized void main(int i) { while(sub) { // 注意 spurious wake up,所以不要用 if try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } for (int j = 1; j <= 10; j++) { System.out.println(Thread.currentThread().getName() + j + ",i=" + i); } sub = true; this.notify(); } }
※使用喚醒
※API 的 Object 物件的 wait 方法有說明,有時候會有 spurious wake up (偽喚醒),所以使用 while 代替 if
如果第二步不寫類別
public class Outer5Main3Sub10 { private boolean sub = true; private void xxx() { Outer5Main3Sub10 sm = new Outer5Main3Sub10(); new Thread(() -> { try { TimeUnit.SECONDS.sleep(1); for (int i = 1; i <= 5; i++) { synchronized (sm) { while (!sub) { try { sm.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } for (int j = 1; j <= 3; j++) { System.out.println(Thread.currentThread().getName() + j + ",i=" + i); } sub = false; sm.notify(); } } } catch (InterruptedException e) { e.printStackTrace(); } }, "子").start(); new Thread(() -> { try { TimeUnit.SECONDS.sleep(1); for (int i = 1; i <= 5; i++) { synchronized (sm) { while (sub) { try { sm.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } for (int j = 1; j <= 10; j++) { System.out.println(Thread.currentThread().getName() + j + ",i=" + i); } sub = true; sm.notify(); } } } catch (InterruptedException e) { e.printStackTrace(); } }, "主").start(); } public static void main(String[] args) { new Outer5Main3Sub10().xxx(); } }
※
※練習二
兩個執行緒,第一個印 1~52;第二個印 A~Z,結果為 12A34B56C...5152Zprivate void xxx() { SynchronizedTest st = new SynchronizedTest(); new Thread(() -> { for (var i = 1; i <= 26; i++) { synchronized (st) { while (!flag) { try { st.wait(); } catch (InterruptedException e) { System.err.println("t1err=" + e.getMessage()); } } System.out.print(i * 2 - 1 + " "); System.out.println(i * 2); System.out.println("========================"); flag = false; st.notify(); } } }).start(); new Thread(() -> { for (var i = 1; i <= 26; i++) { synchronized (st) { while (flag) { try { st.wait(); } catch (InterruptedException e) { System.err.println("t2err=" + e.getMessage()); } } System.out.println((char) (i + ('A' - 1))); System.out.println("========================"); flag = true; st.notify(); } } }).start(); }
沒有留言:
張貼留言