zoukankan      html  css  js  c++  java
  • 并发编程与高并发学习笔记一

    一,线程安全性
    1.定义:
    当多个线程访问某个类时,不管运行时环境采用 任何调度方式 或者这些进程将如何交替执行,并且在主调代码中
    不需要任何额外的同步或协同,这个类都能表现出 正确的行为,那么称这个类是线程安全的
    2.线程安全性体现在三个方面:
    原子性:提供了互斥访问,同一时刻只能有一个线程来对他操作
    可见性:一个线程对主内存的修改可以及时被其他线程观察到
    有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序的存在,该观察结果一般杂乱无序
    3.并发模拟代码:
    //并发模拟代码
    public class CountExample {
        //请求总数
        public static int clientTotal = 5000;
        //同时并发执行的线程数
        public static int threadTotal = 200;
        //全局变量
        public static int count = 0;
        public static void main(String[] args) {
            ExecutorService executorService = Executors.newCachedThreadPool();
            //信号灯,同时允许执行的线程数
            final Semaphore semaphore = new Semaphore(threadTotal);
            //计数器,
            final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
    
            for (int i = 0; i < clientTotal; i++) {
                executorService.execute(()->{
                    try {
                        //获取信号灯,当并发达到一定数量后,该方法会阻塞而不能向下执行
                        semaphore.acquire();
                        add();
                        //释放信号灯
                        semaphore.release();
                    }catch (InterruptedException e){
                        System.out.println("exception");
                        e.printStackTrace();
                    }
                    //闭锁,每执行一次add()操作,请求数就减一
                    countDownLatch.countDown();
                });
            }
    
            //等待上面的线程都执行完毕,countDown的值减为0,然后才向下执行主线程
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //打印count的值
            System.out.println("count:"+count);
    
            //关闭线程池
            executorService.shutdown();
    
        }
    
        private static void add(){
            count++;
        }
    }
    
    



    .原子性-Atomic包
    1.AtomicInteger类中提供了incrementAndGet方法;
    public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }
    2.incrementAndGet方法又调用了Unsafe类的getAndAddInt方法
    public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
    var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
    }
    3.getAndAddInt方法又是如何保证原子性的呢?该方法调用了compareAndSwapInt方法(就是我们说的CAS)
    public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
    compareAndSwapInt方法是native方法,这个方法是java底层的方法(不是通过java实现的)
    4.原理解析:

    public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
    var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
    }
    Object var1:传进来的AtomicInteger对象
    long var2:是传进来的值,当前要进行加一的值 (比如要进行2+1的操作, var2就是2)
    int var4:是传进来的值,进行自增要加上的值 (比如要进行2+1的操作, var4就是1)
    int var5:是通过调用底层的方法this.getIntVolatile(var1, var2);得到的底层当前的值
    while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)):
    通过do{} while()不停的将当前对象的传进来的值和底层的值进行比较,
    如果相同就将底层的值更新为:var5+var4(加一的操作),
    如果不相同,就重新再从底层取一次值,然后再进行比较,这就是CAS的核心。
    帮助理解:
    AtomicInteger里面存的值看成是工作内存中的值
    把底层的值看成是主内存中的值。在多线程中,工作内存中的值和主内存中的值会出现不一样的情况。

    线程安全的代码:
    //线程安全的并发
    public class CountExample2 {
        //请求总数
        public static int clientTotal = 5000;
        //同时并发执行的线程数
        public static int threadTotal = 200;
        //全局变量
        public static AtomicInteger count = new AtomicInteger(0);
        public static void main(String[] args) {
            ExecutorService executorService = Executors.newCachedThreadPool();
            //信号灯,同时允许执行的线程数
            final Semaphore semaphore = new Semaphore(threadTotal);
            //计数器,
            final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
            for (int i = 0; i < clientTotal; i++) {
                executorService.execute(()->{
                    try {
                        //获取信号灯,当并发达到一定数量后,该方法会阻塞而不能向下执行
                        semaphore.acquire();
                        add();
                        //释放信号灯
                        semaphore.release();
                    }catch (InterruptedException e){
                        System.out.println("exception");
                        e.printStackTrace();
                    }
                    //闭锁,每执行一次add()操作,请求数就减一
                    countDownLatch.countDown();
                });
            }
    
            //等待上面的线程都执行完毕,countDown的值减为0,然后才向下执行主线程
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //打印count的值
            System.out.println("count:"+count.get());
            //关闭线程池
            executorService.shutdown();
        }
        private static void add(){
            //count++;
            count.incrementAndGet();
        }
    }
     
    5.还有AotimcLong和LongAddr
    他俩的区别没听懂???????

    6.CAS中的ABA问题
    描述:在CAS操作时,其他线程将变量的值从A改成了B,然后又将B改回了A。
    解决思路:每次变量改变时,将变量的版本号加1,只要变量被修改过,变量的版本号就会发生递增变化
    使用的类:AtomicStampedReference,
    调用compareAndSet方法:
    public boolean compareAndSet(V expectedReference,
    V newReference,
    int expectedStamp,
    int newStamp) {
    Pair<V> current = pair;
    return
    expectedReference == current.reference &&
    expectedStamp == current.stamp &&
    ((newReference == current.reference &&
    newStamp == current.stamp) ||
    casPair(current, Pair.of(newReference, newStamp)));
    }
    stamp是每次更新时就维护的, 通过对比来判断是不是一个版本号,expectedStamp == current.stamp

    7.AtomicBoolean
    代码:
    //让某一段代码只执行一次
    public class CountExample3 {
        //请求总数
        public static int clientTotal = 5000;
        //同时并发执行的线程数
        public static int threadTotal = 200;
        public static AtomicBoolean isHappened = new AtomicBoolean(false);
        public static void main(String[] args) {
            ExecutorService executorService = Executors.newCachedThreadPool();
            //信号灯,同时允许执行的线程数
            final Semaphore semaphore = new Semaphore(threadTotal);
            //计数器,
            final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
            for (int i = 0; i < clientTotal; i++) {
                executorService.execute(()->{
                    try {
                        //获取信号灯,当并发达到一定数量后,该方法会阻塞而不能向下执行
                        semaphore.acquire();
                        test();
                        //释放信号灯
                        semaphore.release();
                    }catch (InterruptedException e){
                        System.out.println("exception");
                        e.printStackTrace();
                    }
                    //闭锁,每执行一次add()操作,请求数就减一
                    countDownLatch.countDown();
                });
            }
    
            //等待上面的线程都执行完毕,countDown的值减为0,然后才向下执行主线程
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //打印count的值
            System.out.println("isHappened:"+isHappened.get());
            //关闭线程池
            executorService.shutdown();
        }
    
        private static void test(){
            //如果是false,就更新为true
           if (isHappened.compareAndSet(false,true)){
               System.out.println("execute");
           }
        }
    }

    .原子性-锁
    1.synchronized:依赖JVM
    (1).synchronized修饰的对象有四种:
    修饰代码块:作用范围是大括号括起来的代码,作用于调用的对象
    修饰方法:作用范围是整个方法,作用于调用的对象
    修饰静态方法:作用范围是整个静态的方法,作用于这个类的所有对象
    修饰类:作用范围是synchronized括号括起来的部分,作用于这个类的所有对象

    2.Lock:依赖特殊的cpu指令,代码实现,ReentrantLock

    3.原子性-对比
    synchronized:不可中断锁,适合竞争不激烈,可读性好.(竞争激烈时性能下降非常快)
    Lock:可中断锁(调用unlock即可),多样化同步,竞争激烈时能维持常态
    Atomic:竞争激烈时呢能维持常态,比Lock性能好,只能同步一个值

    .可见性
    1.定义:一个线程对主内存的修改可以及时被其他线程观察到
    2.导致共享变量在线程间不可见的原因:
    线程交叉执行
    重排序结合交叉执行
    共享变量更新后的值没有在工作内存与主内存之间及时更新
    3.可见性-synchronized
    java内存模型(JMM)关于synchronized的两条规定:
    A.线程解锁前,必须把共享变量的最新值刷新到主内存
    B.线程加锁时,将清空工作内存中的共享变量的值,从而在使用共享变量时,需要从主内存中重新读取最新的值(注意:
    加锁和解锁是同一把锁)
    4.可见性-volatile
    (1).通过加入内存屏障和禁止重排序优化来实现
    A.对volatile变量写操作时,会在写操作后加入一条store屏障指令,将本地内存中的共享变量值刷新到主内存
    B.对volatile变量读操作时,会在读操作前加入一条load屏障指令,从主内存中读取共享变量
    通俗地说就是,volatile变量每次被线程访问时,都强迫从主内存读取该变量的值,而当该变量发生变化时,又会强迫线程
    将最新的值刷新到主内存,这样,任何时候不同的线程总能看到该变量的最新值
    (2).内存屏障是如何禁止重排序的呢?
    图示:
    (3).注意:volatile可不具有原子性,用它修饰变量是没有用的
    比如:public static volatile int count = 0;这样的变量虽然用volatile修饰了,但是并不是线程安全的
    //volatile并不保证原子性
    public class VolatileExample {
    
        //请求总数
        public static int clientTotal = 5000;
        //同时并发执行的线程数
        public static int threadTotal = 200;
        //全局变量
        public static volatile int count = 0;
    
        public static void main(String[] args) {
            ExecutorService executorService = Executors.newCachedThreadPool();
            //信号灯,同时允许执行的线程数
            final Semaphore semaphore = new Semaphore(threadTotal);
            //计数器,
            final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
    
            for (int i = 0; i < clientTotal; i++) {
                executorService.execute(()->{
                    try {
                        //获取信号灯,当并发达到一定数量后,该方法会阻塞而不能向下执行
                        semaphore.acquire();
                        add();
                        //释放信号灯
                        semaphore.release();
                    }catch (InterruptedException e){
                        System.out.println("exception");
                        e.printStackTrace();
                    }
                    //闭锁,每执行一次add()操作,请求数就减一
                    countDownLatch.countDown();
                });
            }
    
            //等待上面的线程都执行完毕,countDown的值减为0,然后才向下执行主线程
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //打印count的值
            System.out.println("count:"+count);
    
            //关闭线程池
            executorService.shutdown();
    
        }
    
        private static void add(){
            count++;
            /*
            count++操作其实有三步:
                获取count
                +1操作
                将count刷新到主内存
            当有多个线程同时执行count++时,因为上面有三步,所以保证不了线程的安全性
             */
        }
    }
    
    
    

    (4).volatile适用场景一:
    使用volatile必须具备两个条件:
    A.对变量的写操作,不依赖于当前值
    B.该变量没有包含在具有其他变量的式子中
    所以,volatile特别适合作为状态标记量
    例子:
    volatile boolean inited = false; //inited用来标识线程初始化是否完成
    //线程一: 线程一负责初始化
    context = loadContext(); //初始化操作
    init = true;//初始化完成后修改状态

    //线程二: 线程二必须保证初始化完成才能执行
    while(!inited){//所以线程二不断的判断是否为inited是否true,只有为true时,线程二才开始执行
    sleep();
    }
    doSomethingWithConfig(context);
    (5).volatile适用场景二:
    用来双重检测,单例模式中的双重检测机制。
    .有序性
    1.定义:
    java内存模型中,允许编译器和处理器对指令进行重排序,重排序过程是不会影响到单线程程序的执行的,但是会影响到
    多线程并发执行的正确性
    2.保证有序性的方式有哪些?
    volatile,synchronized,Lock
    3.happens-before原则 (即先行发生原则),共有8个规则:
    程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
    锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作
    volatile规则L:对一个变量的写操作先行发生于后面对这个变量的读操作
    传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
    后四个是显而易见的,重点是前四个
    线程启动规则:Thread对象的start()方法先行发生于此线程的每一个动作
    线程中规则:对线程interrupt()方法的调用先行发生于 被中断线程的代码检测到 中断事件的发生
    线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束,
    Thread.isAlive()的返回值手段检测到线程已经终止执行
    对象终结规则:一个对象的初始化完成先发生于他的finalize()方法的开始

    所以:如果两个操作的执行顺序无法通过happen-before的8个原则推断出来,那么就不能保证他们的有序性。
    虚拟机就可以随意的对他们进行重排序
    
    
     
  • 相关阅读:
    修改ssh默认端口
    网络配置
    nginx 反向代理
    nginx web 服务
    小白日记22:kali渗透测试之提权(二)--抓包嗅探
    小白日记21:kali渗透测试之提权(一)--本地提权
    小白日记20:kali渗透测试之后渗透测试阶段(一)--上传工具
    小白日记19:kali渗透测试之选择和修改EXP
    小白日记18:kali渗透测试之缓冲区溢出实例(二)--Linux,穿越火线1.9.0
    小白日记17:kali渗透测试之缓冲区溢出实例-windows,POP3,SLmail
  • 原文地址:https://www.cnblogs.com/inspred/p/9520805.html
Copyright © 2011-2022 走看看