多线程并发

多线程

线程

线程状态转换

在这里插入图片描述

volatile

关键字,保证可见性但是不保证原子性,可以被称为改时通知。保证线程可见性,禁止指令重排序。被volatile修饰的变量在被其他线程使用时实际上是使用复制该变量的副本到对应线程中进行操作,当有进行更新操作时会立刻将变量写会到主线程中,并通知其他使用该变量的线程。实际上使用的是CPU的缓存一致性协议MESI。
禁止指令重排序:现代CPU执行指令不再是单个顺序执行,而是会将指令进行重排序并发执行指令。在单例模式中,使用JVM内部类的懒加载实现的方法中如果INSTANCE不加volatile的话在超高的并发当中会出现指令重排序(new对象时三步1.分配内存 2.初始化 3.赋值 重排序会优化指令顺序2,3更换执行顺序所以会造成执行异常)。解决单例模式中Double check中出现的问题。
在这里插入图片描述
在这里插入图片描述

synchronized与lock

1.synchronized:

  • 修饰实例方法锁住的是当前实例对象;
  • 修饰静态方法锁住的是当前类的Class;
  • 修饰代码块是对括号内的对象加锁;

synchorized锁在JVM中是基于进出入每个Monitor对象(每个Object都会存在一个Monitor)来实现,在实际加锁与解锁在实际代码运行时是通过monitorentry和monitorexit两个字节码指令来完成,每次其他线程进入Monitor对象时若是当前计数器不为0则该锁正在被占用,若为0则计数器加一。若是当前线程进入monitor对象计数器不为0则再该基础上加一即为锁的重入(防止子同步方法调用父同步方法造成死锁)。 其中synchronized具有如下特性:

  • 原子性:保证语句块内的操作是原子的;
  • 可见性:通过在执行“unlock”操作时必须把变量同步回主内存;
  • 有序性:一个变量同时只能有一个线程对其进行lock操作;
synchronized实现原理:

JVM中对象在内存中由三个部分构成(对象头,实例变量,填充字节),那么Synchronized锁对象是存在锁对象的对象头的MarkWord(对象运行时所产生的数据)中。
MarkWord数据结构
在JDK1.6之前synchronized都是使用重量级锁来进行加锁来实现同步但效率非常低,在1.6进行了优化不再使用单一的重量级锁来阻塞线程以达到同步,而是使用锁升级机制来优化所效率。

字节码

实际上是通过monitorenter和monitorexit来实现的,但是退出会有两种情况所以在针对代码块的sychnorized会有两个monitorexit。

JVM

C,C++调用操作系统提供的同步机制

锁升级(https://www.cnblogs.com/linghu-java/p/8944784.html)

对于synchronized锁的状态从低到高无锁–>偏向锁–>轻量级锁–>重量级锁四种状态(锁的状态只会升级不会降级):

  • 偏向锁:当线程1访问代码块并获取锁对象时,会在java对象头和栈帧中记录偏向的锁的threadID,因为偏向锁不会主动释放锁,因此以后线程1再次获取锁的时候,需要比较当前线程的threadID和Java对象头中的threadID是否一致,如果一致(还是线程1获取锁对象),则无需使用CAS来加锁、解锁;如果不一致(其他线程,如线程2要竞争锁对象,而偏向锁不会主动释放因此还是存储的线程1的threadID),那么需要查看Java对象头中记录的线程1是否存活,如果没有存活,那么锁对象被重置为无锁状态,其它线程(线程2)可以竞争将其设置为偏向锁;如果存活,那么立刻查找该线程(线程1)的栈帧信息,如果还是需要继续持有这个锁对象,那么暂停当前线程1,撤销偏向锁,升级为轻量级锁,如果线程1 不再使用该锁对象,那么将锁对象状态设为无锁状态,重新偏向新的线程。

  • 轻量级锁:线程1获取轻量级锁时会先把锁对象的对象头MarkWord复制一份到线程1的栈帧中创建的用于存储锁记录的空间(称为DisplacedMarkWord),然后使用CAS把对象头中的内容替换为线程1存储的锁记录(DisplacedMarkWord)的地址;如果在线程1复制对象头的同时(在线程1CAS之前),线程2也准备获取锁,复制了对象头到线程2的锁记录空间中,但是在线程2CAS的时候,发现线程1已经把对象头换了,线程2的CAS失败,那么线程2就尝试使用自旋锁来等待线程1释放锁。但是如果自旋的时间太长也不行,因为自旋是要消耗CPU的,因此自旋的次数是有限制的,比如10次或者100次,如果自旋次数到了线程1还没有释放锁,或者线程1还在执行,线程2还在自旋等待,这时又有一个线程3过来竞争这个锁对象,那么这个时候轻量级锁就会膨胀为重量级锁。重量级锁把除了拥有锁的线程都阻塞,防止CPU空转。

  • 重量级锁:使用Java每个对象自带的Monitor(监控器)来实现,监控锁本质上也是使用操作系统的Mutex Lock(排它锁,操作系统级别)。该锁不是用自旋不会是CPU空转,但是会阻塞其他线程。(唤醒其他等待现线程和上下文切换会有较大的资源消耗)

修饰代码块

synchronized在修饰代码块的时候会通过monitor插入monitorentry和monitorexit两条命令来占用和释放被锁的对象的monitor锁。

修饰方法类

在修饰方法的时候不在通过monitor的命令来占用和释放monitor对象,在访问同步方法的是时候方法调用指令来读取运行时的常量池中的ACC_SYNCHRONIZED标志隐式实现,访问时如果该标志已经被设置了则去获取该对象的的monitor对象,如果获取成功则执行代码,使用完释放,如果获取失败则阻塞。

2.lock: (https://yq.aliyun.com/articles/640868)

Java中的Lock主要有三个实现类ReentrantLock,ReadLock,writeLock。其中以ReentrantLock为例:
在这里插入图片描述
Lock是可以控制加锁是否公平,ReentrantLock默认是非公平锁,其中AbstractQueuedSynchronizer(简称AQS)主要负责锁的的排队以及是否主要功能(加锁解锁),AQS继承于AbstractOwnableSynchronizer(AOS),AOS主要用来存放当前锁的线程对象,即锁拥有者信息。
其中AQS是主要的接口是由一个由volatile修饰的双向链表和int状类型state态值构成(CLH队列)。
在这里插入图片描述
在这里插入图片描述
waitStatus:当前节点状态
thread:当前正在等待的线程
nextWait:下一个等待着

Lock加锁

以NoFairSync为例,实际上通过CAS来修改当前锁的state来获取锁。如果因为状态获取锁失败则
1.tryAcquire:会尝试再次通过CAS获取一次锁。
2.addWaiter:将当前线程加入上面锁的双向链表(等待队列)中
3.acquireQueued:通过自旋,判断当前队列节点是否可以获取锁。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Lock解锁

NonfairSync中释放锁实际上是更新AQS当前状态值,同时更新链表中下一个线程的状态值。

Lock和Synchronized的区别

(1)synchronized锁是关键字,作用在jvm层面上而lock是一个接口类作用在类的层次上,简而言之synchronized是java的一个特性,而lock则是jdkcurrent下的一个作用类

(2)synchronized是非公平锁,而lock则是公平锁,简单的说当有多个线程同时等待一个锁时,lock是按照等待的时间来获取锁,而synchronized则不是。

(3)synchronized放弃锁只有两种情况:①线程执行完了同步代码块的内容②发生异常;而lock不同,它可以设定超时时间,也就是说他可以在获取锁时便设定超时时间,如果在你设定的时间内它还没有获取到锁,那么它会放弃获取锁然后响应放弃操作。

(4)synchronized不能够知道线程是否已经获取到锁,但是lock可以知道。

(5)synchronized在对一个代码块加锁时不能对于读操作和写操作进行分离,但是lock可以,简单说如果两个线程同时要读取一个代码块的内容,synchronized只能一个线程执行完归还锁,下一个线程才能执行,而lock不一样,lock可以使这两个锁同时进行读操作。

AtomicInteger

是一个提供原子性操作的Integer类,通过线程安全的方式进行数值操作。CAS操作是CPU原语支持的,因为AtomicInteger只关注这个类从已经拿到的1变成2不关注其中的变化所以会出现ABA问题(另外一个线程1->2,2->1 这样AtomicInteger还是会进行数值的修改),但是ABA问题可以通过Atomic

数据结构

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    //可以直接操控内存进行线程调度
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    //在对象初始化的过程中计算出value变量在对象中的偏移量
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
    //实际存储数值,所有线程可见
    private volatile int value;

    /**
     * Creates a new AtomicInteger with the given initial value.
     *
     * @param initialValue the initial value
     */
    public AtomicInteger(int initialValue) {
        value = initialValue;
    }

    /**
     * Creates a new AtomicInteger with initial value {@code 0}.
     */
    public AtomicInteger() {
    }

操作

layzSet

实际上等同于set()但是不同的是相比于set()直接对volatile修饰的value进行赋值更节省内存以及效率更高。layzSet()实际上依赖硬件的系统指令完成赋值的,虽然使用layzSet()赋值后非常短的一段时间内其他线程还是会读到旧数据,但是对比volatile赋值不会造成storeLoad级别的内存屏障。(StoreStore:在intel cpu中, 不存在[写写]重排序, 这个可以直接省略了;StoreLoad:这个是所有内存屏障里最耗性能的)putOrderedInt 之前的写不会被重排序,之后的写会被重排序。之后会被重排序意味着执行以后对其他线程是没有可见性保证的。

    /**
     * Eventually sets to the given value.
     *
     * @param newValue the new value
     * @since 1.6
     */
    public final void lazySet(int newValue) {
        unsafe.putOrderedInt(this, valueOffset, newValue);
    }
incrementAndGet

采用CAS机制来更新value,增加更新时的性能损耗。

public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}  

/**
* var1: Object
*var2: valueOffset
*var5: 当前该变量在内存中的值
*var5 + var4: 需要写进去的值
**/
public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        // 直接从该对象对应位置取出变量的值
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
    return var5;
}

Semaphore

Semaphore是一个计数信号量,用来控制并发中的资源访问。加强版的synchronized,未获取到信号量的线程会被处于阻塞状态。其本质是AQS的实现,通过等待队列来实现公平或非公平的。

数据结构

  • sync:基于AQS的同步锁,默认使用非公平的等待队列。
public class Semaphore implements java.io.Serializable {
    private static final long serialVersionUID = -3222578661600680210L;
    /** All mechanics via AbstractQueuedSynchronizer subclass */
    private final Sync sync;

    /**
     * Synchronization implementation for semaphore.  Uses AQS state
     * to represent permits. Subclassed into fair and nonfair
     * versions.
     */
    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 1192457210091910933L;

        Sync(int permits) {
            setState(permits);
        }

        final int getPermits() {
            return getState();
        }

        final int nonfairTryAcquireShared(int acquires) {
            for (;;) {
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }

        protected final boolean tryReleaseShared(int releases) {
            for (;;) {
                int current = getState();
                int next = current + releases;
                if (next < current) // overflow
                    throw new Error("Maximum permit count exceeded");
                if (compareAndSetState(current, next))
                    return true;
            }
        }

        final void reducePermits(int reductions) {
            for (;;) {
                int current = getState();
                int next = current - reductions;
                if (next > current) // underflow
                    throw new Error("Permit count underflow");
                if (compareAndSetState(current, next))
                    return;
            }
        }

        final int drainPermits() {
            for (;;) {
                int current = getState();
                if (current == 0 || compareAndSetState(current, 0))
                    return current;
            }
        }
    }

    /**
     * NonFair version
     */
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -2694183684443567898L;

        NonfairSync(int permits) {
            super(permits);
        }

        protected int tryAcquireShared(int acquires) {
            return nonfairTryAcquireShared(acquires);
        }
    }

    /**
     * Fair version
     */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = 2014338818796000944L;

        FairSync(int permits) {
            super(permits);
        }

        protected int tryAcquireShared(int acquires) {
            for (;;) {
                if (hasQueuedPredecessors())
                    return -1;
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }
    }

CountDownLatch

门栓计数器,内部维护一个线程安全的计数器。初始化默认值,每次线程使用计数器都会-1当减为0时就会释放所有等待队列中的线程。

数据结构

//参数count为计数值
//只会初始化容量
public CountDownLatch(int count) {  };  

操作

挂起
//调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
public void await() throws InterruptedException { };   
//和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };  

CyclicBarrier

栅栏类,阻塞一组线程至某一事件发生,当线程到达栅栏时调用await()阻塞当前线程从而等待其他线程,其中还存在一个计数器count,每个线程调用该栅栏的await类该计数器都会减一,当计数器为0时所有调用await()被阻塞的线程会都被唤醒。
guava rateLimiter

数据结构

public class CyclicBarrier {

//同步操作锁
private final ReentrantLock lock = new ReentrantLock();
//线程拦截器 等待队列
private final Condition trip = lock.newCondition();
//每次拦截的线程数
private final int parties;
//换代前执行的任务
private final Runnable barrierCommand;
//表示栅栏的当前代
private Generation generation = new Generation();
//计数器
private int count;

//静态内部类Generation
private static class Generation {
  boolean broken = false;
}
  • barrierCommand:所有线程均已到达栅栏需要执行的自定义任务
  • generation:表示栅栏的生命代,每次栅栏满后重新开始积攒await线程会重新new出对象证明是新一代栅栏。
    在这里插入图片描述
    PS:比较通俗的理解,CylicBarrier就是游戏,其中generation代表本局游戏,每次释放所有await线程相当于游戏结束需要重新new开一局。parties表示游戏中需要完成任务的数量,这些任务可以同时进行完成,而count来记录已经完成的任务数量。而barrierCommand代表游戏结束前的自定义的奖励流程(可以不设置该阶段)。

构造函数

//构造器1
//核心构造函数,指定任务数量以及任务完成后的自定义奖励环节
public CyclicBarrier(int parties, Runnable barrierAction) {
  if (parties <= 0) throw new IllegalArgumentException();
  this.parties = parties;
  this.count = parties;
  this.barrierCommand = barrierAction;
}
 
//构造器2
public CyclicBarrier(int parties) {
  this(parties, null);
}

操作

await
//非定时等待
public int await() throws InterruptedException, BrokenBarrierException {
  try {
    return dowait(false, 0L);
  } catch (TimeoutException toe) {
    throw new Error(toe);
  }
}
 
//定时等待
public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException {
  return dowait(true, unit.toNanos(timeout));
}

//核心等待方法
private int dowait(boolean timed, long nanos) throws InterruptedException, BrokenBarrierException, TimeoutException {
  final ReentrantLock lock = this.lock;
  lock.lock();
  try {
    final Generation g = generation;
    //检查当前栅栏是否被打翻
    if (g.broken) {
      throw new BrokenBarrierException();
    }
    //检查当前线程是否被中断
    if (Thread.interrupted()) {
      //如果当前线程被中断会做以下三件事
      //1.打翻当前栅栏
      //2.唤醒拦截的所有线程
      //3.抛出中断异常
      breakBarrier();
      throw new InterruptedException();
    }
    //每次都将计数器的值减1
    int index = --count;
    //计数器的值减为0则需唤醒所有线程并转换到下一代
    if (index == 0) {
      boolean ranAction = false;
      try {
        //唤醒所有线程前先执行指定的任务
        final Runnable command = barrierCommand;
        if (command != null) {
          command.run();
        }
        ranAction = true;
        //唤醒所有线程并转到下一代
        nextGeneration();
        return 0;
      } finally {
        //确保在任务未成功执行时能将所有线程唤醒
        if (!ranAction) {
          breakBarrier();
        }
      }
    }
 
    //如果计数器不为0则执行此循环
    for (;;) {
      try {
        //根据传入的参数来决定是定时等待还是非定时等待
        if (!timed) {
          trip.await();
        }else if (nanos > 0L) {
          nanos = trip.awaitNanos(nanos);
        }
      } catch (InterruptedException ie) {
        //若当前线程在等待期间被中断则打翻栅栏唤醒其他线程
        if (g == generation && ! g.broken) {
          breakBarrier();
          throw ie;
        } else {
          //若在捕获中断异常前已经完成在栅栏上的等待, 则直接调用中断操作
          Thread.currentThread().interrupt();
        }
      }
      //如果线程因为打翻栅栏操作而被唤醒则抛出异常
      if (g.broken) {
        throw new BrokenBarrierException();
      }
      //如果线程因为换代操作而被唤醒则返回计数器的值
      if (g != generation) {
        return index;
      }
      //如果线程因为时间到了而被唤醒则打翻栅栏并抛出异常
      if (timed && nanos <= 0L) {
        breakBarrier();
        throw new TimeoutException();
      }
    }
  } finally {
    lock.unlock();
  }
}

/**
* 正常游戏结束
* condition唤醒所有等待队列的线程
* 重置计数器
* 更新生命代
**/
    private void nextGeneration() {
        // signal completion of last generation
        trip.signalAll();
        // set up next generation
        count = parties;
        generation = new Generation();
    }

/**
* 异常游戏结束 任务超时,任务异常 游戏宕机
* 告诉本场游戏结束
* 重置计数器
* 唤醒所有线程
**/
 private void breakBarrier() {
        generation.broken = true;
        count = parties;
        trip.signalAll();
    }

两种等待限时与非限时,非限时会无限等下去知道出现意外打破栅栏。限时等待则将该线程加入condition的等待队列,当有线程因为超时而被唤醒的则整局游戏结束,相当于给任务加一个时限,从第一个任务到最后一个任务进行定时。

reset

强行打破当前栅栏释放所有等待线程,并设置下一句游戏相关配置。

public void reset() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        breakBarrier();   // break the current generation
        nextGeneration(); // start a new generation
    } finally {
        lock.unlock();
    }
}

ThreadLocal

ThreadLocal是一个线程内部的存储类(线程副本),某一线程存储的数据只有该线程可以获取,可以做到线程级别的隔离所以每个线程的get和set方法总是在处理本线程的数据。可以将其看成一个Map,key是当前线程,value是需要存储的对象。但是实际ThreadLocal内部静态类ThreadLocalMap为每一个线程都维护了一个数组table,ThreadLocal确定数据下表取出对应下标的value就是当前线程的value。线程独有的,其他线程不可见。

数据结构

ThreadLocal数据结构

ThreadLocalMap

在每个Thread对象中都存有一份ThreadLocalMap来存放线程的副本信息。实际上ThreadLocalMap是ThreadLocal的内部类,没有实现Map接口,选择独立的方式实现Map的功能,而Map中的Entry也是独立实现的。
ThreadLocalMap数据结构
在ThreadLocalMap中,也是用Entry来保存K-V结构数据的。但是Entry中key只能是ThreadLocal对象,这点被Entry的构造方法已经限定死了。Entry继承WeakRefence(弱引用),虽然Key是弱引用,但是Value是强引用,所以再使用完后不释放的话就会造成内存泄漏。

ThreadLocalMap解决Hash冲突

相比于Collection中的Map的hash冲突,ThreadLocalMap不再采用拉链法,主要数据结构只有Entry构成的数据,所以ThreadLocalMap在解决Hash冲突的时候采用线性探索的方式(当产生哈希冲突的时候index加一或者减一或者去找下一个相邻位置)。
所以ThreadLocalMap为了减少hash冲突,每个线程再ThreadLocal存放最好只有一个value,如果要存放多个对象则封装成一个对象或者使用多个ThreadLocal进行存放。
ThreadLocalMap的key为弱引用,value为强引用,在ThreadLocal没有外部引用时JVM的GC机制会将Key进行回收但是Value是未失效的强应用所以不会被回收,这样就导致ThreadLocal被创建的线程会一直占用着Value所使用的的内存一直无法得到释放。

操作

get

获取当前ThreadLocal在目标线程中存储的数据ThreadLocalMap,再根据当前线程找到ThreadLocalMap中的数组下标所存储的Value并返回。

/**
 * Returns the value in the current thread's copy of this
 * thread-local variable.  If the variable has no value for the
 * current thread, it is first initialized to the value returned
 * by an invocation of the {@link #initialValue} method.
 *
 * @return the current thread's value of this thread-local
 */
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null)
            return (T)e.value;
    }
    return setInitialValue();
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

protected T initialValue() {
    return null;
}
Set

检查当前线程的ThreadLocalMap是否为空,若是为空则对ThreadLocalMap进行初始化并将ThreadLocal和value副本放入map中,若不为空则直接将ThreadLocal和value放入ThreadLcoalMap中。

/**
 * Sets the current thread's copy of this thread-local variable
 * to the specified value.  Most subclasses will have no need to
 * override this method, relying solely on the {@link #initialValue}
 * method to set the values of thread-locals.
 *
 * @param value the value to be stored in the current thread's copy of
 *        this thread-local.
 */
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
remove()

清除线程中的ThreadLocalMap中该Threadlocal的相关数据,因为ThreadLocal被回收或者释放时,线程中的ThreadLocalMap的key为null,但是value中的副本数据依旧存在,引用尚未消失该ThreadLocal-value不会被GC造成内存泄露,而remove作用就是用完即清理所有相关副本数据,保证不会出现内存泄漏。

/**
 * Removes the current thread's value for this thread-local
 * variable.  If this thread-local variable is subsequently
 * {@linkplain #get read} by the current thread, its value will be
 * reinitialized by invoking its {@link #initialValue} method,
 * unless its value is {@linkplain #set set} by the current thread
 * in the interim.  This may result in multiple invocations of the
 * <tt>initialValue</tt> method in the current thread.
 *
 * @since 1.5
 */
public void remove() {
 ThreadLocalMap m = getMap(Thread.currentThread());
 if (m != null)
     m.remove(this);
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

ThreadPoolExecutor

线程池大小设置

线程池核心线程大小设置是根据线程池中线程类型来进行区分设计:计算密集型,IO密集型,混合型

  • 计算密集型:corePoolSize=N,N+1,N-1
    • N :等于CPU核数可以保证线程池对于CPU的利用率最大化;
    • N-1:计算太过密集,例如ForkJoin线程池中执行forkjoinTask会在短时间内吃满CPU影响其他业务;
    • N+1:充分利用CPU,恰好在某时因为发生一个页错误或者因其他原因而暂停,刚好有一个“额外”的线程,可以确保在这种情况下CPU周期不会中断工作。所以N+1确实是一个经验值。
  • IO密集型:N*2
  • 混合型:N/(1-阻塞系数(阻塞性任务/(任务之和)))
    在这里插入图片描述

Java里面的线程池只有两个ThreadPoolExecutor和ScheduleTEhreadPoolExecutor,且这两个类都继承ExecutorService,虽然Java中提供了Executor来创建初始化线程池,但是在规范中尽量使用类自带的方法进行初始化。
类继承图

数据结构

  public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
  • corePoolSize:线程池核心线程数量;
  • maxmumPoolSize:线程池最大线程数量,达到最大值后线程池不会再增加线程任务;
  • keepAliveTime:非core的空闲线程存活最大时间;
  • allowCoreThreadTimeOut:core核心线程有效时间;
  • unit:时间单位;
  • workQueue:阻塞队列,用于保存任务线程,或者为工作线程提供等待执行的任务线程;
  • threadFactory:线程工厂,线程的生产器;
  • handler:拒绝策略,当任务提交数量超过maxmumPoolSize+workQueue总和,任务就会提交给RejectExecutionHandler来处理,用来拒绝执行以及反馈任务;四种策略测试
    • CallerRunsPolicy:超过数量的任务由提交任务的线程(主线程)来执行;
    • AbortPolicy:抛出RejectExtractionException异常来拒绝提交任务;
    • DiscardPolicy:直接抛弃任务不做处理;
    • DiscardOldestPolicy:去除任务队列中第一个任务,重新提交;
    • 自定义:还可以自定义拒绝策略,只用实现RejectedExcutionHandler接口,并重写rejectedExecution方法即可实现自定义拒绝策略。

以上为核心参数

  • ctl:用来存储目前所有有效线程数量和当前线程池状态(32位,高三位存储状态,低29位存储数量),原子性操作保证线程之间互相不影响,初始化为0。
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static int ctlOf(int rs, int wc) { return rs | wc; }
  • Worker:内部类Work是对于任务的封装,每次线程池新建线程时都需要绑定到Work队列中,但不是每次提交任务的时候都会新建线程,只有在小于corePoolSize或者MaxmumPoolSize的时候才会新建线程,用完后处于空闲状态。但是非core线程在用完后不一定会一直处于空闲,直到超过KeepAliveTime时间非core线程会被释放。

线程池状态

    private static final int COUNT_BITS = Integer.SIZE - 3;
    private static final int RUNNING    = -1 << COUNT_BITS;  //-1的二进制码1111111111111111111111111111111
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS; 
 private static final int CAPACITY   = (1 << COUNT_BITS) - 1;//线程池最大线程数=536870911(2^29-1)

事实上COUNT_BITS =29,而上面的5重线程状态实际上是使用32位中的高3位来表示,低29位存线程数,这样线程池的状态和线程数量就由一个变量存储,即:

  • RUNNING=111: 线程池正常运行,可以接受新的任务并处理队列中的任务。
  • SHUTDOWN=000:关闭状态,不再接受新的任务,但是会执行队列中的任务。在线程池处于 RUNNING 状态时,调用 shutdown()方法会使线程池进入到该状态。(finalize() 方法在执行过程中也会调用shutdown()方法进入该状态);
  • STOP=001:不再接受新任务,不处理队列中的任务,中断进行中的任务。在线程池处于 RUNNING 或 SHUTDOWN 状态时,调用 shutdownNow() 方法会使线程池进入到该状态;
  • TIDYING=010:所有任务已经终止,workerCount为0,线程状态转换到TIDYING,线程池进入该状态后会调用 terminated() 方法进入TERMINATED 状态。
  • TERMINATED=110:terminate()函数执行完成后进入该状态。
    线程池状态转换图

操作

execute

同步执行任务

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        //如果当前运行线程的数量小于corePoolSize就调用addWork()创建新线程运行
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        //如果正在执行的数量大于等于corePoolSize,将任务放到阻塞队列.如果阻塞队列没有满并且是运行着的,直接放入阻塞队列.放入队列之后还要再做一次检查,如果线程池不在运行状态,把刚才的任务移除,调用reject方法,否则查看worker数量,若为0起一个新的worker去执行任务
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
        //加入队列失败的话,会addWorker尝试一个新的worker去执行任务,新worker创建失败,调用reject方法
            reject(command);
    }

 private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c); //获取当前线程池状态

            // Check if queue empty only if necessary.
            //主要判断当前是否允许添加work
            // 1. 线程池不在RUNNING状态并且状态是STOP、TIDYING或TERMINATED中的任意一种状态
            // 2. 线程池不在RUNNING状态,线程池接受了新的任务 
            // 3. 线程池不在RUNNING状态,阻塞队列为空。
            // 满足这3个条件中的任意一个的话,拒绝执行任务
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

           for (;;) {
            int wc = workerCountOf(c); // 线程池线程个数
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize)) // 如果线程池线程数量超过线程池最大容量或者线程数量超过了基本大小(core参数为true,core参数为false的话判断超过最大大小)
                return false; // 超过直接返回false
            if (compareAndIncrementWorkerCount(c)) // 没有超过各种大小的话,cas操作线程池线程数量+1,cas成功的话跳出循环
                break retry;
            c = ctl.get();  // 重新检查状态
            if (runStateOf(c) != rs) // 如果状态改变了,重新循环操作
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }
    // 走到这一步说明cas操作成功了,线程池线程数量+1
    boolean workerStarted = false; // 任务是否成功启动标识
    boolean workerAdded = false; // 任务是否添加成功标识
    Worker w = null;
    try {
        final ReentrantLock mainLock = this.mainLock; // 得到线程池的可重入锁
        w = new Worker(firstTask); // 基于任务firstTask构造worker
        final Thread t = w.thread; // 使用Worker的属性thread,这个thread是使用ThreadFactory构造出来的
        if (t != null) { // ThreadFactory构造出的Thread有可能是null,做个判断
            mainLock.lock(); // 锁住,防止并发
            try {
                // 在锁住之后再重新检测一下状态
                int c = ctl.get();
                int rs = runStateOf(c);

                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) { // 如果线程池在RUNNING状态或者线程池在SHUTDOWN状态并且任务是个null
                    if (t.isAlive()) // 判断线程是否还活着,也就是说线程已经启动并且还没死掉
                        throw new IllegalThreadStateException(); // 如果存在已经启动并且还没死的线程,抛出异常
                    workers.add(w); // worker添加到线程池的workers属性中,是个HashSet
                    int s = workers.size(); // 得到目前线程池中的线程个数
                    if (s > largestPoolSize) // 如果线程池中的线程个数超过了线程池中的最大线程数时,更新一下这个最大线程数
                        largestPoolSize = s;
                    workerAdded = true; // 标识一下任务已经添加成功
                }
            } finally {
                mainLock.unlock(); // 解锁
            }
            if (workerAdded) { // 如果任务添加成功,运行任务,改变一下任务成功启动标识
                t.start(); // 启动线程,这里的t是Worker中的thread属性,所以相当于就是调用了Worker的run方法
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted) // 如果任务启动失败,调用addWorkerFailed方法
            addWorkerFailed(w);
    }
    return workerStarted;
}
submit

异步提交线程任务

Callable

Runnable + Result,有返回的Runnable线程

Feature

存储执行将来才会获得的结果,其中get方法是阻塞的,直到线程池内的线程异步执行成功后才会唤醒。

FeatureTask

Feature+Task:即是异步任务,也会将结果保存到异步任务中。

CompletableFeature

任务队结果集管理,管理多个异步任务结果。所有都完成或者任意一个完成。

SingleThreadPool

单线程线程池,适用于保证按照顺序执行各个任务,并且在任意时间点,不会有多个线程活动的应用场景。

数据结构

 public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

CacheThreadPool

无界线程池(Integer最大值),无核心线程,单反有需要切勿空闲时间线程则会启动新线程。

数据结构

 public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

FixThreadPool

固定线程池,固定了核心线程数和最大线程数。

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

ScheduleThreadPool

也是一个无界Queue,但是实际上使用的是延迟队列。

    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

WorkStealingPool

每个线程都会维护各自的一个任务队列,当自己的线程执行完后可以去别的线程的任务队列获取任务线程进行执行。本质上还是ForkJoinPool。
在这里插入图片描述

数据结构
public static ExecutorService newWorkStealingPool() {
        return new ForkJoinPool
            (Runtime.getRuntime().availableProcessors(),
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }

ForkJoinPool

分解汇总的任务线程池,其中添加的任务不再是Runnable或者Callable,而是ForkJoin自己封装的ForkJoinTask(但是该类比较原始,一般会使用重新封装的类RecursiveAction(无返回)或者RecursiveTask(有返回)),重写其中的compute方法实现任务的分拆,通过fork将子任务加入到ForkJoinPool中,如果有返回通过Join合并结果集。
在这里插入图片描述

数据结构
 public ForkJoinPool() {
        this(Math.min(MAX_CAP, Runtime.getRuntime().availableProcessors()),
             defaultForkJoinWorkerThreadFactory, null, false);
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值