原課程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關鍵字只有鎖住相同的對象才能使不同線程互斥的進入臨界區