文章目錄
- JUC并發程式設計——八鎖現象
JUC并發程式設計——八鎖現象
八鎖現象
八鎖現象,其實就是關于鎖的八個問題,通過八個問題,逐漸加深對鎖的認識,為了友善示範,這裡全部用synchronized。
案例一:兩個synchronized 同步方法 + 兩個線程 + 一個方法調用者
package com.cheng.Lock8;
public class Test1 {
public static void main(String[] args) {
BackHome backHome = new BackHome();
new Thread(()->{
backHome.car();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
backHome.bus();
},"B").start();
}
}
class BackHome{
public synchronized void car(){
System.out.println("開車回家");
}
public synchronized void bus(){
System.out.println("坐公家回家");
}
}
問題一:正常情況下,線程先列印"開車回家"還是"坐公家回家"?
啟動程式列印測試:
問題二:car() 同步方法延遲3秒,線程先列印"開車回家"還是"坐公家回家"?
public synchronized void car(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("開車回家");
}
啟動程式列印測試:
解釋:synchronized 鎖的對象是方法的調用者,也就是上面的 backHome 對象, backHome 對象隻有一個,是以鎖也隻有一個,兩個方法用的是用一把鎖,誰先拿到誰就先執行。
案例二:兩個同步方法 + 一個普通方法 + 兩個線程 + 一個方法調用者
package com.cheng.Lock8;
import java.util.concurrent.TimeUnit;
public class Test2 {
public static void main(String[] args) {
BackHome2 backHome = new BackHome2();
new Thread(()->{
backHome.car();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
backHome.bus();
},"B").start();
new Thread(()->{
backHome.taxi();
},"C").start();
}
}
class BackHome2{
public synchronized void car(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("開車回家");
}
public synchronized void bus(){
System.out.println("坐公家回家");
}
//普通方法
public void taxi(){
System.out.println("坐計程車回家");
}
}
問題三:car() 同步方法延遲3秒,線程先列印"開車回家"還是"坐計程車回家"?
啟動程式列印測試:
解釋:普通方法沒有 synchronized 關鍵字,不受鎖的影響,又因為car() 同步方法延遲3秒,是以線程先執行普通方法。
案例三:兩個同步方法 + 兩個線程 + 兩個方法調用者
backHome1 調用 car(),
backHome2 調用 bus()
package com.cheng.Lock8;
import java.util.concurrent.TimeUnit;
public class Test2 {
public static void main(String[] args) {
BackHome2 backHome1 = new BackHome2();
BackHome2 backHome2 = new BackHome2();
new Thread(()->{
backHome1.car();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
backHome2.bus();
},"B").start();
}
}
class BackHome2{
public synchronized void car(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("開車回家");
}
public synchronized void bus(){
System.out.println("坐公家回家");
}
}
問題四:兩個方法調用者情況下,線程先列印"開車回家"還是"坐公家回家"?
啟動程式列印測試:
一秒後列印"坐公家回家",三秒後再列印 “開車回家”
解釋:有兩個方法調用者backHome1和backHome2,是以兩個同步方法有兩把鎖,而因為car()方法有延遲,是以bus()方法先執行。
案例四:兩個靜态同步方法 + 兩個線程 + 一個方法調用者
package com.cheng.Lock8;
import java.util.concurrent.TimeUnit;
public class Test3 {
public static void main(String[] args) {
BackHome3 backHome = new BackHome3();
new Thread(()->{
backHome.car();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
backHome.bus();
},"B").start();
}
}
class BackHome3{
public static synchronized void car(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("開車回家");
}
public static synchronized void bus(){
System.out.println("坐公家回家");
}
}
問題五:靜态同步方法下情況下,線程先列印"開車回家"還是"坐公家回家"?
啟動程式列印測試:
在案例四的基礎上再加一個方法調用者:
BackHome3 backHome = new BackHome3();
BackHome3 backHome1 = new BackHome3();
new Thread(()->{
backHome.car();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
backHome1.bus();
},"B").start();
問題六:靜态同步方法下情況下兩個方法調用者,線程先列印"開車回家"還是"坐公家回家"?
啟動程式列印測試:
解釋:加了 static 的同步方法,在類一加載就有了,鎖的對象不再是方法的調用者,而是Class的類模闆,不管有幾個方法調用者,鎖的對象都是這個類模闆且全局唯一。
案例五:一個靜态同步方法 + 一個普通同步方法 + 一個方法調用者
package com.cheng.Lock8;
import java.util.concurrent.TimeUnit;
public class Test4 {
public static void main(String[] args) {
BackHome4 backHome = new BackHome4();
new Thread(()->{
backHome.car();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
backHome.bus();
},"B").start();
}
}
class BackHome4{
public static synchronized void car(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("開車回家");
}
public synchronized void bus(){
System.out.println("坐公家回家");
}
}
問題七:一個靜态同步方法 + 一個普通同步方法情況下,線程先列印"開車回家"還是"坐公家回家"?
啟動程式列印測試:
在案例五的基礎上新增一個方法調用者:
BackHome4 backHome = new BackHome4();
BackHome4 backHome1 = new BackHome4();
new Thread(()->{
backHome.car();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
backHome1.bus();
},"B").start();
問題八:一個靜态同步方法 + 一個普通同步方法情況下 + 兩個方法調用者,線程先列印"開車回家"還是"坐公家回家"?
啟動程式列印測試:
解釋:一個靜态同步方法 + 一個普通同步方法情況下,鎖的對象不同,靜态同步方法鎖的是全局唯一的類模闆,普通同步方法鎖的是方法的調用者,是以就算有兩個方法調用者,鎖也不會沖突,是以沒有延遲的方法先執行。
總結
synchronized 鎖的是方法的調用者,不同的對象對應不同的鎖
static 修飾的方法屬于Class模版,鎖的對象隻有一個,就是Class模版