深入浅出synchronized(一)

简介: 深入浅出synchronized(一)

介绍


多个线程对共享资源的进行读写操作的时候,由于cpu指令执行的顺序不同,导致每次的结果可能不一样。为了解决这一问题,可以用加锁的方式解决。


临界区和竞态条件


临界区: 一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区。

竞态条件: 多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件。

为了避免临界区的竞态条件发生,有多种手段可以达到目的。

  • 阻塞式的解决方案:synchronized,Lock
  • 非阻塞式的解决方案:原子变量


synchronized加锁


synchronized让同一时刻至多只有一个线程能持有【对象锁】,其它线程再想获取这个【对象锁】时就会阻塞住。这样就能保证拥有锁的线程可以安全的执行临界区内的代码,不用担心线程上下文切换,保证了临界区代码的原子性。

可以做如下类比:

  • synchronized(对象)中的对象好比是房间,每个房间只有一把钥匙,多个线程相当于人,人需要有钥匙(持有锁)才能进入房间。
  • 当线程t1执行临界区时,相当于第一个人拿到了钥匙进入房间做事情。
  • 当线程t2也运行到了临界区时,相当于第二个人来到房间门口,由于没有钥匙,只能阻塞住。
  • 如果在这中间线程t1的cpu时间片不幸用完,它会被踢出门外(不要以为持有了锁,它就会一直执行下去),t1仍然拿着钥匙,下次t1再次被cpu分配到时间片时,它继续开门进入工作。
  • 当t1执行完sychronized的代码,它会开门,交出钥匙,同时唤醒其他阻塞的线程,让他们竞争钥匙,进门工作。


synchronized实践


synchronized加在方法上,相当于对当前对象加锁,synchronized加在静态方法上,相当于对类对象加锁。进行synchronized的加锁分析时,主要看不同的线程是否持有同一把锁,或者拿上面的比方来说,是不是进入同一个房间。如果是类对象,那么一个类就是一个房间,对象的话,一个对象是同一个房间。


synchronized加在方法上


public class SychronizedTest {
    public static void main(String[] args) {
        Number n1 = new Number();
        new Thread(()->{ n1.a(); }).start();
        new Thread(()->{ n1.b(); }).start();
    }
}
@Slf4j
class Number{
    @SneakyThrows
    public synchronized void a() {
        log.debug("a start......");
        Thread.sleep(1000);
        log.debug("a end......");
    }
    @SneakyThrows
    public synchronized void b() {
        log.debug("b start......");
        Thread.sleep(1000);
        log.debug("b end......");
    }
}

结果:

1671089868554.jpg

synchronized加在方法上相当于对当前这个对象加锁,他们想要进的是同一个房间(同一把锁)。


synchronized加在其中一个方法上


public class SychronizedTest2 {
    public static void main(String[] args) {
        Number2 n1 = new Number2();
        new Thread(() -> {
            n1.a();
        }).start();
        new Thread(() -> {
            n1.b();
        }).start();
    }
}
@Slf4j
class Number2 {
    @SneakyThrows
    public synchronized void a() {
        log.debug("a start......");
        Thread.sleep(1000);
        log.debug("a end......");
    }
    @SneakyThrows
    public void b() {
        log.debug("b start......");
        Thread.sleep(1000);
        log.debug("b end......");
    }
}

结果:

1671089885351.jpg

方法a加了锁,方法b未加锁,所以两个线程没有持有同一个锁,所以不会阻塞。


synchronized加在不同锁上


public class SychronizedTest3 {
    public static void main(String[] args) {
        Number3 n1 = new Number3();
        Number3 n2 = new Number3();
        new Thread(() -> {
            n1.a();
        }).start();
        new Thread(() -> {
            n2.b();
        }).start();
    }
}
@Slf4j
class Number3 {
    @SneakyThrows
    public synchronized void a() {
        log.debug("a start......");
        Thread.sleep(1000);
        log.debug("a end......");
    }
    @SneakyThrows
    public synchronized void b() {
        log.debug("b start......");
        Thread.sleep(1000);
        log.debug("b end......");
    }
}

结果:

1671089902248.jpg

本质上,两个线程是在不同的两个锁,也就是说进入的两个不同的房间,所以不会阻塞。


synchronized加在静态方法上


public class SychronizedTest4 {
    public static void main(String[] args) {
        Number4 n1 = new Number4();
        new Thread(() -> {
            n1.a();
        }).start();
        new Thread(() -> {
            n1.b();
        }).start();
    }
}
@Slf4j
class Number4 {
    @SneakyThrows
    public synchronized static void a() {
        log.debug("a start......");
        Thread.sleep(1000);
        log.debug("a end......");
    }
    @SneakyThrows
    public synchronized void b() {
        log.debug("b start......");
        Thread.sleep(1000);
        log.debug("b end......");
    }
}

结果:

1671089916040.jpg

synchronized加在静态方法上,相当于对这个类对象加锁,和加在方法上不是同一个锁,他们进入的不是同一个房间,所以不会阻塞。


synchronized全加在静态方法上


public class SychronizedTest5 {
    public static void main(String[] args) {
        Number5 n1 = new Number5();
        new Thread(() -> {
            n1.a();
        }).start();
        new Thread(() -> {
            n1.b();
        }).start();
    }
}
@Slf4j
class Number5 {
    @SneakyThrows
    public synchronized static void a() {
        log.debug("a start......");
        Thread.sleep(1000);
        log.debug("a end......");
    }
    @SneakyThrows
    public synchronized static void b() {
        log.debug("b start......");
        Thread.sleep(1000);
        log.debug("b end......");
    }
}

结果:

1671089930128.jpg

两个线程加的是同一个锁,这个Numer4的这个类对象的锁,所以会阻塞。

目录
相关文章
|
2月前
|
存储 监控 安全
吃透synchronized实现原理
吃透synchronized实现原理
46 0
|
2月前
|
安全 Java
Java并发编程:Synchronized及其实现原理
Java并发编程:Synchronized及其实现原理
29 4
|
24天前
|
存储 Java C++
Synchronized底层原理
Synchronized底层原理
|
12天前
|
存储 Java
Java并发编程 Synchronized原理
Java并发编程 Synchronized原理
12 0
|
2月前
|
Java API
【并发编程】吃透Synchronized
【并发编程】吃透Synchronized
12 1
|
2月前
|
存储 Java 编译器
synchronized原理进阶
synchronized原理进阶
44 1
|
2月前
|
安全 Java
并发编程之synchronized的详细解析
并发编程之synchronized的详细解析
24 0
阅读完synchronized和ReentrantLock的源码后,我竟发现其完全相似
阅读完synchronized和ReentrantLock的源码后,我竟发现其完全相似
|
存储 安全 Java
synchronized原理详解(通俗易懂超级好)
当系统检查到锁是重量级锁之后,会把等待想要获得锁的线程进行阻塞,被阻塞的线程不会消耗cpu。但是阻塞或者唤醒一个线程时,都需要操作系统来帮忙,这就需要从用户态转换到内核态,而转换状态是需要消耗很多时间的,有可能比用户执行代码的时间还要长。
synchronized原理详解(通俗易懂超级好)
|
存储 安全 Java
synchronized 的底层原理
synchronized 的底层是通过 Java 中的监视器锁(monitor)来实现的。每个 Java 对象都有一个与之对应的监视器锁,当一个线程获取了该对象的监视器锁,就可以执行 synchronized 代码块或方法。其他线程只能等待该线程释放锁,才能获取该对象的监视器锁并执行 synchronized 代码块或方法。
84 0
synchronized 的底层原理

热门文章

最新文章