1. 程式人生 > >物件和變數的併發訪問synchronized解析以及死鎖分析排查

物件和變數的併發訪問synchronized解析以及死鎖分析排查

一.synchronized   “非執行緒安全"是指發生在多個執行緒對同一個物件中的例項變數併發訪問時,產生的”髒讀“現象。synchronized同步處理可解決這一問題。 非執行緒安全問題存在於例項變數中,不存在方法內部的私有變數。 1、synchronized修飾方法的兩種情況: (1).當A執行緒呼叫某個物件的synchronized方法,先持有某個物件的鎖;這時B執行緒需要等待A執行緒執行完畢後釋放這個物件鎖才可呼叫這個物件的synchronized方法,即同步。synchronized是一個獨佔鎖,每個鎖請求之間是互斥的 (2).當A執行緒呼叫某個物件的synchronized方法時,B執行緒呼叫這個物件的其他非synchronized方法,不需要等待。 下面是上面兩種結論的證明:
/**
 * @author tangquanbin
 * @date 2018/11/26 21:12
 */
public class Service2 {
    /**
     * 同步方法
     */
    public synchronized void printService() {
        System.out.println(Thread.currentThread().getName() + " " + "start printService thread");
        try {
            TimeUnit.MILLISECONDS.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " " + "printService end ");
    }

    /**
     * 同步方法
     */
    public synchronized void printServiceOther() {
        System.out.println(Thread.currentThread().getName() + " " + "start printServiceOther thread");
        try {
            TimeUnit.MILLISECONDS.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " " + "printServiceOther end ");
    }

    /**
     * 非同步方法
     */
    public void printServiceNotSynchronized() {
        System.out.println(Thread.currentThread().getName() + " " + "start printServiceNotSynchronized thread");
        try {
            TimeUnit.MILLISECONDS.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " " + "printServiceNotSynchronized end ");
    }
}

  執行緒A

public class Syn2ThreadA extends Thread{
    private Service2 service;
    public Syn2ThreadA(Service2 service) {
        this.service = service;
    }

    @Override
    public void run() {
        super.run();
        service.printService();
    }
}

  執行緒B

public class Syn2ThreadB extends Thread{
    private Service2 service;
    public Syn2ThreadB(Service2 service) {
        this.service = service;
    }

    @Override
    public void run() {
        super.run();
        service.printServiceNotSynchronized();
    }
}

  執行緒C

public class Syn2ThreadC extends Thread{
    private Service2 service;
    public Syn2ThreadC(Service2 service) {
        this.service = service;
    }

    @Override
    public void run() {
        super.run();
        service.printServiceOther();
    }
}

  測試方法:

public class Syn2Test {
    public static void main(String[] args) {
        Service2 service = new Service2();
        //Syn2ThreadA呼叫了同步方法
        Syn2ThreadA threadA = new Syn2ThreadA(service);
        threadA.setName("threadA");
        //Syn2ThreadB呼叫非同步
        Syn2ThreadB threadB = new Syn2ThreadB(service);
        threadB.setName("threadB");
        //Syn2ThreadC呼叫了同步方法
        Syn2ThreadC threadC = new Syn2ThreadC(service);
        threadC.setName("threadC");
        threadA.start();
        threadB.start();
        threadC.start();
    }
}
可通過執行順序證明以上結論的正確性。   2、synchronized重入 可重入鎖:即某個執行緒可以獲得一個它自己已持有的鎖。下面的例子在繼承關係中子類可以通過可重入鎖呼叫父類的同步方法,提升了加鎖行為的封裝性。如果沒有可重入鎖就會產生死鎖。 父類
public class Fruit {
    public synchronized void dosomething(){
        System.out.println("printFruit");
    }
}

 子類

public class Apple extends Fruit {
    @Override
    public synchronized void dosomething() {
        super.dosomething();
        System.out.println("apple");
    }
}

  

3.死鎖

  那什麼是死鎖呢? 

      下面是維基百科對死鎖的定義:

  死鎖(英語:Deadlock),又譯為死結,電腦科學名詞。當兩個以上的運算單元,雙方都在等待對方停止執行,以獲取系統資源,但是沒有一方提前退出時,就稱為死鎖。 死鎖的四個條件是:   禁止搶佔 no preemption - 系統資源不能被強制從一個程序中退出   持有和等待 hold and wait - 一個程序可以在等待時持有系統資源   互斥 mutual exclusion - 只有一個程序能持有一個資源   迴圈等待 circular waiting - 一系列程序互相持有其他程序所需要的資源 死鎖只有在這四個條件同時滿足時出現。預防死鎖就是至少破壞這四個條件其中一項,即破壞“禁止搶佔”、破壞“持有等待”、破壞“資源互斥”和破壞“迴圈等待”。 下面這張圖片描述了死鎖情況:

編碼說明:

/**
 * @author tangquanbin
 * @date 2018/11/26 22:37
 */
public class TestDeadlock {

    static final String resource1 = "resource1";
    static final String resource2 = "resource2";

    static class ThreadA extends Thread {
        @Override
        public void run() {
            synchronized (resource1) {
                System.out.println("ThreadA: locked resource 1");
                try {
                    TimeUnit.MILLISECONDS.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (resource2) {
                    System.out.println("ThreadA: locked resource 2");
                }
            }
        }
    }

    static class ThreadB extends Thread {
        @Override
        public void run() {
            synchronized (resource2) {
                System.out.println("ThreadB: locked resource 2");
                try {
                    TimeUnit.MILLISECONDS.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (resource1) {
                    System.out.println("ThreadB: locked resource 1");
                }
            }
        }
    }

    public static void main(String[] args) {
        ThreadA threadA = new ThreadA();
        threadA.setName("====ThreadA====");
        ThreadB threadB = new ThreadB();
        threadB.setName("====ThreadB====");
        threadA.start();
        threadB.start();
    }
}

  執行會產生死鎖情況。

4.追蹤、分析死鎖發生

死鎖檢查方法,命令視窗執行: 1、jps 2、jstack -l 埠     //-l 選項用於列印鎖的附加資訊     下面是部分死鎖資訊:   死鎖只有在這四個條件同時滿足時出現。預防死鎖就是至少破壞這四個條件其中一項,即破壞“禁止搶佔”、破壞“持有等待”、破壞“資源互斥”和破壞“迴圈等待”。