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关键字只有锁住相同的对象才能使不同线程互斥的进入临界区

发布了45 篇原创文章 · 获赞 113 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/baidu_41860619/article/details/105645605
今日推荐