Java中关键字synchronized的使用方法详解

作者:超新星燃烧 时间:2022-04-14 06:18:54 

synchronized是Java里的一个关键字,起到的一个效果是“监视器锁”~~,它的功能就是保证操作的原子性,同时禁止指令重排序和保证内存的可见性!


public  class TestDemo {
   static  class Counter{
       public int count = 0;

public void add(){
           count++;
       }
   }
   public static void main(String[] args) throws InterruptedException {
       Counter counter = new Counter();
       Thread t1 = new Thread(){
           @Override
           public void run() {
               for (int i = 0; i < 50000; i++) {
                   counter.add();
               }
           }
       };

Thread t2 = new Thread(){
           @Override
           public void run() {
               for (int i = 0; i < 50000; i++) {
                   counter.add();
               }
           }
       };

//启动两个线程
       t1.start();
       t2.start();
       //等待两个线程结束
       t1.join();
       t2.join();
       System.out.println(counter.count);
   }
}

Java中关键字synchronized的使用方法详解

此时的线程就是不安全的,如何解决呢?

给我们的Counter对象里的add方法加上synchronized关键字,针对这个方法进行了加锁操作。进入代码块(调用方法)自动加锁,出了代码块(方法结束),自动解锁。


public  class TestDemo {
   static  class Counter{
       public int count = 0;

//修饰方法
        synchronized public void add{
            count++;
       }
   }
   public static void main(String[] args) throws InterruptedException {
       Counter counter = new Counter();
       Thread t1 = new Thread(){
           @Override
           public void run() {
               for (int i = 0; i < 50000; i++) {
                   counter.add();
               }
           }
       };

Thread t2 = new Thread(){
           @Override
           public void run() {
               for (int i = 0; i < 50000; i++) {
                   counter.add();
               }
           }
       };

//启动两个线程
       t1.start();
       t2.start();
       //等待两个线程结束
       t1.join();
       t2.join();
       System.out.println(counter.count);
   }
}

Java中关键字synchronized的使用方法详解

那么这里的代码是如何保证正确的呢?

使用synchronized 就相当于在我们执行的指令里又加入了2条新指令。

LOCK (加锁)

UNLOCK (解锁)

LOCK操作特性:只有一个线程能执行成功!如果第一个线程执行成功,第二个线程也尝试LOCK,就会阻塞等待,直到第一个线程执行UNLOCK 释放锁~

Java中关键字synchronized的使用方法详解

通过LOCK和UNLOCK 就把 LOAD ADD SAVE 这三个指令,给打包成了一个原子的操作(中间不能被打断,也不能被其他线程穿插)。

这里的加锁也是保证原子性的核心操作,所以线程里的没组指令就会顺序执行,不在穿插执行,就保证了线程1执行完之后再去执行线程2。

举个例子:

就好比张三和李四去ATM里去取钱,当张三进去取钱时,进去后就会锁门,李四就会在外面等待,直到张三取完钱出来后,李四在进去取钱。

synchronized 也会禁止编译器进行“内存可见性”和“指令重排序”的优化~ 同时程序运行的效率就会降低,
也会导致线程之间相互去等待,就涉及到系统的一些调度,也会引入一些时间成本。

synchronized修饰的对象有以下几种:

修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;


public class TestDemo{
   public void methond() {
       // 进入代码块会锁 this 指向对象中的锁;
       // 出代码块会释放 this 指向的对象中的锁
       synchronized (this) {

}
   }
   public static void main(String[] args) {
       TestDemo demo = new TestDemo();
       demo.methond();
   }
}

修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;


public class TestDemo{
   public synchronized void methond() {

}
   public static void main(String[] args) {
       TestDemo demo = new TestDemo();
       demo.methond();
       // 进入方法会锁 demo 指向对象中的锁;
       // 出方法会释放 demo 指向的对象中的锁
   }
}

修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;


public class TestDemo{
   public synchronized static void methond() {

}
   public static void main(String[] args) {
       methond();
       // 进入方法会锁 TestDemo.class 指向对象中的锁;
       //出方法会释放 TestDemo.class 指向的对象中的锁
   }
}

修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。


public class TestDemo{
   public static void methond() {
       // 进入代码块会锁 TestDemo.class 指向对象中的锁;
       //出代码块会释放 TestDemo.class 指向的对象中的锁
       synchronized (TestDemo.class) {

}
   }
   public static void main(String[] args) {
       TestDemo demo = new TestDemo();
       demo.methond();
   }
}

总结:

  • 无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;

  • 如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。

  • 每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。

  • 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。

拓展:


public  class TestDemo {

static   class Counter{
       public int count = 0;

public void add(){
              synchronized (this){
                  count++;
              }
       }
   }
   public static void main(String[] args) throws InterruptedException {
       Counter counter = new Counter();
       Thread t1 = new Thread(){
           @Override
           public void run() {
               for (int i = 0; i < 50000; i++) {
                   synchronized (counter){
                       counter.add();
                   }
               }
           }
       };
       Thread t2 = new Thread(){
           @Override
           public void run() {
               for (int i = 0; i < 50000; i++) {
                   synchronized (counter){
                       counter.add();
                   }
               }
           }
       };

//启动两个线程
       t1.start();
       t2.start();
       //等待两个线程结束
       t1.join();
       t2.join();
       System.out.println(counter.count);
   }
}

此时可以看出上述代码,加了两次锁,会发生什么呢?

Java中关键字synchronized的使用方法详解

Java中关键字synchronized的使用方法详解

但是运行代码发现程序依然正确运行?? 为什么

但是上述分析死锁的思路是对的

只是因为synchronized内部使用特殊手段来处理了这种情况 。

这样的操作特性我们叫做 可重入锁

synchronized 内部记录了当前这把锁是哪个线程持有的~

如果当前加锁线程和持有锁的线程是同一个线程~

此时就并不是真的进行“加锁操作”,而是把一个计数器加一;

如果后续该线程继续尝试获取锁,继续判定加锁线程和持有锁线程是不是同一个线程,只要是同一个线程,就不真的加锁,而是计数器+1;

如果该线程调用解锁操作,也不是立刻就解锁,而是计数器减1

直到计数器减成0了,才认为真的要“释放锁”,才允许其他线程来获取锁~~

来源:https://blog.csdn.net/wlm123666/article/details/119580473

标签:java,synchronized
0
投稿

猜你喜欢

  • 使用Docker搭建Java环境的步骤方法

    2022-09-07 16:03:59
  • Android中外接键盘的检测的实现

    2023-07-27 21:15:13
  • springboot通过spel结合aop实现动态传参的案例

    2022-01-26 01:00:47
  • 详解获取Spring MVC中所有RequestMapping以及对应方法和参数

    2023-12-09 21:29:17
  • Java集合TreeSet用法详解

    2023-11-10 22:53:34
  • 基于Java实现收发电子邮件功能

    2021-08-23 17:30:14
  • 新手初学Java集合框架

    2022-10-06 03:01:51
  • 解决java文件流处理异常 mark/reset not supported问题

    2022-10-05 14:28:08
  • SpringMVC请求流程源码解析

    2021-08-07 03:35:11
  • 浅谈Android开发中项目的文件结构及规范化部署建议

    2022-05-13 12:47:37
  • SpringBoot配置拦 截器实现过程详解

    2023-11-24 17:14:58
  • Java面试必备八股文整理

    2023-11-29 12:03:50
  • Java面向对象程序设计:类的定义,静态变量,成员变量,构造函数,封装与私有,this概念与用法详解

    2022-07-12 03:35:14
  • java 键盘输入一个数,输出数组中指定元素的示例

    2023-11-24 20:31:14
  • SpringBoot整合Swagger2的步骤详解

    2021-07-19 11:59:49
  • 如何将默认的maven仓库改为阿里的maven仓库

    2022-09-30 14:16:31
  • java swing实现简单的五子棋游戏

    2022-04-16 22:10:10
  • Java图论进阶之最小生成树算法详解

    2022-05-28 12:17:54
  • 基于Ant路径匹配规则AntPathMatcher的注意事项

    2021-11-19 03:58:16
  • java连接sql server 2008数据库代码

    2023-05-27 10:40:01
  • asp之家 软件编程 m.aspxhome.com