转载

Java并发篇(1)入坑并发编程的正确姿势

随着当今CPU的高速发展,4核、8核甚至16核CPU已经面世了。在以往单核CPU的时代,每一个线程只能争抢一个CPU去获取运行的权利。在多核CPU的场景下,一个线程已经无法充分地利用多个CPU了,再者,数字化时代更加加剧了用户对应用的性能需求,传统的单线程应用已经逐渐被淘汰了, 通过多线程并发执行的形式可以将CPU的计算能力发挥到极限 ,这是为什么需要学习并发编程的一个重要原因。

Java并发篇(1)入坑并发编程的正确姿势

并发编程也有缺点

频繁的上下文切换

即使单核处理器也支持多线程执行代码,CPU通过给每个线程分配时间片来实现多线程并发执行。当任务A时间片执行完以后会切换到下一个任务,此时需要保存当前任务A的状态,在下一次再次轮到任务A执行的时候需要恢复这个状态, 这样的一次保存和恢复称为上下文切换。频繁的上下文切换会耗费系统大量资源!

Java并发篇(1)入坑并发编程的正确姿势

减少上下文切换的方法有

concurrentHashMap

线程安全问题

并发编程环境下,多线程有可能带来的安全问题有:

  1. 线程死锁:每个线程互相等待对方释放正在占有的资源。
  2. 线程饥饿:线程永远无法得到CPU的时间片,永远处于等待状态。
public class DeadLockDemo {
    private static String resource_a = "A";
    private static String resource_b = "B";

    public static void main(String[] args) {
        deadLock();
    }
    public static void deadLock() {
        Thread threadA = new Thread(()->{
            synchronized (resource_a) {
                    System.out.println("get resource a");
                    try {
                        Thread.sleep(3000);
                        synchronized (resource_b) {
                            System.out.println("get resource b");
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
        });
        Thread threadB = new Thread(()->{
            synchronized (resource_b) {
                    System.out.println("get resource b");
                    synchronized (resource_a) {
                        System.out.println("get resource a");
                    }
                }
        });
        threadA.start();
        threadB.start();
    }
}
复制代码

上面这段代码演示了死锁这个场景,通过 jps 查看进程号, jstack 查看应用线程状态,可以看到两个线程都在等待对方正在占有的资源,自身却不释放资源,造成永久地相互等待现象。

Java并发篇(1)入坑并发编程的正确姿势

避免死锁的几种常见方法:

  1. 避免一个线程同时请求获取多个锁
  2. 给资源加上编号,线程只能按照编号按顺序获取锁
  3. 获取锁的时间加上一个 期限值 ,不要让线程永久地请求锁
  4. 银行家算法,在 分配锁之前判断是否会造成死锁 ,如果是则拒绝本次获取锁的请求

在学习并发编程前需要了解的概念

同步和异步

同步:方法A调用方法B后,需要等待方法B执行结束后才能继续执行方法A。

异步:方法A调用方法B后,方法A可以继续处理自己的业务逻辑,无需等待方法B执行结束。

比如,在超时购物,如果一件物品没了,你得等仓库人员跟你调货,直到仓库人员跟你把货物送过来,你才能继续去收银台付款,这就类似同步调用。而异步调用了,就像网购,你在网上付款下单后,什么事就不用管了,该干嘛就干嘛去了,当货物到达后你收到通知去取就好。

并发与并行

并发:多个线程不断地交替执行,在 同一时刻只有一个线程在执行

并行:真正意义上的多个线程同时执行, 多个线程分配在多个CPU上在同一时刻一起执行

比如,在单核CPU中,不存在并行执行线程的概念,只存在并发执行的概念。因为多个线程必须共享一个CPU,CPU不断地切换线程上下文让不同的线程执行(真的累)。在多核CPU中,多个线程都可以分配在不同的CPU上在同一时刻同时执行。

阻塞和非阻塞

阻塞:如果一个线程A占用了资源X,当线程B请求操作资源X时必须等待线程A释放资源X,等待的过程就称为阻塞。

非阻塞:和上面的例子相似,不同的是线程B请求操作资源X时不需要等待线程A释放,多个线程可以对资源X进行随意地访问。

临界区

临界区用来表示一种公共资源或者说是 共享数据 ,可以被多个线程访问。但是每个线程对临界区资源进行操作时, 一旦临界区资源被一个线程占有,那么其他线程必须等待。

例如Java中的 CopyOnWriteArrayList ,在读数据时不会对这个临界资源加锁,但是在对它添加、删除或更新数据时就会把整个集合锁住,其它线程必须等待该线程操作完后才能够访问。

原文  https://juejin.im/post/5e9f8994e51d4546fc797628
正文到此结束
Loading...