• synchronized关键字的作用


    目录

    一、互斥

    1、修饰普通成员方法

    2、修饰代码块

    3、修饰静态成员方法

    4、总结:

    二、可重入


    一、互斥

            多个线程如果同时针对一个对象进行加锁时(进入synchronized修饰的代码块相当于加锁,退出synchronized修饰的代码块相当于解锁),会发生“锁竞争”,但只有一个线程(先进行加锁操作的)能够加锁成功,其他线程就会阻塞等待,这就是synchronized的互斥

            举个例子,就好比几个兄弟同时去抢占厕所的一个坑位,即“所(锁)竞争”先抢到的先上厕所,没抢到的兄弟只能在外面等,当第一个兄弟上完厕所出来后,其他几个兄弟会再次进行“所(锁)竞争”,并不会遵循先来后到的规则

            如果多个线程针对不同的对象进行加锁时,不会发生锁竞争:线程1针对A对象加锁,线程2针对B对象加锁,此时不会发生“锁竞争”,也就不会产生阻塞等待。好比几个兄弟各自使用不同的坑位:

            synchronized可以通过修饰普通成员方法、代码块、静态成员方法进行加锁操作,针对同一个对象进行加锁就会产生互斥,针对不同对象加锁就不会互斥。

    1、修饰普通成员方法

             synchronized修饰普通成员方法时,被加锁的对象(即锁对象)为当前对象本身,相当于this

    1. public class Test {
    2. public static int count;
    3. //synchronized修饰普通成员方法
    4. public synchronized void increase(){
    5. count++;
    6. }
    7. public static void main(String[] args) throws InterruptedException {
    8. Test test = new Test();
    9. //让两个线程同时进行50000次count++操作
    10. Thread t1 = new Thread(() -> {
    11. for (int i = 0; i < 50000; i++) {
    12. test.increase();
    13. }
    14. });
    15. Thread t2 = new Thread(() -> {
    16. for (int i = 0; i < 50000; i++) {
    17. test.increase();
    18. }
    19. });
    20. t1.start();
    21. t2.start();
    22. t1.join();
    23. t2.join();
    24. System.out.println("count = " + Test.count);
    25. }
    26. }

            上述代码中的两个线程,都是通过test对象来调用被synchronized修饰的increase方法,那么两个线程的锁对象都是test对象,因此两个线程之间就会产生锁竞争,一个线程在调用increase方法进行count++操作时,另一个线程只能阻塞等待,这样就相当于把count++这个不是原子的操作,打包成一个原子操作,从而使count得到正确的结果:

    2、修饰代码块

            synchronized修饰代码块时,括号中填入的对象就是被加锁的对象,这个对象可以是任意类型的对象

    1. public class Test {
    2. public static int count;
    3. public void increase(){
    4. //synchronized修饰代码块
    5. synchronized(this){
    6. count++;
    7. }
    8. }
    9. public static void main(String[] args) throws InterruptedException {
    10. Test test = new Test();
    11. //让两个线程同时进行50000次count++操作
    12. Thread t1 = new Thread(() -> {
    13. for (int i = 0; i < 50000; i++) {
    14. test.increase();
    15. }
    16. });
    17. Thread t2 = new Thread(() -> {
    18. for (int i = 0; i < 50000; i++) {
    19. test.increase();
    20. }
    21. });
    22. t1.start();
    23. t2.start();
    24. t1.join();
    25. t2.join();
    26. System.out.println("count = " + Test.count);
    27. }
    28. }

            上述代码中,synchronized的锁对象为this,两个线程也都是通过test对象调用的increase方法,所以this指向的都是test对象,即两个线程还是在针对同一个对象进行加锁,因此也会发生锁竞争,说明当前线程也是安全的: 

    3、修饰静态成员方法

            synchronized修饰静态成员方法时,锁对象相当于是一个类对象,类对象只有一份,所以在多线程环境下是安全的。

    1. public class Test {
    2. public static int count;
    3. //synchronized修饰静态方法
    4. public static synchronized void increase(){
    5. count++;
    6. }
    7. public static void main(String[] args) throws InterruptedException {
    8. //让两个线程同时进行50000次count++操作
    9. Thread t1 = new Thread(() -> {
    10. for (int i = 0; i < 50000; i++) {
    11. increase();
    12. }
    13. });
    14. Thread t2 = new Thread(() -> {
    15. for (int i = 0; i < 50000; i++) {
    16. increase();
    17. }
    18. });
    19. t1.start();
    20. t2.start();
    21. t1.join();
    22. t2.join();
    23. System.out.println("count = " + Test.count);
    24. }
    25. }

    4、总结:

    (1)使用synchronized加锁的目的是保证线程安全;

    (2)多个线程的锁对象为同一个对象时,会产生锁竞争,把不是原子的操作打包成一个原子操作;多个线程的锁对象为不同对象时,不会产生锁竞争。

    二、可重入

    一个线程如果加锁两次是否会把自己锁死呢?,比如下面这段代码:

    1. public synchronized void func(){
    2. synchronized (this){
    3. count++;
    4. }
    5. }

            进入synchronized修饰的func方法时,第一次加锁可以成功,但是进入synchronized修饰的代码块时,也需要进行加锁,但是第一次加的锁还没有释放,可以进行第二次加锁吗?而想要释放第一次加的锁,又必须进行第二次加锁、解锁操作,此时就产生了矛盾,这样的锁被称为不可重入锁

      

            Java中的synchronized可重入锁,不会出现一个线程把自己锁死的问题,所以上面的代码完全是OK的。

    可重入锁的内部,包含“线程持有者”和“计数器”两个信息:

    线程持有者:记录第一次加锁的线程,如果该线程在释放第一次加的锁之前再次进行加锁,那么仍然可以加锁成功,并让计数器自增一次。

    计数器:加锁时自增一次,解锁时自减一次,只有当计数器递减为0的时候,线程进行解锁操作才会真正释放锁(其他线程才能获取到这个锁)。

  • 相关阅读:
    BUUCTF reverse wp 76 - 80
    横向的Excel输出为pdf自动分成两页怎么办?不分页,铺满整张纸的方法来了
    前端暴露后端接口及传参方式总结~
    深入了解Python中的浮点数、自动转换、强制转换与增强赋值运算符
    提升Mac运行速度的十大小技巧,你用过几个?
    Android Studio中Touch监听器的使用
    slamplay:用C++实现的SLAM工具集
    Linux设备树详解
    Tomcat 启动闪退问题解决方法
    面试突击72:输入URL之后会执行什么流程?
  • 原文地址:https://blog.csdn.net/m0_67683346/article/details/126798890