synchronized案例与原理分析

本文通过一个并发编程案例分析了未使用synchronized时可能出现的有序性、原子性和可见性问题,并展示了如何通过synchronized关键字修复这些问题,确保线程安全。优化后的代码利用synchronized保证了数据读写的有序性、原子性和可见性,防止并发冲突。
摘要由CSDN通过智能技术生成

synchronized使用案例

并发问题的代码如下:

​
private static  volatile int j=100;
    public static void main(String[] args) throws InterruptedException {
        updateJ updateJ = new updateJ(2, 100);
        updateJ updateJ1 = new updateJ(-3, 1000);
        updateJ.start();
        updateJ1.start();
    }
    private static class  updateJ extends Thread{
        int i=0;
        long sleepTime=0;
        public updateJ(int i,long executeTime){
            this.i=i;
            this.sleepTime=executeTime;
        }
        @Override
        public void run() {
                int y = getJ();
                System.out.println(Thread.currentThread().getName()+"开始更新数据库中的j,此时数据中  j为:"+y);
                j =y-i;
                System.out.println(Thread.currentThread().getName()+"第一次将数据库中的j更新写成了:"+getJ());
                try {
                    Thread.sleep(sleepTime);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"第二次从数据库中读取j成了:"+getJ());
        }
        private int getJ(){
            return j;
        }
    }

​

结果分析

image.png

  • 可以看到结果,线程0和线程1 可以理解为两个人先后去调用接口更新数据中的数据j. 线程0对j 减二, 线程1对j加3
  • 有序性上来说,假设对于一个线程在对数据库中的j变量进行修改前,先进行读取,然后进行修改,是要保证其他线程不能读取和修改的.否则会导致多个线程第一次读取的j都是一样的. 比如上图上显示的两个线程都同时读到了j为100.这就破坏了有序性.因为其他线程有可能还要对读到的数据进行修改.此时如果不按照顺序,那么线程各自就会对初始读到的数据进行修改了.
  • 原子性来说,线程0在进行写的同时,线程1按理是不允许进行读写的.但是上图,不仅读了,还对其进行了修改.导致线程0修改的失效了.
  • 可见性来说,虽然j变量加了volatile关键字,但是因为前面的有序性和原子性被破坏.导致即使保证了可见性也没有意义了.
  • 这就是典型的并发情况下的读写有序性,原子性.和可见性问题.

优化后代码

对上面案例进行优化如下:

private static  volatile int j=100;
    public static void main(String[] args) throws InterruptedException {
        updateJ updateJ = new updateJ(2, 1000);
        updateJ updateJ1 = new updateJ(-3, 1000);
        updateJ.start();
        updateJ1.start();
    }
    private static class  updateJ extends Thread{
        int i=0;
        long sleepTime=0;
        public updateJ(int i,long executeTime){
            this.i=i;
            this.sleepTime=executeTime;
        }
        @Override
        public void run() {
            synchronized (updateJ.class){
                System.out.println(Thread.currentThread().getName()+"开始更新数据库中的j,此时数据中  j为:"+getJ());
                j =getJ()-i;

                System.out.println(Thread.currentThread().getName()+"第一次将数据库中的j更新写成了:"+getJ());
                try {
                    Thread.sleep(sleepTime);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"第二次从数据库中读取j成了:"+getJ());
            }
        }
        private synchronized int getJ(){
            return j;
        }
    }

结果分析

image.png

 

  • 有序性上来说,线程0和线程1读取数据是有顺序的.如果线程0先获取到synchronized锁.就先执行读写
  • 原子性来说,多个线程抢占锁.只有一个线程抢的到.可以对变量进行操作.
  • 可见性来说,线程0更新完了数据之后,线程1再去获取到锁之后是最新的数据.

总结

synchronized可以保证多个线程执行的顺序,保证了有序性,在获取变量的时候会将本地内存删掉.然后从主内存重新获取,保证了可见性, 线程读写完全隔离,从而保证了原子性.

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值