线程池与线程安全

线程多起来的话就需要管理,不然就会乱成一锅。我们知道线程在物理上对应的就是栈里面的一段内存,存放着局部变量的空间和待执行指令集。如果每次执行都要从头初始化这段内存,然后再交给 CPU 执行,效率就有点低了。假如我们知道该段栈内存会被经常用到,那我们就不要回收,创建完就让它在栈里面呆着,要用的时候取出来,用完换回去,是不是就省了初始化线程空间的时间,这样是我们搞出线程池的初衷。

其实线程池很简单,就是搞了个池子放了一堆线程。既然我们搞线程池是为了提高效率,那就要考虑线程池放多少个线程比较合适,太多了或者太少了有什么问题,怎么拒绝多余的请求,除了异常怎么处理。首先我们来看跟线程池有关的一张类图。

ThreadFactory

在 Java 中并不鼓励直接创建线程,线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。

ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
.setNameFormat("demo-pool-%d").build();
ExecutorService singleThreadPool = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
singleThreadPool.execute(()-> System.out.println(Thread.currentThread().getName()));
singleThreadPool.shutdown();

线程安全

在 Java 中,保证线程安全一般会用两种方式:锁和原子变量。volatile 确保每次操作都能强制同步 CPU 缓存和主存直接的变量。而且在编译期间能阻止指令重排。读写并发情况下 volatile 也不能确保线程安全。

通过加锁的方式实现线程安全

采取加锁的方式,默认线程会冲突,访问数据时,先加上锁再访问,访问之后再解锁。通过锁界定一个临界区,同时只有一个线程进入。如上图所示,Thread2 访问 Entry 的时候,加了锁,Thread1 就不能再执行访问 Entry 的代码,从而保证线程安全。下面是 ArrayBlockingQueue 通过加锁的方式实现的 offer 方法,保证线程安全。

public boolean offer(E e) {
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        if (count == items.length)
            return false;
        else {
            insert(e);
            return true;
        }
    } finally {
        lock.unlock();
    }
}Copy to clipboardErrorCopied

原子变量

原子变量能够保证原子性的操作,意思是某个任务在执行过程中,要么全部成功,要么全部失败回滚,恢复到执行之前的初态,不存在初态和成功之间的中间状态。例如 CAS 操作,要么比较并交换成功,要么比较并交换失败。由 CPU 保证原子性。通过原子变量可以实现线程安全。执行某个任务的时候,先假定不会有冲突,若不发生冲突,则直接执行成功;当发生冲突的时候,则执行失败,回滚再重新操作,直到不发生冲突。

通过原子变量 CAS 实现线程安全

如图所示,Thread1 和 Thread2 都要把 Entry 加 1。若不加锁,也不使用 CAS,有可能 Thread1 取到了 myValue=1,Thread2 也取到了 myValue=1,然后相加,Entry 中的 value 值为 2。这与预期不相符,我们预期的是 Entry 的值经过两次相加后等于 3。CAS 会先把 Entry 现在的 value 跟线程当初读出的值相比较,若相同,则赋值;若不相同,则赋值执行失败。一般会通过 while/for 循环来重新执行,直到赋值成功。

代码示例是 AtomicInteger 的 getAndAdd 方法。CAS 是 CPU 的一个指令,由 CPU 保证原子性。

/**
 * Atomically adds the given value to the current value.
 *
 * @param delta the value to add
 * @return the previous value
 */
public final int getAndAdd(int delta) {
    for (;;) {
        int current = get();
        int next = current + delta;
        if (compareAndSet(current, next))
            return current;
    }
}
/**
 * Atomically sets the value to the given updated value
 * if the current value {@code ==} the expected value.
 *
 * @param expect the expected value
 * @param update the new value
 * @return true if successful. False return indicates that
 * the actual value was not equal to the expected value.
 */
public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}Copy to clipboardErrorCopied

在高度竞争的情况下,锁的性能将超过原子变量的性能,但是更真实的竞争情况下,原子变量的性能将超过锁的性能。同时原子变量不会有死锁等活跃性问题。