zoukankan      html  css  js  c++  java
  • Java并发编程 (四) 线程安全性

    个人博客网:https://wushaopei.github.io/    (你想要这里多有)

    一、线程安全性-原子性-atomic-1

    1、线程安全性

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

    特点

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

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

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

    2、原子性 - Atomic包

    Atomic包下的类可以 实现线程并发的原子性,主要的类有:

    ① AtomicXXX : CAS 、Unsafe.compareAndSwapInt

    ② AtomicLong、LongAdder

    3、AtomicXXX 案例实测

    使用Atomic确保线程并发的原子性,代码演示:

    package com.mmall.concurrency.example.count;
    
    import com.mmall.concurrency.annoations.NoThreadSafe;
    import lombok.extern.slf4j.Slf4j;
    
    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;
    
    /**
     * @ClassName CountExample1
     * @Description TODO
     * @Author wushaopei
     * @Date 2019/10/30 17:10
     * @Version 1.0
     */
    @Slf4j
    @NoThreadSafe
    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) throws InterruptedException {
            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 (Exception e){
                        log.error("exception",e);
                    }
                    countDownLatch.countDown();
                });
            }
            countDownLatch.await();
            executorService.shutdown();
            log.info("count:{}",count.get());
        }
    
        private static void  add(){
            count.incrementAndGet();
        }
    }

    执行结果:

    17:14:59.658 [main] INFO com.mmall.concurrency.example.count.CountExample2 - count:5000
    
    Process finished with exit code 0

    由结果可知,无论执行多少次,count都是5000的值,说明了Atomic可以确保线程的安全性。

    原理解析:

    这里对add()方法所做的修改,即将count++改为count.incrementAndGet()是保证线程安全的主要原因!

    源码进行分析:

    点击计入incrementAndGet( )类中:

    由源码可知,incrementAndGet()方法里使用了一个叫unsafe的类,该类实现了一个getAndAddInt()的方法,

    进入getAndAddInt方法进一步分析:

    由源码可知,getAndAddInt方法内部调用了do-while循环结构,在while循环条件中,调用了compareAndSwapInt()方法,这是一个重点,进一步查看该方法:

    由图中源码可知,该方法被native所修饰,这是代表是java底层的方法,而不是通过java去实现的。

     

    分析getAndAddInt()方法源码:

    这里传过来的var1对象值相当于案例中的 count 值,其中的var2 值则是当前的值。

    compareAndSwapInt()方法的作用是,在count值时,var2=2和var5=2的值相同时,将他的值更新为后面的var5+var4的值。这里的var5是从底层取的。

    安全原则示例:当var2=2,var4=4,进入do作用域中,调用this.getIntVolatile()方法进行修改,然后进入while循环,此时会对var2var5进行判断,var2是上一次修改后被返回的校验字段,而var5则是对应保存在底层的校验字段,单线程执行时,每一次var5都会在执行compareAndSwapInt()方法后进行变更,即每一次var4+var5都会++;

    重点:当程序并发请求,当前线程执行var2=2时,有其他线程抢夺CPU执行权执行了一次compareAndSwapInt()方法后,当前线程再获得锁进入执行compareAndSwapInt()方法后var5发生改变,被替换为var4+var5(2+1=3)的值,var2=2和var5=3(此时底层取的是3)是不同的值,校验不通过。此时var2的值会重新从 count中取值,当var2取值为3后,再与var5=3进行比较,比较通过后,再对var4+var5进行执行,结果为3+1=4 ; 逻辑依次循环判断,不断对底层值进行覆盖,从而保证执行线程的安全。

    4、AtomicLong、LongAdder 案例实测

    1)AtomicLong代码实例:

    package com.mmall.concurrency.example.atomic;
    
    import lombok.extern.slf4j.Slf4j;
    
    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;
    import java.util.concurrent.atomic.AtomicLong;
    
    /**
     * @ClassName AtomicExample1
     * @Description TODO
     * @Author wushaopei
     * @Date 2019/10/31 10:23
     * @Version 1.0
     */
    @Slf4j
    @ThreadSafe
    public class AtomicExample2 {
    
        //请求总数
        public static int clientTotal = 5000;
    
        // 同时并发执行的线程数
        public static int threadTotal = 200;
    
        public static AtomicLong count = new AtomicLong(0);
    
        public static void main(String[] args) throws InterruptedException {
            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 (Exception e){
                        log.error("exception",e);
                    }
                    countDownLatch.countDown();
                });
            }
            countDownLatch.await();
            executorService.shutdown();
            log.info("count:{}",count);
        }
    
        private static void  add(){
            count.incrementAndGet();
        }
    
    }

    执行结果:

    10:30:44.260 [main] INFO com.mmall.concurrency.example.atomic.AtomicExample2 - count:5000
    
    Process finished with exit code 0

    2)LongAdder代码实例:

    package com.mmall.concurrency.example.atomic;
    
    import lombok.extern.slf4j.Slf4j;
    
    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.AtomicLong;
    import java.util.concurrent.atomic.LongAdder;
    
    /**
     * @ClassName AtomicExample1
     * @Description TODO
     * @Author wushaopei
     * @Date 2019/10/31 10:23
     * @Version 1.0
     */
    @Slf4j
    @ThreadSafe
    public class AtomicExample3 {
    
        //请求总数
        public static int clientTotal = 5000;
    
        // 同时并发执行的线程数
        public static int threadTotal = 200;
    
        public static LongAdder count = new LongAdder();
    
        public static void main(String[] args) throws InterruptedException {
            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 (Exception e){
                        log.error("exception",e);
                    }
                    countDownLatch.countDown();
                });
            }
            countDownLatch.await();
            executorService.shutdown();
            log.info("count:{}",count);
        }
    
        private static void  add(){
            count.increment();
        }
    
    }

    执行结果:

    10:32:09.818 [main] INFO com.mmall.concurrency.example.atomic.AtomicExample3 - count:5000
    
    Process finished with exit code 0

    知识点:

      对于普通类型的long、double变量,JVM允许将64位的读操作或写操作拆成两个32位的操作。

    LongAdder的实现是基于什么思想?

    LongAdder的核心是将核心数据分离;比如LongAdder内部的value分离成为一个数组,每个线程访问时通过hash等算法映射到其中一个数字进行计数,而最终的计数结果则为这个数组的求和累加,其中热点单元的数据会被分离为多个单元的shell,每个shell独自维护内部的值,当前对象的值由所有shell累计而成。这样的话,热点就进行了有效的分离并提高了并行度,这样一来LongAdder相当于在AtomicLong的基础上将单点的更新压力分散到各个节点上,在低并发的时候通过对base的值进行更新可以很好的保障和Atomic的性能基本一致,而在高并发的时候则通过分散提高了性能。

    注意:实际在处理并发更新统计时,优先使用LongAdder。并发要求低时使用AtomicLong,效率高。

    二、线程安全性-原子性-atomic-2

    1、AtomicReference代码实例

    package com.mmall.concurrency.example.atomic;
    
    
    
    /**
     * @ClassName AtomicExample1
     * @Description TODO
     * @Author wushaopei
     * @Date 2019/10/31 10:23
     * @Version 1.0
     */
    @Slf4j
    @ThreadSafe
    public class AtomicExample4 {
    
       private static AtomicReference<Integer> count = new AtomicReference<>(0);
    
        public static void main(String[] args) {
            count.compareAndSet(0,2); // 2
            count.compareAndSet(0,1); // no
            count.compareAndSet(1,3); // no
            count.compareAndSet(2,4); // 4
            count.compareAndSet(3,5); // no
            log.info("count:{}",count.get());
        }
    
    }

    执行结果:

    10:55:53.811 [main] INFO com.mmall.concurrency.example.atomic.AtomicExample4 - count:4
    
    Process finished with exit code 0

    2、AtomicIntegerFieldUpdater代码实例

    package com.mmall.concurrency.example.atomic;
    
    import com.mmall.concurrency.annoations.ThreadSafe;
    import lombok.Getter;
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
    import java.util.concurrent.atomic.AtomicReference;
    
    /**
     * @ClassName AtomicExample1
     * @Description TODO
     * @Author wushaopei
     * @Date 2019/10/31 10:23
     * @Version 1.0
     */
    @Slf4j
    @ThreadSafe
    public class AtomicExample5 {
    
        @Getter
        public volatile int count = 100;
    
        private static AtomicExample5 example5 = new AtomicExample5();
    
        private static AtomicIntegerFieldUpdater<AtomicExample5> updater = AtomicIntegerFieldUpdater.newUpdater(AtomicExample5.class,"count");
    
        public static void main(String[] args) {
            if (updater.compareAndSet(example5,100,120)){
                log.info("update success 1, {}",example5.getCount());
            }
            if (updater.compareAndSet(example5,100,120)){
                log.info("update success 2, {}",example5.getCount());
            }else {
                log.info("update failed,{}",example5.getCount());
            }
        }
    
    }

    执行结果:

    11:02:48.170 [main] INFO com.mmall.concurrency.example.atomic.AtomicExample5 - update success 1, 120
    11:02:48.177 [main] INFO com.mmall.concurrency.example.atomic.AtomicExample5 - update failed,120
    
    Process finished with exit code 0
    

    分析执行结果:

    AtomicIntegerFieldUpdater的核心是根据原子性去更新某个类的实例,当前案例修改的是AtomicExample5 类的example5 的某一个字段 count;这里要求count 必须被volatile进行修饰;执行第一个compareAndSet方法时,compareAndSet方法中是将第二个变量100修改为第三个变量120,此时example5通过getCount()更新成了120;再执行第二个compareAndSet方法时,example5与100校验不相同,不执行当前compareAndSet方法,所以没有日志打印,流程进入到else中,打印 “update failed ,120 ”。

    3、AtomicStampReference:CAS的ABA问题

    关于ABA问题:

    ABA问题是指在CAS操作的时候,其他线程将变量的值A改成了B,但是又改回了A,本线程使用期望值A与当前变量进行比较的时候,发现A变量没有变,于是CAS就将A值进行了交换操作,这个时候,其实该值已经被其他线程改变过,这与设计思想是不符合的。

    因此,ABA问题的解决思路是:每次变量更新的时候,把变量的版本号加一,那么之前那个A改成B,再改成A,就会变成A变成1版本,改成B变成2版本,再改回A变成3版本,这个时候只要变量被某一个线程修改过,该变量对应的版本号就会发生过递增变化。从而解决了ABA问题。

    package com.mmall.concurrency.example.atomic;
    
    import com.mmall.concurrency.annoations.ThreadSafe;
    import lombok.Getter;
    import lombok.extern.slf4j.Slf4j;
    
    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.AtomicBoolean;
    import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
    import java.util.concurrent.atomic.AtomicLong;
    
    /**
     * @ClassName AtomicExample1
     * @Description TODO
     * @Author wushaopei
     * @Date 2019/10/31 10:23
     * @Version 1.0
     */
    @Slf4j
    @ThreadSafe
    public class AtomicExample6 {
    
    
        //请求总数
        public static int clientTotal = 5000;
    
        // 同时并发执行的线程数
        public static int threadTotal = 200;
    
        public static AtomicBoolean isHappened = new AtomicBoolean(false);
    
        public static void main(String[] args) throws InterruptedException {
            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 (Exception e){
                        log.error("exception",e);
                    }
                    countDownLatch.countDown();
                });
            }
            countDownLatch.await();
            executorService.shutdown();
            log.info("isHappened:{}",isHappened.get());
        }
    
        private static void test(){
            if (isHappened.compareAndSet(false,true)){
                log.info("execute");
            }
        }
    
    }

    执行结果:

    11:25:55.399 [pool-1-thread-1] INFO com.mmall.concurrency.example.atomic.AtomicExample6 - execute
    11:25:55.429 [main] INFO com.mmall.concurrency.example.atomic.AtomicExample6 - isHappened:true

    结果分析:为什么声明了5000次线程,却只执行了一次?

    因为AtomicBoolean的compareAndSet具有原子性,它可以保证从false变成true只有一次,之后的4999次在compareAndSet的判断当前值为true后,不会再执行变成true的操作。

    使用场景: 让某一段代码只执行一次,而不会发生重复。

    三、线程安全性-原子性-synchronized

    原子性的实现是 加锁;而加锁的方式有两种:synchronized 、Lock

    区别:

     synchronized : 依赖JVM

     Lock : 依赖特殊的CPU指令,代码实现,ReentrantLock

    1、原子性 - synchronized

    1)synchronized  的使用:

    •  修饰代码块: 大括号括起来的代码,作用于调用的对象
    •  修饰方法:整个方法,作用于调用的对象
    •  修饰静态方法:整个静态方法,作用于所有对象
    •  修饰类:括号括起来的部分,作用于所有对象

    同步操作:

      修饰代码块也叫同步代码块

      修饰方法也叫同步方法

    synchronized关键字代码演示:

    2、同一对象的两个线程调用同步代码块:

    package com.mmall.concurrency.example.sync;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * @ClassName SynchronizedExample1
     * @Description TODO
     * @Author wushaopei
     * @Date 2019/10/31 14:11
     * @Version 1.0
     */
    @Slf4j
    public class SynchronizedExample1 {
    
        //修饰一个代码块
        //作用的对象是调用的对象
        public void test1(){
            synchronized (this){
                for (int i = 0 ; i < 10 ; i ++){
                    log.info("test1 - {}",i);
                }
            }
        }
    
        //修饰一个方法
        public synchronized void test2(){
            for (int i = 0 ; i < 10 ; i ++){
                log.info("test - {}",i);
            }
        }
    
        public static void main(String[] args) {
            SynchronizedExample1 example1 = new SynchronizedExample1();
            ExecutorService executorService = Executors.newCachedThreadPool();
            //在不使用线程池的情况下,本身就是同步执行,synchronized修饰意义不大,
            //使用线程池后,存在两条线程,同时执行代码,在没有synchronized修饰时是并发异步的执行,交替穿插打印结果
            //使用synchronized修饰后,由于锁定的是当前对象example1,所以只有第一个线程执行完,第二个线程才会执行
            executorService.execute(()->{
                example1.test2();
            });
            executorService.execute(()->{
                example1.test2();
            });
        }
    }

    执行test1()同步方法,结果:

    14:21:22.164 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 - 0
    14:21:22.168 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 - 1
    14:21:22.168 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 - 2
    14:21:22.168 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 - 3
    14:21:22.168 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 - 4
    14:21:22.168 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 - 5
    14:21:22.168 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 - 6
    14:21:22.168 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 - 7
    14:21:22.168 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 - 8
    14:21:22.168 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 - 9
    14:21:22.168 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 - 0
    14:21:22.168 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 - 1
    14:21:22.168 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 - 2
    14:21:22.168 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 - 3
    14:21:22.168 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 - 4
    14:21:22.168 [pool-1-thread-2] INFO 

    由结果可知,先执行完线程1的test1-0到test1-9,再执行线程2的test0到test9

    执行test2()同步方法

        executorService.execute(()->{
            example1.test2();
        });
        executorService.execute(()->{
            example1.test2();
        });

    运行test2()方法,打印结果:

    14:24:17.003 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 - 0
    14:24:17.008 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 - 1
    14:24:17.008 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 - 2
    14:24:17.008 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 - 3
    14:24:17.008 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 - 4
    14:24:17.008 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 - 5
    14:24:17.008 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 - 6
    14:24:17.008 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 - 7
    14:24:17.008 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 - 8
    14:24:17.008 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 - 9
    14:24:17.008 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 - 0
    14:24:17.008 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 - 1
    14:24:17.008 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 - 2
    

    由结果可知,先执行完线程1的test2-0到test2-9,再执行线程2的test2-0到test2-9

    3、不同对象调用同步代码块:

    public void test1(int j){
        synchronized (this){
            for (int i = 0 ; i < 10 ; i ++){
                log.info("test1 {} - {}",j,i);
            }
        }
    }
    
    public static void main(String[] args) {
        SynchronizedExample1 example1 = new SynchronizedExample1();
        SynchronizedExample1 example2 = new SynchronizedExample1();
        ExecutorService executorService = Executors.newCachedThreadPool();
       
    executorService.execute(()->{
            example1.test1(1);
        });
        executorService.execute(()->{
            example2.test1(2);
        });
    }

    执行结果:

    14:31:42.315 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 1 - 0
    14:31:42.315 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 2 - 0
    14:31:42.320 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 1 - 1
    14:31:42.320 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 2 - 1
    14:31:42.320 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 1 - 2
    14:31:42.320 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 1 - 3
    14:31:42.320 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 2 - 2
    14:31:42.320 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 1 - 4
    14:31:42.320 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 2 - 3
    14:31:42.320 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 1 - 5
    14:31:42.320 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 2 - 4
    14:31:42.321 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 2 - 5
    14:31:42.321 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 1 - 6
    14:31:42.321 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 2 - 6
    14:31:42.321 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 2 - 7
    14:31:42.321 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 1 - 7
    14:31:42.321 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 2 - 8
    14:31:42.321 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 1 - 8
    14:31:42.321 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 2 - 9
    14:31:42.321 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test1 1 - 9

    由结果可知,两个不同的对象调用同步代码块时,它们的结果是互相不影响的。

    执行test2()同步方法,

    //修饰一个方法
    public synchronized void test2(int j){
        for (int i = 0 ; i < 10 ; i ++){
            log.info("test2 {} - {}",j,i);
        }
    }
    
    executorService.execute(()->{
        example1.test2(1);
    });
    executorService.execute(()->{
        example2.test2(2);
    });

    执行结果:

    14:37:14.704 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 2 - 0
    14:37:14.704 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 1 - 0
    14:37:14.708 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 1 - 1
    14:37:14.708 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 2 - 1
    14:37:14.708 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 1 - 2
    14:37:14.708 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 2 - 2
    14:37:14.708 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 1 - 3
    14:37:14.708 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 2 - 3
    14:37:14.708 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 1 - 4
    14:37:14.708 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 2 - 4
    14:37:14.708 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 1 - 5
    14:37:14.708 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 2 - 5
    14:37:14.708 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 1 - 6
    14:37:14.708 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 2 - 6
    14:37:14.708 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 1 - 7
    14:37:14.708 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 1 - 8
    14:37:14.708 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 2 - 7
    14:37:14.708 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 1 - 9
    14:37:14.708 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 2 - 8
    14:37:14.708 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample1 - test2 2 - 9

    有结果可知,两个不同的对象调用同步方法时,它们的结果是互相不影响的。

    4、静态同步方法、静态同步代码块:

    package com.mmall.concurrency.example.sync;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * @ClassName SynchronizedExample1
     * @Description TODO
     * @Author wushaopei
     * @Date 2019/10/31 14:11
     * @Version 1.0
     */
    @Slf4j
    public class SynchronizedExample2 {
    
        //修饰一个代码块
        //作用的对象是调用的对象
        public static void test1(int j){
             synchronized  (SynchronizedExample2.class){
                for (int i = 0 ; i < 10 ; i ++){
                    log.info("test1 {} - {}",j,i);
                }
            }
        }
    
        //修饰一个静态方法
        public static synchronized void test2(int j){
            for (int i = 0 ; i < 10 ; i ++){
                log.info("test2 {} - {}",j,i);
            }
        }
    
        public static void main(String[] args) {
            SynchronizedExample2 example1 = new SynchronizedExample2();
            SynchronizedExample2 example2 = new SynchronizedExample2();
            ExecutorService executorService = Executors.newCachedThreadPool();
            executorService.execute(()->{
                example1.test2(1);
            });
            executorService.execute(()->{
                example2.test2(2);
            });
        }
    }

    静态同步代码块测试结果:

    14:46:52.930 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 1 - 0
    14:46:52.935 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 1 - 1
    14:46:52.935 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 1 - 2
    14:46:52.935 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 1 - 3
    14:46:52.935 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 1 - 4
    14:46:52.935 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 1 - 5
    14:46:52.935 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 1 - 6
    14:46:52.935 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 1 - 7
    14:46:52.935 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 1 - 8
    14:46:52.935 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 1 - 9
    14:46:52.935 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 2 - 0
    14:46:52.935 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 2 - 1
    14:46:52.935 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 2 - 2
    14:46:52.935 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 2 - 3
    14:46:52.935 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 2 - 4
    14:46:52.935 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 2 - 5
    14:46:52.935 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 2 - 6
    14:46:52.935 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 2 - 7
    14:46:52.935 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 2 - 8
    14:46:52.935 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test1 2 - 9
    
    

    静态同步方法测试结果:

    14:43:19.042 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 1 - 0
    14:43:19.046 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 1 - 1
    14:43:19.046 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 1 - 2
    14:43:19.046 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 1 - 3
    14:43:19.046 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 1 - 4
    14:43:19.046 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 1 - 5
    14:43:19.046 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 1 - 6
    14:43:19.046 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 1 - 7
    14:43:19.046 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 1 - 8
    14:43:19.046 [pool-1-thread-1] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 1 - 9
    14:43:19.046 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 2 - 0
    14:43:19.046 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 2 - 1
    14:43:19.047 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 2 - 2
    14:43:19.047 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 2 - 3
    14:43:19.047 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 2 - 4
    14:43:19.047 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 2 - 5
    14:43:19.047 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 2 - 6
    14:43:19.047 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 2 - 7
    14:43:19.047 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 2 - 8
    14:43:19.047 [pool-1-thread-2] INFO com.mmall.concurrency.example.sync.SynchronizedExample2 - test2 2 - 9
    
    Process finished with exit code 0

    5、对并发执行增加操作线程使用synchronized修饰:

    @Slf4j
    @ThreadSafe
    public class CountExample3 {
    
        //请求总数
        public static int clientTotal = 5000;
    
        // 同时并发执行的线程数
        public static int threadTotal = 200;
    
        public static int count = 0;
    
        public static void main(String[] args) throws InterruptedException {
            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 (Exception e){
                        log.error("exception",e);
                    }
                    countDownLatch.countDown();
                });
            }
            countDownLatch.await();
            executorService.shutdown();
            log.info("count:{}",count);
        }
    
        //同步方法锁 --- synchronized
        private synchronized static void  add(){
            count ++;
        }
    }
    14:49:18.636 [main] INFO com.mmall.concurrency.example.count.CountExample3 - count:5000
    
    Process finished with exit code 0

    由执行结果可知,count符合执行的要求,说明当前程序是线程安全的

    6、原子性  - 对比

    synchronized : 不可中断锁,适合竞争不激烈,可读性好

    Lock : 可中断锁,多样化同步,竞争激烈时能维持常态

    Atomic : 竞争激烈时能维持常态,比Lock性能好;只能同步一个值

    四、线程安全性-可见性

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

    1、导致共享变量在线程间不可见的原因

    •   线程交叉执行
    •   重排序结合线程交叉执行
    •   共享变量更新后的值没有在工作内存与主存间及时更新

    保证线程可见性的方法 —— synchronized 、volatile

    2、JMM关于synchronized的两条规定

    •       线程解锁前,必须把共享变量的最新值刷新到主内存
    •       线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(注意,加锁与解锁是同一把锁)

    3、可见性 - volatile

    volatile实现内存可见性的原理通过加入内存屏障和禁止重排序优化来实现

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

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

    4、Volatile 代码示例

    public class CountExample4 {
    
        //请求总数
        public static int clientTotal = 5000;
    
        // 同时并发执行的线程数
        public static int threadTotal = 200;
    
        public static volatile int count = 0;
    
        public static void main(String[] args) throws InterruptedException {
            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 (Exception e){
                        log.error("exception",e);
                    }
                    countDownLatch.countDown();
                });
            }
            countDownLatch.await();
            executorService.shutdown();
            log.info("count:{}",count);
        }
    
        private static void  add(){
            count ++;
        }
    }

    执行结果:

    15:04:05.623 [main] INFO com.mmall.concurrency.example.count.CountExample4 - count:4922
    
    Process finished with exit code 0

    由结果可知,count小于5000,说明当前线程依旧是不安全的

    结果说明了直接使用volatile做加操作,是线程不安全的;同时也说明了volatile不具有原子性。

    5、Volatile适合什么样的场景?

    使用volatile必须具备两个条件:

    1. 对变量的写操作不依赖于当前值;
    2. 该变量没有包含在具有其他变量的独变量的式子中。

    6、volatile代码片段分析:

     volatile boolean inited = false;
     
     //线程1:
     context = loadContext();
     inited = true;
    
     //线程2:
     while(!inited){
        sleep():
     }
     doSomethingWithConfig(context);

    分析:当线程1初始化完成后,inited变为true,此时线程2的while会!Inited变为false,知道了线程1初始化已经完成,可以继续从主内存读取变量值并执行相应的业务。这时候线程2去使用初始化好的context就不会出问题了。

    五、线程安全性-有序性与总结

    1、有序性

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

    实现方式: volatile 、synchronized、Lock

    2、有序性 - happens - before原则

    程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作;

    锁定规则: 一个unlOCK操作先行发生于后面对同一个锁的lock操作

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

    传递规则:如果操作A线性发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C

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

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

    线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束 、 Thread.isAlive()的返回值手段检测到线程已经终止执行

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

    小结 :

     原子性 : Atomic包、CAS算法、synchronized、Lock

     可见性:synchronized 、volatile

     有序性:happens-before

  • 相关阅读:
    Java回顾之Spring基础
    Java回顾之ORM框架
    Java回顾之JDBC
    Java回顾之一些基础概念
    Java回顾之反射
    Java回顾之序列化
    platform_device与platform_driver
    DB9 公头母头引脚定义及连接
    浅谈UML的概念和模型之UML九种图
    为Windows 7的winsxs目录瘦身,谨慎。
  • 原文地址:https://www.cnblogs.com/wushaopei/p/11978992.html
Copyright © 2011-2022 走看看