Concurrency

一、简介

多线程表示同一应用程序中具有多个执行线程

二、说明

线程

  • 守护线程
    • 程序运行时在后台提供服务的线程,不属于程序中不可或缺的部分
    • 当所有非守护线程结束时,程序也就终止,同时会杀死所有守护线程

JAVA

线程状态

  • 新建:创建后尚未启动
  • 可运行:正在JVM中运行(具体有没有运行要看操作系统的资源调度)
  • 阻塞:请求获取 monitor lock (被动)
  • 无限期等待:等待其它线程显式地唤醒 (主动)
  • 限期等待:在一定时间之后会被系统自动唤醒 (主动)
  • 死亡:线程运行结束或产生了异常而结束

实现方法

  • 实现 Runnable
  • 实现 Callable
  • 继承 Thread

互斥同步

悲观并发策略:无论共享数据是否真的会出现竞争,它都要进行加锁、用户态核心态转换、维护锁计数器和检查是否有被阻塞的线程需要唤醒等操作

synchronizedReentrantLock
JVMJDK
不可中断可中断
非公平可配置
简单(JVM管理锁)复杂(代码管理锁)
多Condition

非阻塞同步

乐观并发策略:先进行操作,如果没有其它线程争用共享数据,那操作就成功了,否则采取补偿措施(不断地重试,直到成功为止)

  • CAS

    靠硬件来完成。需要有 3 个操作数,分别是内存地址 V、旧的预期值 A 和新值 B。当执行操作时,只有当 V 的值等于 A,才将 V 的值更新为 B。

  • AtomicInteger

    调用了 Unsafe 类的 CAS 操作

  • ABA

    • 如果一个变量初次读取的时候是 A 值,它的值被改成了 B,后来又被改回为 A,那 CAS 操作就会误认为它从来没有被改变过
    • AtomicStampedReference 可以解决这个问题,通过控制变量值的版本来保证 CAS 的正确性

AQS

用于构建锁和同步器的框架。能够降低构建锁和同步器的工作量,还可避免处理多个位置上发生的竞争问题。

  • 模式

    • 独占锁
    • 共享锁
  • 工作原理

    • 同步队列:当线程获取资源失败之后,就进入同步队列的尾部保持自旋等待,不断判断自己是否是链表的头节点,如果是头节点,就不断尝试获取资源,获取成功后则退出同步队列。
    • 条件队列:为Lock实现的一个基础同步器,使用Condition来维护

内存模型

内存模型试图屏蔽各种硬件和操作系统的内存访问差异,以实现让 Java 程序在各种平台下都能达到一致的内存访问效果

  • 主内存与工作内存

    • 所有的变量都存储在主内存中,每个线程还有自己的工作内存,工作内存存储在高速缓存或者寄存器中,保存了该线程使用的变量的主内存副本拷贝
    • 线程只能直接操作工作内存中的变量,不同线程之间的变量值传递需要通过主内存来完成。
  • 内存间交互操作

    内存模型定义了 8 个操作来完成主内存和工作内存的交互操作

    • read:把一个变量的值从主内存传输到工作内存
    • load:在 read 之后执行,把 read 得到的值放入工作内存的变量副本中
    • use:把工作内存中一个变量的值传递给执行引擎
    • assign:把一个从执行引擎接收到的值赋给工作内存的变量
    • store:把工作内存的一个变量的值传送到主内存中
    • write:在 store 之后执行,把 store 得到的值放入主内存的变量中
    • lock:作用于主内存的变量
    • unlock:作用于主内存的变量
  • 三大特性

    • 原子性:一个线程的原子操作不受其他线程影响

      • Java 内存模型允许虚拟机将没有被 volatile 修饰的 64 位数据(long,double)的读写操作划分为两次 32 位的操作来进行,即 load、store、read 和 write 操作可以不具备原子性
      • int 类型读写操作满足原子性只是说明 load、assign、store 这些单个操作具备原子性
      • synchronized 和 Atomic* 能保证多个线程操作的原子性
    • 可见性:当一个线程修改了共享变量的值,其它线程能够立即得知这个修改

      • Java 内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值来实现可见性的
      • volatile 并不能保证操作的原子性
      • synchronized,对一个变量执行 unlock 操作之前,必须把变量值同步回主内存
      • final,被 final 关键字修饰的字段在构造器中一旦初始化完成,并且没有发生 this 逃逸(其它线程通过 this 引用访问到初始化了一半的对象),其它线程就能看见 final 字段的值
    • 有序性:在本线程内观察,所有操作都是有序的。在一个线程观察另一个线程,所有操作都是无序的,无序是因为发生了指令重排序。

      • Java 内存模型中,允许编译器和处理器对指令进行重排序,重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性
      • volatile 通过添加内存屏障的方式来禁止指令重排,即重排序时不能把后面的指令放到内存屏障之前
      • synchronized 保证每个时刻只有一个线程执行同步代码,相当于是让线程顺序执行同步代码
  • 先行发生原则

    让一个操作无需控制就能先于另一个操作完成

    • 单一线程原则:在一个线程内,在程序前面的操作先行发生于后面的操作
    • 管程锁定规则:一个 unlock 操作先行发生于后面对同一个锁的 lock 操作
    • volatile 变量规则:对一个 volatile 变量的写操作先行发生于后面对这个变量的读操作
    • 线程启动规则:Thread 对象的 start() 方法调用先行发生于此线程的每一个动作
    • 线程加入规则:Thread 对象的结束先行发生于 join() 方法返回
    • 线程中断规则:对线程 interrupt() 方法的调用先行发生于被中断线程的代码检测到中断事件的发生
    • 对象终结规则:一个对象的初始化完成(构造函数执行结束)先行发生于它的 finalize() 方法的开始
    • 传递性:操作 A 先行发生于操作 B,操作 B 先行发生于操作 C,那么操作 A 先行发生于操作 C
  • 线程安全

    多个线程不管以何种方式访问某个类,且在代码中不需要进行同步,都能表现正确的行为

    • 实现方式
      • 不可变
        • final 关键字修饰的基本数据类型
        • String
        • 枚举类型
        • Number 部分子类,如 Long 和 Double 等数值包装类型,BigInteger 和 BigDecimal 等大数据类型
        • Collections.unmodifiableXXX() 方法来获取一个不可变的集合
      • 互斥同步
        • synchronized
        • ReentrantLock
      • 非阻塞同步
        • CAS
        • Atomic
      • 无同步方案
        • 栈封闭:多个线程访问同一个方法的局部变量时,不会出现线程安全问题
        • 线程本地存储:把共享数据的可见范围限制在同一个线程之内,如一个请求对应一个服务器线程
          ThreadLocal 有内存泄漏的情况,应尽可能在每次使用 ThreadLocal 后手动调用 remove()
        • 可重入代码(纯代码):可在代码执行的任何时刻中断它,转而去执行另外一段代码(包括递归调用它本身),而在控制权返回后,原来的程序不会出现任何错误

synchronized优化

  • 状态:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态

  • 自旋锁:让一个线程在请求一个共享数据的锁时执行忙循环(自旋)一段时间,如果在这段时间内能获得锁,就可避免进入阻塞状态

    • 忙循环操作会占用 CPU 时间,只适用于共享数据的锁定状态很短的场景
    • 自适应:自旋的次数不再固定了,而是由前一次在同一个锁上的自旋次数及锁的拥有者的状态来决定
  • 锁消除:对于被检测出不可能存在竞争的共享数据的锁进行消除

    • 通过逃逸分析来支持,如果堆上的共享数据不可能逃逸出去被其它线程访问到,那么就可以把它们当成私有数据对待,也就可以将它们的锁进行消除
    • 一些看起来没有加锁的代码,其实隐式的加了很多锁,如 String 拼接代码
  • 锁粗化:扩大锁的范围

    • 如果一系列的连续操作都对同一个对象反复加锁和解锁,频繁的加锁操作就会导致性能损耗
  • 轻量级锁:使用 CAS 操作来避免重量级锁使用互斥量的开销

    • 如果有两条以上的线程争用同一个锁,那轻量级锁就不再有效,要膨胀为重量级锁
    • 对于绝大部分的锁,在整个同步周期内都是不存在竞争的,因此也就不需要都使用互斥量进行同步,可以先采用 CAS 操作进行同步,如果 CAS 失败了再改用互斥量进行同步。
  • 偏向锁:偏向于让第一个获取锁对象的线程,这个线程在之后获取该锁就不再需要进行同步操作

    • 当有另外一个线程去尝试获取这个锁对象时,偏向状态就宣告结束,此时撤销偏向后恢复到未锁定状态或者轻量级锁状态

建议

  • 给线程起个有意义的名字,这样可以方便找 Bug
  • 缩小同步范围,从而减少锁争用
  • 多用同步工具少用 wait() 和 notify()
  • 使用 BlockingQueue 实现生产者消费者问题
  • 多用并发集合少用同步集合
  • 使用本地变量和不可变类来保证线程安全
  • 使用线程池而不是直接创建线程

三、使用

JAVA

join()

在线程中调用另一个线程的 join() 方法,会将当前线程挂起,而不是忙等待,直到目标线程结束

public class JoinExample {
    private class A extends Thread {
        @Override
        public void run() {
            System.out.println("A");
        }
    }

    private class B extends Thread {
        private A a;

        B(A a) {
            this.a = a;
        }

        @Override
        public void run() {
            try {
                a.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("B");
        }
    }

    public void test() {
        A a = new A();
        B b = new B(a);
        b.start();
        a.start();
    }

    public static void main(String[] args) {
        JoinExample example = new JoinExample();
        example.test();
    }
}

wait() notify() notifyAll()

属于 Object 的一部分,只能用在同步方法或者同步控制块中使用,否则会在运行时抛出 IllegalMonitorStateException

wait()sleep()
Object 方法Thread 静态方法
会释放锁不会释放锁
public class WaitNotifyExample {
    public synchronized void before() {
        System.out.println("before");
        notifyAll();
    }

    public synchronized void after() {
        try {
            wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("after");
    }

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        WaitNotifyExample example = new WaitNotifyExample();
        executorService.execute(() -> example.after());
        executorService.execute(() -> example.before());
    }
}

await() signal() signalAll()

await() 可以指定等待的条件,更加灵活

public class AwaitSignalExample {
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void before() {
        lock.lock();
        try {
            System.out.println("before");
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }

    public void after() {
        lock.lock();
        try {
            condition.await();
            System.out.println("after");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        AwaitSignalExample example = new AwaitSignalExample();
        executorService.execute(() -> example.after());
        executorService.execute(() -> example.before());
    }
}

interrupted()

在线程的 run() 方法执行一个无限循环,此时调用线程的 interrupt() 方法无法使线程提前结束,
但是调用 interrupt() 方法会设置线程的中断标记,此时调用 interrupted() 方法会返回 true。
因此可以在循环体中使用 interrupted() 方法来判断线程是否处于中断状态,从而提前结束线程。

public class InterruptExample {
    private static class MyThread2 extends Thread {
        @Override
        public void run() {
            while (!interrupted()) {
                //TODO
            }
            System.out.println("Thread end");
        }
    }
}

Atomic

public class AtomicExample {
    private AtomicInteger cnt = new AtomicInteger();

    public void add() {
        cnt.incrementAndGet();
    }

    public int get() {
        return cnt.get();
    }

    public static void main(String[] args) throws InterruptedException {
        final int threadSize = 1000;
        AtomicExample example = new AtomicExample(); // 只修改这条语句
        final CountDownLatch countDownLatch = new CountDownLatch(threadSize);
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < threadSize; i++) {
            executorService.execute(() -> {
                example.add();
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        System.out.println(example.get());
    }
}

ThreadLocal

把共享数据的可见范围限制在同一个线程之内

public class ThreadLocalExample {
    public static void main(String[] args) {
        ThreadLocal threadLocal = new ThreadLocal();
        Thread thread1 = new Thread(() -> {
            threadLocal.set(1);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(threadLocal.get());
            threadLocal.remove();
        });
        Thread thread2 = new Thread(() -> {
            threadLocal.set(2);
            threadLocal.remove();
        });
        thread1.start();
        thread2.start();
    }
}

CountDownLatch

用来控制一个或者多个线程等待多个线程

public class CountdownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        final int totalThread = 10;
        CountDownLatch countDownLatch = new CountDownLatch(totalThread);
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < totalThread; i++) {
            executorService.execute(() -> {
                System.out.print("run..");
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        System.out.println("end");
        executorService.shutdown();
    }
}

CyclicBarrier

用来控制多个线程互相等待,只有当多个线程都到达时,这些线程才会继续执行,可循环使用

public class CyclicBarrierExample {
    public static void main(String[] args) {
        final int totalThread = 10;
        CyclicBarrier cyclicBarrier = new CyclicBarrier(totalThread);
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < totalThread; i++) {
            executorService.execute(() -> {
                System.out.print("before..");
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
                System.out.print("after..");
            });
        }
        executorService.shutdown();
    }
}

Semaphore

控制对互斥资源的访问线程数

public class SemaphoreExample {
    public static void main(String[] args) {
        final int clientCount = 3;
        final int totalRequestCount = 10;
        Semaphore semaphore = new Semaphore(clientCount);
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < totalRequestCount; i++) {
            executorService.execute(()->{
                try {
                    semaphore.acquire();
                    System.out.print(semaphore.availablePermits() + " ");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();
                }
            });
        }
        executorService.shutdown();
    }
}

FutureTask

可用于异步获取执行结果或取消执行任务的场景

public class FutureTaskExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> futureTask = new FutureTask<Integer>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int result = 0;
                for (int i = 0; i < 100; i++) {
                    Thread.sleep(10);
                    result += i;
                }
                return result;
            }
        });

        Thread computeThread = new Thread(futureTask);
        computeThread.start();

        Thread otherThread = new Thread(() -> {
            System.out.println("other task is running...");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        otherThread.start();
        System.out.println(futureTask.get());
    }
}

BlockingQueue

  • FIFO 队列:LinkedBlockingQueue、ArrayBlockingQueue(固定长度)
  • 优先级队列:PriorityBlockingQueue
public class ProducerConsumer {
    private static BlockingQueue<String> queue = new ArrayBlockingQueue<>(5);

    private static class Producer extends Thread {
        @Override
        public void run() {
            try {
                queue.put("product");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.print("produce..");
        }
    }

    private static class Consumer extends Thread {
        @Override
        public void run() {
            try {
                String product = queue.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.print("consume..");
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 2; i++) {
            Producer producer = new Producer();
            producer.start();
        }
        for (int i = 0; i < 5; i++) {
            Consumer consumer = new Consumer();
            consumer.start();
        }
        for (int i = 0; i < 3; i++) {
            Producer producer = new Producer();
            producer.start();
        }
    }
}

ForkJoin

  • 把大的计算任务拆分成多个小任务并行计算
  • 使用 ForkJoinPool 来启动,它是一个特殊的线程池,线程数量取决于 CPU 核数
  • ForkJoinPool 实现了工作窃取算法来提高 CPU 的利用率。每个线程都维护了一个双端队列,用来存储需要执行的任务
  • 工作窃取算法允许空闲的线程从其它线程的双端队列中窃取一个任务来执行。窃取的任务必须是最晚的任务,避免和队列所属线程发生竞争
public class ForkJoinExample extends RecursiveTask<Integer> {
    private final int threshold = 5;
    private int first;
    private int last;

    public ForkJoinExample(int first, int last) {
        this.first = first;
        this.last = last;
    }

    @Override
    protected Integer compute() {
        int result = 0;
        if (last - first <= threshold) {
            // 任务足够小则直接计算
            for (int i = first; i <= last; i++) {
                result += i;
            }
        } else {
            // 拆分成小任务
            int middle = first + (last - first) / 2;
            ForkJoinExample leftTask = new ForkJoinExample(first, middle);
            ForkJoinExample rightTask = new ForkJoinExample(middle + 1, last);
            leftTask.fork();
            rightTask.fork();
            result = leftTask.join() + rightTask.join();
        }
        return result;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ForkJoinExample example = new ForkJoinExample(1, 10000);
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        Future result = forkJoinPool.submit(example);
        System.out.println(result.get());
    }
}

四、链接

jenkov的JAVA 并发教程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值