• JAVA--并发的线程安全处理(一)--线程安全性


    多线程并发与线程安全相关知识整理如下:

    1. 线程怎么保证安全性
    2. 如何安全发布对象
    3. 线程安全有哪些手段
    4. JUC组件的讲解
    5. 如何提高线程的调度

    一、线程怎么保证安全性。

    • 什么是线程安全性

    当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。

     

    • 线程安全性的三大特征

    原子性、有序性、可见性

    原子性:提供互斥访问,同一时刻只能有一个线程来对它进行操作。

    有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序的存在,该观察结果一般杂乱无序。

    可见性:一个线程对主内存的修改可以及时被其他线程观察到。

    特性 操作
    原子性  synchronized的代码块能保证串行执行
    有序性  可以由volatile(禁止指令重排序)/synchronized(一个变量最多只能有一个线程对其lock)实现
    可见性 可以由final(不会修改)、volatile(强制更新+读取主内存)以及synchronized(在unlock时会刷新所有已修改数据到主内存,lock时会从主内存重新加载数据)实现

     

    • 原子性-Atomic包

    Java从JDK1.5开始提供了java.util.concurrent.atomic包,方便程序员在多线程环境下,无锁的进行原子操作。原子变量的底层使用了处理器提供的原子指令,但是不同的CPU架构可能提供的原子指令不一样,也有可能需要某种形式的内部锁,所以该方法不能绝对保证线程不被阻塞。

    上代码

    package Atomic;
    
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Semaphore;
    import java.util.concurrent.atomic.AtomicInteger;
    
    //线程安全
    public class AtomicIntegerDemo {
    
        private final static int clientTotal=5000;//线程总数量
        private final static int threadTotal=200;//每次通过的线程数
    
        static AtomicInteger ai=new AtomicInteger(0);
    
        public static void main(String[] args) {
            //线程池
            ExecutorService executorService= Executors.newCachedThreadPool();
            //生成信号量
            final Semaphore semaphore=new Semaphore(threadTotal);//每次通过20个
            final CountDownLatch latch=new CountDownLatch(clientTotal);//计数器
            for(int i=0;i<clientTotal;i++) {
                executorService.execute(() -> {
                    try {
                        semaphore.acquire();//申请/获取许可
                        //incrementAndGet()
                        //-->1.unsafe.getAndAddInt
                        //-->2.this.compareAndSwapInt(CAS)
                        //-->3.native 方法
                        //第2->3,实际是工作内存与主内存校验的过程(CAS特别重要)
                        ai.incrementAndGet();//数据加1
                        semaphore.release();//释放许可
                    } catch (Exception e) {
                        System.out.println("execption:" + e);
                    }
                    latch.countDown();//计数器减1
                });
            }
            try {
               latch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            executorService.shutdown();//关闭线程
            System.out.println(ai.get());
        }
    }
    • 原子性-锁(synchronized,lock)

      synchronized:依赖JVM,JVM会自动锁定和解除锁定

      Lock:依赖CPU指令,需要人工解锁,ReentrantLock

      Synchroized代码

      

    package Sync;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class SynchronizedDemo {
    
        //修饰整一个方法
        private synchronized void test1(){
            for(int i=0;i<10;i++){
                System.out.println("Test1 - "+i);
            }
        }
        //修饰某个代码块
        private void test2(){
            synchronized (this){
                for(int i=0;i<10;i++){
                    System.out.println("Test2 - "+i);
                }
            }
        }
    
        public static void main(String[] args) {
    
            SynchronizedDemo demo1=new SynchronizedDemo();
            ExecutorService executorService= Executors.newCachedThreadPool();
                executorService.execute(()->{
                    demo1.test1();
                });
                executorService.execute(()->{
                    demo1.test2();
                });
            executorService.shutdown();
        }
    }

    Synchronized对应的作用域如下表

    操作方法 作用域
    修饰某段代码 大括号括起来的代码,作用于调用的对象
    修饰方法 整个方法,作用于调用的对象
    修饰静态方法 整个静态方法,作用于所有对象(从JVM原理考虑)
    修饰类 括号括起来的部分,作用于所有对象(从JVM原理考虑)

     Lock代码

    package Sync;
    
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Semaphore;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class LockDemo {
    
        private static int count=0;
        private final static int threadPoolCount=5000;
        private final static int threadCount=200;
        private static Lock lock=new ReentrantLock();
        public static void main(String[] args) {
    
            ExecutorService executorService= Executors.newCachedThreadPool();//线程池
            Semaphore semaphore=new Semaphore(threadCount);
            final CountDownLatch countDownLatch = new CountDownLatch(threadPoolCount);
    
            for(int i=0;i<threadPoolCount;i++){
                executorService.execute(()->{
                    try {
                        semaphore.acquire();
                         add();
                        semaphore.release();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    countDownLatch.countDown();
                });
            }
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            executorService.shutdown();
            System.out.println(count);
        }
    
        private static void add(){
            lock.lock();//锁定
            count++;//需要同步的方法
            lock.unlock();//解锁
        }
    }

    Atomic、synchronized、Lock对比

    Atomic:在竞争激烈时能维持常态,比Lock性能好,但只能更新一个值

    synchronized:不可中断锁,适合竞争不激烈,可读性好,JVM会自动释放资源。

    Lock:可中断锁,在竞争激烈时能维持常态,需要人工加锁与解锁。

      

    • 可见性

    导致共享变量在线程间不可见的原因(线程交叉执行、重排序结合线程交叉执行、共享变量更新后的值没有在工作内存和主存及时更新)

    可见性-synchronized

    JAVA内存模型里面synchronized的两条规定:

    1.线程解锁前,必须把共享变量的最新刷新到主内存

    2.线程加锁时,将清空工作内存中共享变量的值,从而触发需要使用共享变量时,必须从主内存中重新读取最新的值

     

    可见性-volatile

    通过加入内存屏障禁止重排序优化来实现

    volatile变量在写操作时,会在写操作后加入一个store屏障指令,将本地内存中的共享变量值刷新到主内存。

    volatile变量在读操作时,会在读操作前加入一个load屏障指令,从主内存读取共享变量

    package Sync;
    
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Semaphore;
    
    public class VolatileDemo {
    
        private static volatile int count=0;
        private final static int threadPoolCount=5000;
        private final static int threadCount=200;
        public static void main(String[] args) {
    
            ExecutorService executorService= Executors.newCachedThreadPool();//线程池
            Semaphore semaphore=new Semaphore(threadCount);
            final CountDownLatch countDownLatch = new CountDownLatch(threadPoolCount);
    
            for(int i=0;i<threadPoolCount;i++){
                executorService.execute(()->{
                    try {
                        semaphore.acquire();
                        add();
                        semaphore.release();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    countDownLatch.countDown();
                });
            }
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            executorService.shutdown();
            System.out.println(count);
        }
    
        private static void add(){
            //1.第一步获取count
            //2.第二步+1
            //3.把count的值,返回到主内存中
            count++;
            //计算结果显示Volatile不具有原子性
            //Volatile比较适合使用 状态标记(boolean)
            //对变量的写操作不依赖于当前值
            //该变量没有包含在具有其他变量的变量中
        }
    }
    • 有序性

    JAVA内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到但单线程程序的执行,却会影响到多线程并发执行的正确性。

    happens-before八大原则

    程序次序规则:一个线程内,按照代码的顺序执行。

    锁定规则:一个unLock的操作的发生于后面对一个锁的lock操作。

    volatile变量规则:对变量的写操作先行发生于后面对这个变量的读操作。

    传递规则:A操作B,B操作C,那么可以得出A操作C。

    线程启动规则:Thread对象的start()方法先行发送与此线程的每一个动作。

    线程中断规则:对线程interrupt()方法的调用先行发送与中断线程的代码检测到中断事件的发生。

    线程终结规则:线程中所有的操作终止检测,可以通过Thread.join()方法来结束。

    对象终结规则:一个对象的初始化完成先行发送于他的finalize()方法的开始。

     

  • 相关阅读:
    P5356 [Ynoi2017]由乃打扑克
    P4921 [MtOI2018]情侣?给我烧了!/P4931 [MtOI2018]情侣?给我烧了!(加强版)
    P2605 [ZJOI2010]基站选址
    CF1062E Company
    kd-tree
    扩展中国剩余定理(EXCRT)
    CF264C Choosing Balls
    CF1139D Steps to One
    P4655 [CEOI2017]Building Bridges
    P3311 [SDOI2014] 数数
  • 原文地址:https://www.cnblogs.com/lzj2008/p/8907140.html
Copyright © 2020-2023  润新知