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

 更新时间:2021年08月13日 09:23:20   作者:超新星燃烧  
synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块,下面这篇文章主要给大家介绍了关于Java中synchronized使用的相关资料,需要的朋友可以参考下

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);
    }
}

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

给我们的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);
    }
}

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

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

LOCK (加锁)

UNLOCK (解锁)

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

通过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);
    }
}

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

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

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

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

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

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

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

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

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

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

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

总结

到此这篇关于Java中synchronized使用的文章就介绍到这了,更多相关Java中synchronized用法内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java中文件的操作与输入输出流举例详解

    Java中文件的操作与输入输出流举例详解

    Java语言的输入输出功能是十分强大而灵活的,下面这篇文章主要给大家介绍了关于Java中文件的操作与输入输出流的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2023-12-12
  • java前后端使用ajax数据交互问题(简单demo)

    java前后端使用ajax数据交互问题(简单demo)

    这篇文章主要介绍了java前后端使用ajax数据交互问题(简单demo),具有很好的参考价值,希望对大家有所帮助。
    2023-06-06
  • Java优雅的处理金钱问题(BigDecimal)

    Java优雅的处理金钱问题(BigDecimal)

    本文主要介绍了Java优雅的处理金钱问题(BigDecimal),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-06-06
  • SpringBoot如何实现同域SSO(单点登录)

    SpringBoot如何实现同域SSO(单点登录)

    单点登录(SingleSignOn,SSO),就是通过用户的一次性鉴别登录。即在多个应用系统中,只需要登录一次,就可以访问其他相互信任的应用系统,本文将介绍SpringBoot如何实现同域SSO(单点登录)
    2021-05-05
  • spring boot 如何请求后缀匹配

    spring boot 如何请求后缀匹配

    这篇文章主要介绍了spring boot 请求后缀匹配的操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • JSON的String字符串与Java的List列表对象的相互转换

    JSON的String字符串与Java的List列表对象的相互转换

    这篇文章主要介绍了JSON的String字符串与Java的List列表对象的相互转换,如果在浏览器端JSON是list则转为string结构来处理,需要的朋友可以参考下
    2016-04-04
  • Java内存释放实现代码案例

    Java内存释放实现代码案例

    这篇文章主要介绍了Java内存释放实现代码案例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-12-12
  • Java中的static关键字你了解多少

    Java中的static关键字你了解多少

    这篇文章主要为大家详细介绍了Java中的static关键字,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-02-02
  • 在SpringBoot中使用HATEOAS的方法

    在SpringBoot中使用HATEOAS的方法

    这篇文章主要介绍了在SpringBoot中使用HATEOAS的方法,HATEOAS是实现REST规范的一种原则,通过遵循HATEOAS规范,可以解决我们实际代码实现的各种个问题,下文更多相关介绍,需要的小伙伴可以参考一下
    2022-05-05
  • java 线程池存在的意义

    java 线程池存在的意义

    这篇文章主要介绍了java线程池存在的意义,通过多线程案例模拟锁的产生的情况展开对主题的详细介绍,具有一定的参考价值,需要的朋友可以参考一下
    2022-06-06

最新评论