文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2024-04-02 19:55

关注

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

总结:

拓展:


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用法内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯