零散的Java知识记录 1

2021/11/2 Java

# 并发工具类库

# ConcurrentHashMap并不能保证原子性

我们需要注意 ConcurrentHashMap 对外提供的方法或能力的限制:

  • 使用了 ConcurrentHashMap,不代表对它的多个操作之间的状态是一致的,是没有其他线程在操作它的,如果需要确保需要手动加锁。
  • 诸如 size、isEmpty 和 containsValue 等聚合方法,在并发情况下可能会反映 ConcurrentHashMap 的中间状态。因此在并发情况下,这些方法的返回值只能用作参考,而不能用于流程控制。显然,利用 size 方法计算差异值,是一个流程控制。
  • 诸如 putAll 这样的聚合方法也不能确保原子性,在 putAll 的过程中去获取数据可能会获取到部分数据。

# 加锁

# 加锁对象

  • 加锁前要清楚锁和被保护的对象是不是一个层面的
  • 静态字段属于类,类级别的锁才能保护;而非静态字段属于类实例,实例级别的锁 就可以保护。

也就是说,如果在非静态的 wrong 方法上加锁,只能确保多个线程无法执行同一个实例的 wrong 方法, 却不能保证不会执行不同实例的 wrong 方法。
而静态的 counter 在多个实例中共享,所以 必然会出现线程安全问题。 所以我们应该创建一个同样在类中定义一个 Object 类型的静态字段,在操作 counter 之前对这个字段加锁。

public class Data {

    @Getter
    private static int counter = 0;
    private static Object locker = new Object();

    public static int reset() {
        counter = 0;
        return counter;
    }

    public void right() {
        synchronized (locker) {
            counter++;
        }
    }

    public synchronized void wrong() {
        counter++;
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 死锁

    private boolean createOrder(List<Item> order) {
        //存放所有获得的锁
        List<ReentrantLock> locks = new ArrayList<>();
        for (Item item : order) {
            try {
                //获得锁10秒超时
                if (item.lock.tryLock(10, TimeUnit.SECONDS)) {
                    locks.add(item.lock);
                } else {
                    locks.forEach(ReentrantLock::unlock);
                    return false;
                }
            } catch (InterruptedException e) {
            }
        }
        //锁全部拿到之后执行扣减库存业务逻辑
        try {
            order.forEach(item -> item.remaining--);
        } finally {
            locks.forEach(ReentrantLock::unlock);
        }
        return true;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

先声明一个 List 来保存所有获得的锁,然后遍历购物车中的商品依次尝试获得商品的锁,最长等待 10 秒,获得全部锁之后再扣减库存;如果有无法获得锁的情况则解锁之前获得的所有锁,返回 false 下单失败。假设一个购物车中的商品 是 item1 和 item2,另一个购物车中的商品是 item2 和 item1,一个线程先获取到了 item1 的锁,同时另一个线程获取到了 item2 的锁,然后两个线程接下来要分别获取 item2 和 item1 的锁,这个时候锁已经被对方获取了,只能相互等待一直到 10 秒超时。要解决这个问题,只需要将order中的item进行一下排序,这样就不会出现死锁。

  1. 使用 synchronized 加锁虽然简单,但我们首先要弄清楚共享资源是类还是实例级别的、会被哪些线程操作,synchronized 关联的锁对象或方法又是什么范围的。
  2. 加锁尽可能要考虑粒度和场景,锁保护的代码意味着无法进行多线程操作。对于 Web 类型的天然多线程项目,对方法进行大范围加锁会显著降级并发能力,要考虑尽可能地只为必要的代码块加锁,降低锁的粒度;而对于要求超高性能的业务,还要细化考虑锁的读写场景,以及悲观优先还是乐观优先,尽可能针对明确场景精细化加锁方案,可以在适当 的场景下考虑使用 ReentrantReadWriteLock、StampedLock 等高级的锁工具类。
  3. 业务逻辑中有多把锁时要考虑死锁问题,通常的规避方案是,避免无限等待和循环等待。

# 线程池

不建议使用 Executors 提供的两种快捷的线程池,原因如下:

  1. 需要根据自己的场景、并发情况来评估线程池的几个核心参数,包括核心线程数、最大线程数、线程回收策略、工作队列的类型,以及拒绝策略,确保线程池的工作行为符合需求,一般都需要设置有界的工作队列和可控的线程数。
  2. 任何时候,都应该为自定义线程池指定有意义的名称,以方便排查问题。当出现线程数 量暴增、线程死锁、线程占用大量 CPU、线程执行出现异常等问题时,我们往往会抓取 线程栈。此时,有意义的线程名称,就可以方便我们定位问题。
  3. 除了建议手动声明线程池以外,我还建议用一些监控手段来观察线程池的状态。线程池这个组件往往会表现得任劳任怨、默默无闻,除非是出现了拒绝策略,否则压力再大都不会抛出一个异常。如果我们能提前观察到线程池队列的积压,或者线程数量的快速膨胀,往往可以提早发现并解决问题。

# 连接池

在介绍线程池的时候我们强调过,池一定是用来复用的,否则其使用代价会比每次创建 单一对象更大。对连接池来说更是如此,原因如下:

  1. 创建连接池的时候很可能一次性创建了多个连接,大多数连接池考虑到性能,会在初始化的时候维护一定数量的最小连接(毕竟初始化连接池的过程一般是一次性的),可以直接使用。如果每次使用连接池都按需创建连接池,那么很可能你只用到一个连接,但 是创建了 N 个连接。
  2. 连接池一般会有一些管理模块,也就是连接池的结构示意图中的绿色部分。举个例子, 大多数的连接池都有闲置超时的概念。连接池会检测连接的闲置时间,定期回收闲置的连接,把活跃连接数降到最低(闲置)连接的配置值,减轻服务端的压力。一般情况下,闲置连接由独立线程管理,启动了空闲检测的连接池相当于还会启动一个线程。此外,有些连接池还需要独立线程负责连接保活等功能。因此,启动一个连接池相当于启 动了 N 个线程。
  3. 除了使用代价,连接池不释放,还可能会引起线程泄露。
  4. 连接池的配置不是一成不变的,最大连接数不是越大越好,如果每个客户端都抱有大量连接,对于服务器来说压力过大。连接池最大连接数设置得太小,很可能会因为获取连接的等待时间太长,导致吞吐量低下,甚至超时无法获取连接。

客户端 SDK 实现连接池的方式,包括池和连接分离、内部带有连接池和非连接池三种。要正确使用连接池,就必须首先鉴别连接池的实现方式。比如,Jedis 的 API 实现的是池和连接分离的方式,而 Apache HttpClient 是内置连接池的 API。

对于使用姿势其实就是两点,一是确保连接池是复用的,二是尽可能在程序退出之前显式关闭连接池释放资源。连接池设计的初衷就是为了保持一定量的连接,这样连接可以随取随用。从连接池获取连接虽然很快,但连接池的初始化会比较慢,需要做一些管理模块的初始化以及初始最小闲置连接。一旦连接池不是复用的,那么其性能会比随时创建单一连接更差。

最后,连接池参数配置中,最重要的是最大连接数,许多高并发应用往往因为最大连接数不 够导致性能问题。但,最大连接数不是设置得越大越好,够用就好。需要注意的是,针对数 据库连接池、HTTP 连接池、Redis 连接池等重要连接池,务必建立完善的监控和报警机 制,根据容量规划及时调整参数配置。

# Java的一些零碎知识点

# StopWatch

其实就是一个简单的计时器的工具类,可以通过下面代码来分析各个任务耗时的比例。

System.out.println(stopWatch.prettyPrint());
1

# parallel并行处理

在保证现成安全的前提下,通过parallel可以进行并行处理。比如我们要同时下载多个图片这类操作。

Last Updated: 2025/2/21 01:42:36