• 并发编程(三)原子性(1)


    【认识原子性】:

    一个小程序认识原子性:

    package T05_YuanZiXing;
    
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class T00_00_IPlusPlus {
        private static long n = 0L;
    
        public static void main(String[] args) throws Exception {
    
            //Lock lock = new ReentrantLock();
    
            Thread[] threads = new Thread[100];
            CountDownLatch latch = new CountDownLatch(threads.length);
    
            for (int i = 0; i < threads.length; i++) {
                threads[i] = new Thread(() -> {
                    for (int j = 0; j < 10000; j++) {
                        n++;
                    }
                    latch.countDown();
                });
            }
    
            for (Thread t : threads) {
                t.start();
            }
    
            latch.await();
    
            System.out.println(n);
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    【最终输出】:
    在这里插入图片描述
    //启动了100个线程 , 每个线程将数字加到1W , 最后应该是100W。
    【问题所在】:

    在这里插入图片描述
    多个线程同时拿到了变量,同时++,然后同时写回去。

    【一些概念】:
    race condition => 竞争条件 , 指的是多个线程访问共享数据的时候产生竞争———上述程序中变量n即是共享数据。

    数据的不一致(unconsistency),并发访问之下产生的不期望出现的结果

    如何保障数据一致呢?–> 线程同步(线程执行的顺序安排好),

    monitor (管程) —> 锁

    critical section -> 临界区

    如果临界区执行时间长,语句多,叫做 锁的粒度比较粗,反之,就是锁的粒度比较细。

    【修改程序】:

            for (int i = 0; i < threads.length; i++) {
                threads[i] = new Thread(() -> {
                    for (int j = 0; j < 10000; j++) {
                        synchronized (T00_01_IPlusPlus.class) {
                            //lock.lock();
                            n++;
                            //lock.unlock();
                        }
                    }
                    latch.countDown();
                });
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    【原子操作】:

    原子操作的意思就是不能够中间被打断 , 只能作为一个整体 , 不能并发执行 。

    【什么样的语句是原子性的,什么样的语句不是原子性的呢?】:

    不管是Java、C、C++、高级语言、低级语言 最终一定都是要搞成机器语言的。机器语言翻译过来就是汇编语言。即便是汇编语言,它执行的任何一条指令都有可能被其他的线程所打断。
    【得查汇编手册】:

    CPU级别汇编,需要查询汇编手册!

    Java中的8大原子操作:(了解即可,无需背过)

    1. lock:主内存,标识变量为线程独占
    2. unlock:主内存,解锁线程独占变量
    3. read:主内存,读取内存到线程缓存(工作内存)
    4. load:工作内存,read后的值放入线程本地变量副本
    5. use:工作内存,传值给执行引擎
    6. assign:工作内存,执行引擎结果赋值给线程本地变量
    7. store:工作内存,存值到主内存给write备用
    8. write:主内存,写变量值

    【为何不用背呢?】:

    //正常的情况下 , 你判断不了的情况下。这句话到底是否具备原子性呢?——你给它上锁就可以了。

    【n++翻译成汇编指令】:

    在这里插入图片描述
    在这里插入图片描述
    1——将static的值给拿过来;
    2——扔到栈空间里面;
    3——将这个值++;
    4——将值放回去;
    5——返回。

    【 用上锁保证原子性 】:

    synchronized (T00_01_IPlusPlus.class) {
          n++;
    }
    
    • 1
    • 2
    • 3

    //大括号里面的所有操作都被当作了一个整体——不可打断。

    【上锁的本质(一)】:

    一句高级语言翻译成机器语言可能有好多句。需要保证这些操作不被打断。

    【上锁的本质(二)】:

    上锁的本质就是把并发编程序列化。
    【程序解释】:

    package T05_YuanZiXing;
    
    import Utils.SleepHelper;
    
    public class T00_01_WhatIsLock {
        private static Object o = new Object();
        
        public static void main(String[] args) {
            Runnable r = () -> {
                    System.out.println(Thread.currentThread().getName() + " start!");
                    SleepHelper.sleepSeconds(2);
                    System.out.println(Thread.currentThread().getName() + " end!");
            };
    
            for (int i = 0; i < 3; i++) {
                new Thread(r).start();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    【最终输出】:
    在这里插入图片描述
    //基本上是同时启动 , 2秒之后同时结束。

    【进行改进】:

    package T05_YuanZiXing;
    
    import Utils.SleepHelper;
    
    public class T00_01_WhatIsLock_02 {
        private static Object o = new Object();
    
    
        public static void main(String[] args) {
            Runnable r = () -> {
                synchronized (o) {
                    System.out.println(Thread.currentThread().getName() + " start!");
                    SleepHelper.sleepSeconds(2);
                    System.out.println(Thread.currentThread().getName() + " end!");
                }
            };
    
            for (int i = 0; i < 3; i++) {
                new Thread(r).start();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    【最终输出】:
    在这里插入图片描述
    //红线处会等待2S , 一共执行完需要6S的时间。

    【理解序列化】:


    原先上厕所,三个人同时一起完成,现在要排队一个一个完成了。

    【上锁的本质(三)】:

    【程序演示】:

    package T05_YuanZiXing;
    
    import Utils.SleepHelper;
    
    public class T00_02_SingleLockVSMultiLock {
        private static Object o1 = new Object();
        private static Object o2 = new Object();
        private static Object o3 = new Object();
    
        public static void main(String[] args) {
            Runnable r1 = () -> {
                synchronized (o1) {
                    System.out.println(Thread.currentThread().getName() + " start!");
                    SleepHelper.sleepSeconds(2);
                    System.out.println(Thread.currentThread().getName() + " end!");
                }
            };
    
            Runnable r2 = () -> {
                synchronized (o2) {
                    System.out.println(Thread.currentThread().getName() + " start!");
                    SleepHelper.sleepSeconds(2);
                    System.out.println(Thread.currentThread().getName() + " end!");
                }
            };
    
            Runnable r3 = () -> {
                synchronized (o3) {
                    System.out.println(Thread.currentThread().getName() + " start!");
                    SleepHelper.sleepSeconds(2);
                    System.out.println(Thread.currentThread().getName() + " end!");
                }
            };
    
            new Thread(r1).start();
            new Thread(r2).start();
            new Thread(r3).start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    【最终输出】:
    //一共花了2秒。

    【上锁的本质(四)】:

    synchronized是可以保障可见性的,一个线程结束了,会向主内存中做同步;
    synchronized保障了原子性。
    但是synchronized无法保障有序性。

    sync操作中的绿色区域部分的执行顺序是完全可以发生变化的。

    【有序性】:

    单线程保障最终一致性 , 它和锁没有关系。

    【 一些同步的基本概念_锁的粒度 】:

    【 monitor(管程) 】:

    在这里插入图片描述
    //后面跟的那一部分叫做monitor。
    在这里插入图片描述
    // o 即 monitor。

    【 critical section( 临界区 )】:

    当我持有这把锁的时候,我所执行的代码 , 它是临界区 , 是不能够两个同时在一起的,是必须顺序的,按照序列化执行的。
    【实际例子解释临界区】:

    在这里插入图片描述
    sync大括号所包含的叫做临界区——(图中的红色区域)。
    如果临界区执行的时间比较长,语句比较多 , 那我们就说锁的粒度比较粗 , 反之,就是锁的粒度比较细。

    【阶段小结】:

    【如何保障临界区操作的原子性呢?】:

    1)乐观锁
    2)悲观锁

  • 相关阅读:
    Unity SKFramework框架(二十三)、MiniMap 小地图工具
    【Mycat2实战】五、Mycat实现分库分表【实践篇】
    SpringMVC异常处理
    计算机网络基础
    网络的相关概念介绍
    【解决】linux磁盘扩容大全:新增磁盘、原磁盘扩容、home分区root分区扩容
    JQuery系列之事件
    Zookeeper 安装(Windows)
    Socket 服务端实例学习笔记
    关于虚析构函数的问题
  • 原文地址:https://blog.csdn.net/fuyuanduan/article/details/127979568