• Java并发编程笔记1-竞争条件&初识原子类&可重入锁


    我们知道,在多线程访问一个共享变量的时候会发生安全问题。

    首先看下面例子:

    public class Counter {
        private int count;
    
        public void add(){
            try{
                for (int i = 0;i<200;i++){
                    Thread.sleep(100);
                    this.count++;
                    System.out.println(this.count);
                }
            }catch (Exception e ){
                e.printStackTrace();
            }
        }
    }
    public class Test {
        public static void main(String[] args) {
            final Counter counter = new Counter();
    
            new Thread(new Runnable() {
                public void run() {
                    counter.add();
                }
            }).start();
    
            new Thread(new Runnable() {
                public void run() {
                    counter.add();
                }
            }).start();
    
            new Thread(new Runnable() {
                public void run() {
                    counter.add();
                }
            }).start();
        }
    }

    运行结果如下:

    如果没发生线程对数据的赃操作,3个线程执行,结果会累加到600的,看上面结果明显没有,并且出现一下重复的数据。这是因为存在3个线程同时操作同一个数字,导致输出重复数字。

    解决办法:

      1.在方法上加上synchronized关键字。如下图:

      

    虽然结果不会出现重复,但是synchronized效率及其低下,运行速度明显被拖慢。原因是,for循环中的i变量是每个线程都有独自的局部变量,各不影响,但是每个线程都要排队进入这个方法,排队睡觉,这样就导致效率低下

      2.在竞争条件上加synchronized,我们知道各个线程其实竞争的是count这个成员变量。因此在此地方加即可。如下图:

      

    这样运行效率比方法一快了很多,因为省去了排队进入方法,排队睡觉。只需要排队取count值即可,这样效率比方法一快。

    注意上图中,输出语句并不是竞争条件,并不一定要放在synchroized里面,这里放在里面是为了让线程取到值自增后立即输出,这样输出就不会发生混论,不发生抢占输出问题,一样能累加到600,

    如果把输出放在synchronized外面会出现值有重复现象,因为累加后的值并没有立即输出,这样导致输出混乱,但仍然能加到600.知识输出不安全罢了。

      3.使用原子类型,比如将上面的代码的int count类型改成AtomicInteger count类型,我们知道获取count的值然后再自加个1是可能会出现问题的,也就是结果出现重复数字。AtomicInteger类型是以同步的方法解决这个问题的。如下图:

    结果如下图:

    可以看到数字的输出没有严格的排队,但是数据确实给你保证的了,就是完整的加到600.这也恰恰因为不是严格的进行排队,才是的这种方法比前面两种方法的效率大大改进。

    理论总结:synchronized是一种内部锁,就是所对象内部给我们提供的,因为每一个对象有一个状态变量,相当于一个锁,进入同步块,改变这个变量。别的线程进入之后就要判断这个变量有没有改变。

    一个线程获取它本生已经持有的锁,这是可以成功的。我们知道多个线程同时抢占同一个锁它们是失败的。因为它们之间是互斥的。但是呢,一个线程再次获取一个自己已经拿过的锁是可以成功的,那么它是能够成功的。

    看如下例子:

    public class Widget {
    
        public synchronized void doSth(){
    
        }
    
    }
    public class ChildWidget extends Widget {
        @Override
        public synchronized void doSth() {
            super.doSth();
        }
    }
    public class Test {
        public static void main(String[] args) {
    
            Widget w = new ChildWidget();
            w.doSth();
        }  
    }

    子类调用自己的方法的那个synchroized那个锁是w对象的这个锁,而在子类方法中的super.doSth()父类中的方法的synchronized的锁也是w对象的锁。因此不要被子类继承父类的方法中的锁所迷惑了。

    因此这种方式叫做内部锁的可重入机制,也叫可重入锁。

  • 相关阅读:
    程序是如何在 CPU 中运行的(二)
    程序是如何在 CPU 中运行的(一)
    结构体内存对齐解析
    如何以面向对象的思想设计有限状态机
    union 的概念及在嵌入式编程中的应用
    STM32 内存分配解析及变量的存储位置
    C语言如何实现继承及容器
    C语言指定初始化器解析及其应用
    pandas中的 where 和mask方法
    python中的类变量
  • 原文地址:https://www.cnblogs.com/huangjuncong/p/8505134.html
Copyright © 2020-2023  润新知