博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
并发编程与高并发学习笔记一
阅读量:6905 次
发布时间:2019-06-27

本文共 10127 字,大约阅读时间需要 33 分钟。

一,线程安全性 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
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个原则推断出来,那么就不能保证他们的有序性。 虚拟机就可以随意的对他们进行重排序

 

 
 

转载于:https://www.cnblogs.com/inspred/p/9520805.html

你可能感兴趣的文章
搭建本地yum仓库
查看>>
CentOS7 源码编译安装稳定LNMP环境 支持Zabbix
查看>>
常用软件包下载网址
查看>>
Vagrant中Apache或Nginx,修改css/js等静态文件不生效的解决方案
查看>>
Arduino学习笔记01——单个LED灯闪烁
查看>>
学习linux计划书
查看>>
我的友情链接
查看>>
我的友情链接
查看>>
Hapoxy--基础篇
查看>>
centons 6.4 vsftpd部署
查看>>
ftp在强制模式下允许匿名用户上传文件
查看>>
处理Elasticsearch集群yellow和red状态
查看>>
我的友情链接
查看>>
android编译系统makefile(Android.mk)写法
查看>>
我的友情链接
查看>>
我的友情链接
查看>>
Android UI开发第二十八篇——Fragment中使用左右滑动菜单
查看>>
[awk] 用-F指定多分隔符实例_备忘
查看>>
我的友情链接
查看>>
C++字符串高效查找替换
查看>>