java併發編程筆記3----詳解線程八鎖

原課程B站地址:全面深入學習java併發編程,中級程序員進階必會

基礎概念

臨界區:一段代碼塊內如果對共享資源的多線程讀寫操作,稱這段代碼塊爲臨界區

競態條件:多個線程在臨界區內執行,由於代碼的執行序列不同而導致結果無法預測,稱之爲發生了競態條件

爲了避免臨界區內競態條件發生,有兩種主要手段,阻塞式(採用synchronized,lock)和非阻塞式(原子變量)

而此篇介紹的線程八鎖指的是使用synchronized關鍵字對對象加鎖的8種情況,意在說明synchronized關鍵字的正確使用姿勢。

先說結論:

synchronized關鍵字只有鎖住相同的對象才能使不同線程互斥的進入臨界區

預備知識:
  • 1.寫在方法體的synchronized關鍵字與鎖住this對象的語法等價
  • 2.寫在靜態方法體上的synchronized關鍵字與鎖住本類對象的語法等價
import lombok.extern.slf4j.Slf4j;

/**
 *方法上的synchronized
 * */
@Slf4j(topic = "c.Test4")
public class Test4 {
    /**
     * test1_1和test1_2等價
     *
     * 鎖的是this對象
     * */
    public synchronized void test1_1(){

    }

    public void test1_2(){
        synchronized (this){

        }
    }

    /**
     * test2_1和test2_2等價
     *
     * 鎖住類對象
     * */
    public synchronized static void test2_1(){

    }

    public static void test2_2(){
        synchronized (Test4.class){

        }
    }
}

開始分析

第一種情況

分析:

  • 由於鎖住了同一個對象,所以線程會互斥執行

輸出:

  • 1 2 或者 2 1
@Slf4j(topic = "c.Test")
public class TestTemp {

    public static void main(String[] args) {
        Number1 number1 = new Number1();

        new Thread(() -> {
            log.debug("a start");
            number1.a();
        },"t1").start();
        new Thread(() -> {
            log.debug("b start");
            number1.b();
        },"t2").start();
    }
}

@Slf4j(topic = "c.Number1")
class Number1{
    public synchronized void a(){
        log.debug("1");
    }

    public synchronized void b(){
        log.debug("2");
    }
}
第二種情況

分析:

  • 第二種比第一種多了一個睡眠1秒,但仍然鎖住了同一個對象,所以線程依舊會互斥的執行

輸出:

  • a.一秒後輸出 1 2
  • b.先輸出 2 ,一秒後輸出 1
@Slf4j(topic = "c.Test")
public class TestTemp {

    public static void main(String[] args) {
        Number2 number2 = new Number2();

        new Thread(() -> {
            log.debug("a start");
            try {
                number2.a();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t1").start();

        new Thread(() -> {
            log.debug("b start");
            number2.b();
        },"t2").start();
    }
}

@Slf4j(topic = "c.Number2")
class Number2{
    public synchronized void a() throws InterruptedException {
        Thread.sleep(1000);
        log.debug("1");
    }

    public synchronized void b(){
        log.debug("2");
    }
}
第三種情況

分析:

  • 第三種比第二種多了一個方法c,c方法並沒有被synchronized修飾,所以t3線程會和其他線程併發執行,並不會互斥

輸出:

  • a.先輸出3,一秒後輸出 1 2
  • b.先輸出 3 2 或者 2 3 ,一秒後輸出 1
@Slf4j(topic = "c.Test")
@Slf4j(topic = "c.Test")
public class TestTemp {

    public static void main(String[] args) {
        Number3 number = new Number3();

        new Thread(() -> {
            log.debug("a start");
            try {
                number.a();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t1").start();

        new Thread(() -> {
            log.debug("b start");
            number.b();
        },"t2").start();

        new Thread(() -> {
            log.debug(" start");
            number.c();
        },"t3").start();
    }
}

@Slf4j(topic = "c.Number3")
class Number3{
    public synchronized void a() throws InterruptedException {
        Thread.sleep(1000);
        log.debug("1");
    }

    public synchronized void b(){
        log.debug("2");
    }

    public void c(){
        log.debug("3");
    }
}
第四種情況

分析:

  • 雖然方法體使用了synchronized關鍵字,但是新建了兩個對象,鎖住的是不同的對象,所以無法實現互斥的訪問

輸出:

  • 總是先輸出2,一秒後輸出 1
@Slf4j(topic = "c.Test")
public class TestTemp {

    public static void main(String[] args) {
        Number4 n1 = new Number4();
        Number4 n2 = new Number4();

        new Thread(() -> {
            log.debug("a start");
            try {
                n1.a();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t1").start();

        new Thread(() -> {
            log.debug("b start");
            n2.b();
        },"t2").start();
    }
}


@Slf4j(topic = "c.Number4")
class Number4{
    public synchronized void a() throws InterruptedException {
        Thread.sleep(1000);
        log.debug("1");
    }

    public synchronized void b(){
        log.debug("2");
    }
}
第五種情況

分析:

  • 雖然在主方法中只新建了一個對象,但是方法a()的synchronized是在靜態方法上,對Number5.class對象上鎖,而b()方法 是對this對象上鎖,對象不同,所以無法實現互斥訪問

輸出:

  • 總是先輸出2,一秒後輸出 1
@Slf4j(topic = "c.Test")
public class TestTemp {

    public static void main(String[] args) {
        Number5 n = new Number5();

        new Thread(() -> {
            log.debug("a start");
            try {
                n.a();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t1").start();

        new Thread(() -> {
            log.debug("b start");
            n.b();
        },"t2").start();
    }
}

@Slf4j(topic = "c.Number5")
class Number5{
    public static synchronized void a() throws InterruptedException {
        Thread.sleep(1000);
        log.debug("1");
    }

    public synchronized void b(){
        log.debug("2");
    }
}
第六種情況

分析:

  • synchronized關鍵字作用在靜態方法上,加鎖對象是Number6.class,則是同一個對象,能實現互斥訪問

輸出:

  • a. 先輸出 2 ,一秒後輸出 1
  • b. 一秒後輸出 1 2
@Slf4j(topic = "c.Test")
public class TestTemp {

    public static void main(String[] args) {
        Number6 n = new Number6();

        new Thread(() -> {
            log.debug("a start");
            try {
                n.a();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t1").start();

        new Thread(() -> {
            log.debug("b start");
            n.b();
        },"t1").start();
    }
}

@Slf4j(topic = "c.Number6")
class Number6{
    public static synchronized void a() throws InterruptedException {
        Thread.sleep(1000);
        log.debug("1");
    }

    public static synchronized void b(){
        log.debug("2");
    }
}
第七種情況

分析:

  • 很容易看出,加鎖的對象不同,不能實現互斥訪問

輸出:

  • 總是: 先輸出2,一秒後輸出 1
@Slf4j(topic = "c.Test")
public class TestTemp {

    public static void main(String[] args) {
        Number7 n1 = new Number7();
        Number7 n2 = new Number7();

        new Thread(() -> {
            log.debug("a start");
            try {
                n1.a();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t1").start();

        new Thread(() -> {
            log.debug("b start");
            n2.b();
        },"t2").start();
    }
}

@Slf4j(topic = "c.Number7")
class Number7{
    public static synchronized void a() throws InterruptedException {
        Thread.sleep(1000);
        log.debug("1");
    }

    public synchronized void b(){
        log.debug("2");
    }
}
第七種情況

分析:

  • 雖然在main方法內新建了兩個對象,但是a(),b()方法synchronized關鍵字均是加在靜態方法上,即鎖住的同樣都是Number8.class對象,對象相同,可以實現互斥訪問

輸出:

  • a. 先輸出2,一秒後輸出 1
  • b. 一秒後輸出 1 2
@Slf4j(topic = "c.Test")
public class TestTemp {

    public static void main(String[] args) {
        Number8 n1 = new Number8();
        Number8 n2 = new Number8();

        new Thread(() -> {
            log.debug("a start");
            try {
                n1.a();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t1").start();

        new Thread(() -> {
            log.debug("b start");
            n2.b();
        },"t2").start();
    }
}

@Slf4j(topic = "c.Number8")
class Number8{
    public static synchronized void a() throws InterruptedException {
        Thread.sleep(1000);
        log.debug("1");
    }

    public static synchronized void b(){
        log.debug("2");
    }
}

總結

以上就是線程八鎖的全部內容,作爲我的學習筆記記錄在此,也希望大家學習愉快。最後,再重複一遍結論:synchronized關鍵字只有鎖住相同的對象才能使不同線程互斥的進入臨界區

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章