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();
}