(三)Java多线程 —— 并发编程与线程安全

并发编程与线程安全

一、并发模拟

  1. Postman:Http请求模拟,并发模拟
  2. Apache Bench(AB):Apache附带的工具,测试网址性能
  3. JMeter:Apache组织开发的压力测试工具
  4. 代码:Semaphore(信号量)、CountDownLatch(计数器)

二、线程安全性

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

  1. 原子性:提供了互斥访问,统一时刻只能有一个线程来对它进行操作。
  2. 可见性:一个线程对主内存的修改可以及时的被其他线程观察到。
  3. 有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序的存在,该观察结果一般杂乱无序。

1)原子性 - Atomic包:(java.util.concurrent.atomic.XXX)

AtomicInteger 、AtomicLong 、LongAdder

如:计数AtomicInteger(它的实现原理是,Unsafe.compareAndSwapInt实现类先获取了底层(主内存)的值,来跟传进去(运行内存)的值进行了比较,
如果不相等则取内存的值)

例子:并发200个线程 ,去请求5000次的加1操作。

1.1 AtomicInteger

备注:@ThreadSafe 自定义注解表示是线程安全的意思,便于识别!

import com.mmall.concurrency.annoations.ThreadSafe;
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;

@Slf4j
@ThreadSafe
public class AtomicExample1 {

    // 请求总数
    public static int clientTotal = 5000;

    // 同时并发执行的线程数
    public static int threadTotal = 200;

    public static AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) throws Exception {
        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();
        // count.getAndIncrement();
    }
}

多次计算的结果都是正确的:说明是线程安全的
结果打印:

16:43:38.728 [main] INFO  - count:5000

Process finished with exit code 0

1.2 AtomicLong ,LongAdder

AtomicLong ,LongAdder
long相关原子计算类对比:LongAdder 优化了高并发性能。AtomicLong 在低并发有速度和准确优势。

public static AtomicLong count = new AtomicLong(0);
count.incrementAndGet();

public static LongAdder count = new LongAdder();
count.increment();

1.3)AtomicReference、AtomicIntegerFieldUpdater

AtomicReference:

count.compareAndSet(a,b); 该方法作用:对比一致的话则设值。

import com.mmall.concurrency.annoations.ThreadSafe;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.atomic.AtomicReference; 

@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());
    }
}

AtomicIntegerFieldUpdater:
//原子性去更新某个类中的一个实例中的一个字段。必须用volatile 修饰且非静态的字段。

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;

@Slf4j
@ThreadSafe
public class AtomicExample5 {

    private static AtomicIntegerFieldUpdater<AtomicExample5> updater =
            AtomicIntegerFieldUpdater.newUpdater(AtomicExample5.class, "count");

    @Getter
    public volatile int count = 100;

    public static void main(String[] args) {

        AtomicExample5 example5 = new AtomicExample5();

        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());
        }
    }
}

1.4)AtomicStampedReference

AtomicStampedReference: CAS,ABA,每当被修改一次增加一个版本,从而解决原子性问题。

1.5)AtomicBoolean

AtomicBoolean:常用于让某个代码只执行一次。
isHappened.compareAndSet(false, true); //如果是false则改成true.(以下只执行了一次)

import com.mmall.concurrency.annoations.ThreadSafe;
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;

@Slf4j
@ThreadSafe
public class AtomicExample6 {

    private static AtomicBoolean isHappened = new AtomicBoolean(false);

    // 请求总数
    public static int clientTotal = 5000;

    // 同时并发执行的线程数
    public static int threadTotal = 200;

    public static void main(String[] args) throws Exception {
        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");
        }
    }
}

1.6 原子性 - 锁

  1. synchronized:依赖JVM
    修饰代码块:大括号括起来的代码,作用于【调用的对象】
    修饰方法:整个方法,作用于【调用的对象】
    修饰静态方法:整个静态方法,作用于【所有对象】
    修饰类:括号括起来的部分,作用于【所有对象】

测试synchronized修饰代码块、方法:

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Slf4j
public class SynchronizedExample1 {

    // 修饰一个代码块
    public void test1(int j) {
        synchronized (this) {
            for (int i = 0; i < 10; i++) {
                log.info("test1 {} - {}", j, i);
            }
        }
    }

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

    public static void main(String[] args) {
        SynchronizedExample1 example1 = new SynchronizedExample1();
        SynchronizedExample1 example2 = new SynchronizedExample1();
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.execute(() -> {
            example1.test2(1);
        });
        executorService.execute(() -> {
            example2.test2(2);
        });
    }
}

执行结果:
(三)Java多线程 —— 并发编程与线程安全_第1张图片
测试synchronized修饰类、静态方法:

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@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.test1(1);
        });
        executorService.execute(() -> {
            example2.test1(2);
        });
    }
}

执行结果:
(三)Java多线程 —— 并发编程与线程安全_第2张图片

  1. Lock:依赖特殊的CPU指定、代码实现、ReentrantLock

原子性 - 对比:

synchronized:不可中断锁,适合竞争不激烈,可读性好
Lock:可中断锁,多样化同步,竞争激烈时能维持常态
Atomic:竞争激烈时能维持常态,比Lock性能好,只能同步一个值

2)可见性

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

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

2.1 可见性- synchronized

JMM关于synchronized的两条规定:

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

2.2 可见性- volatile

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

  1. 对volatile变量写操作时,会在写操作后加入一条store屏障指令,将本地内存中的共享变量值刷新到主内存
  2. 对volatile变量读操作时,会在读操作前加入一条load屏障指令,从主内存中读取共享变量值

2.2 可见性- volatile使用

满足2个条件:

  1. 对变量的写操作不依赖当前值
  2. 该变量没有包含在具有其他变量的不变式子中
    不适合计数场景,适合状态值标记,检查2次。

例如:

volatile boolean inited = false;

//线程1
context = loadContext();
inited = true;

//线程2
while(!inited){
 sleep();
}
doSomethingWithConfig(context);

3)有序性

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

3.1 有序性 - happends-before 原则

  1. 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作。
  2. 锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作
  3. volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作。
  4. 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
  5. 线程启动原则:Thread对象的start()方法先行发生于此线程的每一个操作。
  6. 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
  7. 线程终结规则:线程中所有的操作都先行发生于线程的终止规则,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
  8. 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始。

线程安全性 - 总结

  • 原子性:Atomic包、CAS算法、synchronized、Lock
  • 可见性:synchronized、volatile
  • 有序性:happends-before

你可能感兴趣的:(多线程)