Java并发学习笔记

245 阅读43分钟

Week1

多个线程读写同一共享变量

CPU内存模型

计算机在执行程序的时候,每条指令都是在CPU中执行的,而在CPU执行指令的过程中会涉及到数据的读取和写入操作,而在计算机运行过程中所有的数据都是存放在主存中的(比如一台普通的4C8G机器,这个8G就是指主存的容量),CPU则是从主存中读取数据进行运算。

每个CPU都会自带一个高速缓冲区,在运行的时候,会将需要运行的数据从计算机主存先复制到CPU的高速缓冲区中,然后CPU再基于高速缓冲区的数据进行运算,运算结束之后,再将高速缓冲区的数据刷新到主存中。这样CPU的执行指令的速度就可以大大提升。

Java内存模型

是JVM的内存模型屏蔽了不同的操作系统和底层硬件之间的内存访问差异,实现了在各个平台都能达到一致的内存访问效果。

JVM启动之后,操作系统会为JVM进程分配一定的内存空间,这部分内存空间就称为“主内存”。

另外Java程序的所有工作都由线程来完成,而每个线程都会有一小块内存,称为“工作内存”, Java中的线程在执行的过程中,会先将数据从主内存中复制到线程的工作内存,然后再执行计算,执行计算之后,再把计算结果刷新到“主内存”中。

并发编程的原子性

解决并发问题的方法

无锁

局部变量

不可变对象

所谓的不可变对象是指一经创建,就对外的状态就不会改变的对象

如果一个对象的状态是亘古不变的,那么自然就不存在什么并发问题了。因为对象是不可变的,所以无论多少个线程,对它做什么操作,它都是不变的。

ThreadLocal

每个线程自己的本地变量。

CAS原子操作

(乐观锁操作)

CAS的意思是Compare And Swap,英文翻译过来的意思是“比较并置换”,CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B,只有当内存地址V所对应的值和旧的预期值A相等的时候,才会将内存地址V对应的值更新为新的值B。

CAS是原子操作。

有锁

悲观锁

synchronized

ReentrantLock

源码剖析-CopyOnWriteArrayList

写时复制,volatile,ReentrantLock

等效不可变对象

对象基本符合不可变对象的一些特征,但是某些情况下内部状态可能会改变

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    private static final long serialVersionUID = 8673264195747942595L;

    /** The lock protecting all mutators */
    final transient ReentrantLock lock = new ReentrantLock();

    /** The array, accessed only via getArray/setArray. */
    private transient volatile Object[] array;

    /**
     * Gets the array.  Non-private so as to also be accessible
     * from CopyOnWriteArraySet class.
     */
    final Object[] getArray() {
        return array;
    }

    /**
     * Sets the array.
     */
    final void setArray(Object[] a) {
        array = a;
    }
    ...
}    

private transient volatile Object[] array;

array数组用来存储集合元素,并且该数组只能通过 getArray和setArray来访问。

迭代器

    public Iterator<E> iterator() {
        return new COWIterator<E>(getArray(), 0);
    }

迭代器会通过 getArray()获取一个数组副本,然后遍历的时候,基于当前获取到的数组来遍历。

add

    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

先获取当前数组,复制到一个新数组,基于新数组,添加元素;最后将新数组 覆盖掉 旧的数组

这个过程需要加锁。

写时复制机制

集合进行迭代的情况,本质上是一个读操作。而往集合中新增一个元素本质上是一个写操作。

适用于读多写少的场景。

  • 读操作拿到的是一份“快照”数据,没法保证拿到最新的数据。(弱一致性)

  • 读操作和写操作 操作的并不是同一个数组。

使用场景

CopyOnWriteArrayList适用于读多写少的场景

JDBC

数据库JDBC驱动加载过程,主要基于Java 1.6 ServiceLoader API (SPI)

这是MySQL的JDBC Driver

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

最终会调用DriverManager.registerDriver 来注册驱动

DriverManager#registerDriver
    public static synchronized void registerDriver(java.sql.Driver driver,
            DriverAction da)
        throws SQLException {

        /* Register the driver if it has not already been added to our list */
        if(driver != null) {
            registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
        } else {
           ...
        }

        ...
    }

registeredDrivers:

// List of registered JDBC drivers
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();

CopyOnWriteArrayList#addIfAbsent

    public boolean addIfAbsent(E e) {
        Object[] snapshot = getArray();
        return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
            addIfAbsent(e, snapshot);
    }
    private boolean addIfAbsent(E e, Object[] snapshot) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] current = getArray();
            int len = current.length;
            if (snapshot != current) {
                // Optimize for lost race to another addXXX operation
                int common = Math.min(snapshot.length, len);
                for (int i = 0; i < common; i++)
                    if (current[i] != snapshot[i] && eq(e, current[i]))
                        return false;
                if (indexOf(e, current, common, len) >= 0)
                        return false;
            }
            Object[] newElements = Arrays.copyOf(current, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

读多写少场景的分析:

  • 写少:

    registerDrivers就是用来保存不同的数据库驱动的,而通常来说,一个项目上只有一个数据库类型,就算在一些复杂的场景下,可能一个项目对应多个数据库类型,有Mysql数据库也有Oracle数据库,甚至其他的一些数据库。

    但是无论有多少个数据库类型,数据库的驱动程序一般都是在程序启动的时候加载的,也就是说registerDriver方法一般来说都是在程序启动的时候进行调用的,在后续程序运行过程中一般不会再调用这个方法,这种场景完美符合“写少”的定义,基本上在程序运行过程中,不会再进行写操作(也就是add/remove等操作)

  • 读多:

    当在我们程序中需要调用JDBC得到数据库连接的时候,会去遍历所有的driver,然后找到一个driver,然后通过那个特定的driver来获取连接。

    DriverManager#getConnection()

            for(DriverInfo aDriver : registeredDrivers) {
                // If the caller does not have permission to load the driver then
                // skip it.
                if(isDriverAllowed(aDriver.driver, callerCL)) {
                    try {
                        println("    trying " + aDriver.driver.getClass().getName());
                        Connection con = aDriver.driver.connect(url, info);
                        if (con != null) {
                            // Success!
                            println("getConnection returning " + aDriver.driver.getClass().getName());
                            return (con);
                        }
                    } catch (SQLException ex) {
                        if (reason == null) {
                            reason = ex;
                        }
                    }
    
                } else {
                    println("    skipping: " + aDriver.getClass().getName());
                }
    
            }
    

    通过遍历registerDrivers,然后判断每个驱动程序能否被加载,如果可以加载就通过驱动程序来获取connection。这个getConnection方法会被频繁调用,因为一般开发一些项目的话,需要经常和数据库进行交互,会经常需要获取连接,所以getConnection的调用频率肯定会高一些。

CopyOnWriteArrayList的实现本质上是通过弱一致性提升读请求并发,适合用在数据读多写少的场景

Week2

线程状态流转

线程状态

![](liutianruo-2019-go-go-go.oss-cn-shanghai.aliyuncs.com/notes/5. 线程状态.png)

Thread.State枚举类

NEW(初始化状态)

新建一个线程时,就会进入NEW状态

Thread state for a thread which has not yet started.

RUNNABLE(就绪、运行状态)

Thread state for a runnable thread. A thread in the runnable state is executing in the Java virtual machine but it may be waiting for other resources from the operating system such as processor

主要分为两种状态:就绪(READY)以及运行中(RUNNING)

  • READY: 调度程序还没调度到你这个线程,你目前还是闲着不用做事的摸鱼人

    • 在调用当前线程的start方法后,线程就进入就绪状态
    • 当前线程的sleep方法结束;其他线程join结束了;某个线程拿到对象锁
    • 当前线程时间片用完了;调用当前线程的yield()方法(当前线程主动让出CPU资源)
    • 锁池中的线程拿到对象锁以后
  • RUNNING:

    ​ 线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。

BLOCKED(阻塞状态)

线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)之前时的状态。

WAITING(等待状态)

调用wait方法后线程处于WAITING状态,等待被唤醒

TIMED_WAITING(等待超时状态)

调用sleep或是wait方法后线程处于TIMED_WAITING状态,等待被唤醒或时间超时自动唤醒

TERMINATED(终止状态)

  1. 当线程的run()方法完成时,或者主线程的main()方法完成时,我们就认为它终止了。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦终止了,就不能复生
  2. 在一个终止的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。

线程间状态切换

NEW -> RUNNABLE

对于一个新创建(NEW)的线程来说,当这个线程要执行时,就必须调用这个对象的start()方法,将NEW 状态转换到 RUNNABLE 状态。

![](liutianruo-2019-go-go-go.oss-cn-shanghai.aliyuncs.com/note/6. NEW到RUNNABLE 状态.png)

RUNNABLE -> BLOCKED

![](liutianruo-2019-go-go-go.oss-cn-shanghai.aliyuncs.com/note/7.RUNN… -_ BLOCKED 状态流转.png)

  1. 线程等待synchronized 的隐式锁时,线程才会从RUNNABLE 向BLOCKED 转换

    上面所说的RUNNABLE其实是狭义上的running,如果不在运行的线程,为什么会阻塞呢,或者为什么会去等待synchoronized隐式锁呢

  2. 当等待的线程获得 synchronized 隐式锁时,就又会从 BLOCKED 转换到 RUNNABLE 状态,然后等待CPU调度吧(runnable -> running)

RUNNABLE -> WAITING

![](liutianruo-2019-go-go-go.oss-cn-shanghai.aliyuncs.com/note/8. RUNNABLE -_ WAITING 状态流转.png)

RUNNABLE -> WAITING

  1. 获得synchoronized隐式锁的线程,调用Object.wait()方法

  2. 调用某个线程的join()

    join方法说明:有一个线程对象 thread A,当调用 A.join() 的时候,执行这条语句的线程会等待 thread A 执行完,而等待中的这个线程,其状态会从 RUNNABLE 转换到 WAITING当线程 thread A 执行完,原来等待它的线程又会从 WAITING 状态转换到 RUNNABLE。

  3. 调用 LockSupport.park() 方法

    Java 并发包中的锁,都是基于LockSupport 对象实现的。调用 LockSupport.park() 方法,当前线程会阻塞,线程的状态会从 RUNNABLE 转换到 WAITING。调用 LockSupport.unpark(Thread thread) 方法,可唤醒目标线程,目标线程的状态又会从 WAITING 状态转换到 RUNNABLE。

RUNNABLE -> TIMEDWAITING

![](liutianruo-2019-go-go-go.oss-cn-shanghai.aliyuncs.com/note/9. RUNNABLE -_ TIMED_WAITING.png)

  1. java.lang.Thread#sleep(long)
  2. 获得synchoronized隐式锁的线程,调用java.lang.Object#wait(long)
  3. java.lang.Thread#join(long)
  4. java.util.concurrent.locks.LockSupport#parkNanos(long)
  5. java.util.concurrent.locks.LockSupport#parkUntil(long)

TIMED_WAITING 和 WAITING 状态的区别,仅仅是触发条件多了超时参数。

RUNNABLE -> TERMINATED

线程执行完 run() 方法后,会自动转换到 TERMINATED 状态,当然如果执行 run() 方法的时候异常抛出,也会导致线程终止。

如果需要强制中断 run() 方法的执行,则调用 interrupt() 方法

interrupt() 方法仅仅是通知线程,让线程有机会执行一些后续操作,同时也可以无视这个通知。

![](liutianruo-2019-go-go-go.oss-cn-shanghai.aliyuncs.com/note/10.RUN… -_ TERMINATED.png)

总结脑图

“死锁”问题

死锁” 是两个或两个以上的线程在执行过程中,互相持有对方所需要的资源,导致这些线程处于等待状态,无法继续执行。若无外力作用,它们都将无法推进下去,就进入了“永久”阻塞的状态

死锁例子

package com.ruyuan2020.java.concurrence.week2;


/**
 * 死锁demo
 * @author ajin
 * */
public class DeadLockDemo {
    public static String obj1 = "obj1";
    public static String obj2 = "obj2";

    public static void main(String[] args) {
        Thread a = new Thread(new Lock1());
        Thread b = new Thread(new Lock2());
        a.start();
        b.start();
    }
}
class Lock1 implements Runnable{

    @Override
    public void run() {
        try {
            System.out.println("lock1 is running");
            while (true){
                synchronized (DeadLockDemo.obj1){
                    System.out.println("Lock1 lock obj1");
                    Thread.sleep(3000);
                    synchronized (DeadLockDemo.obj2){
                        System.out.println("Lock2 lock obj2");
                    }
                }

            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
class Lock2 implements Runnable{

    @Override
    public void run() {
        try {
            System.out.println("lock2 is running");
            while (true){
                synchronized (DeadLockDemo.obj2){
                    System.out.println("Lock2 lock obj2");
                    Thread.sleep(3000);
                    synchronized (DeadLockDemo.obj1){
                        System.out.println("Lock1 lock obj1");
                    }
                }

            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

lock2 is running Lock2 lock obj2 lock1 is running Lock1 lock obj1

死锁产生原因

死锁的发生必须具备以下四个必要条件:

  1. 互斥,互斥资源X和Y只能被一个线程访问
  2. 占有且等待,线程01已经获取了X资源,在等待X资源的同时,不释放共享资源Y
  3. 不可抢占,其他线程不能强行抢占线程01占有的资源
  4. 循环等待,线程01等待线程02的资源,线程02等待线程01的资源。

避免死锁的方法

发生死锁后,并没有什么好的解决方法,通常我们只能重启系统。

解决死锁最好的方式,就是避免死锁。

破坏“占用且等待“条件

就是一次性申请占有所有的资源

破坏“不可抢占”条件

需要发生死锁的线程能够主动释放它占有的资源,但使用synchronized是做不到的。

因为synchronized申请不到资源时,线程直接进入了阻塞状态,而线程进入了阻塞状态也就没有办法释放它占有的资源了(synchronized是隐式锁,非程序直接可控)

解决方法:显式使用Lock类中的定时tryLock功能来代替内置锁机制,可以检测死锁和从死锁中恢复过来。使用内置锁的线程获取不到锁会被阻塞,而显式锁可以指定一个超时时限(Timeout),在等待超过该时间后tryLock就会返回一个失败信息,也会释放其拥有的资源

破坏“循环等待”条件

“资源有序分配法”

需要对系统中的资源进行统一编号,线程可在任何时刻提出资源申请,必须按照资源的编号顺序提出。这样做就能保证系统不出现死锁。

思维导图

Guarded Suspension模式

Guarded Suspension模式的“等待-通知”机制是一种非常普遍的线程间协作的方式。

Guarded :保护

Suspension:暂时挂起

如果线程01拿不到所有的锁,就阻塞自己,进入“等待WAITING”状态。当线程01要求的所有条件都满足后,“通知”等待状态的线程01重新执行。(等待 - 通知机制)

线程首先获取互斥锁,当线程要求的条件不满足时,释放互斥锁,进入等待状态当要求的条件满足时,通知等待的线程,重新获取互斥锁。

代码案例来理解:

public class GuardedQueue {

    private final Queue<Integer> sourceList;

    public GuardedQueue() {
        this.sourceList = new LinkedBlockingDeque<>();
    }

    public synchronized Integer get() {
        // 如果List为空,则进入等待状态
        while (sourceList.isEmpty()) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return sourceList.peek();
    }

    public synchronized void put(Integer e) {
        sourceList.add(e);
        notifyAll();
    }
}


public class Application {

    public static void main(String[] args) throws InterruptedException {
        GuardedQueue guardedQueue = new GuardedQueue();
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        executorService.execute(guardedQueue::get);

        Thread.sleep(2000);

        executorService.execute(() -> {
            guardedQueue.put(20);
        });

        executorService.shutdown();

        executorService.awaitTermination(30, TimeUnit.SECONDS);
    }
}

总结:

就是当前线程首先获取锁,如果不满足条件,则调用wait方法让自己处于WAITING状态;当满足条件时,再调用notifyAll()方法来唤醒线程,进入RUNNABLE状态。

notify()与notifyAll()的比较:

notify()和notifyAll()这两者是有区别的,notify() 是会随机地通知等待队列中的任意一个线程,而 notifyAll() 会通知等待队列中的所有线程。

使用 notify() 也很有风险,因为随机通知等待的线程,可能会导致某些线程永远不会被通知到。

常规情况下建议使用notifyAll()。

Guarded Suspension模式在BlockingQueue中的使用

Guarded Suspension: 保护性暂挂模式。

这种“保护性暂挂”模式可以大大降低多线程获取锁时锁冲突带来的性能开销,当线程在访问某个对象时,发现条件不满足,就暂时挂起等待条件满足时再访问。

如果线程01拿不到所有的锁,就阻塞自己,进入“等待WAITING”状态。当线程01要求的所有条件都满足后,通知等待状态的线程01继续执行。

BlockingQueue为阻塞队列,在某些情况下对阻塞队列的访问可能会造成阻塞。

  • 队列满了,进行入队操作
  • 队列空了,进行出队操作
public interface BlockingQueue<E> extends Queue<E> {
    
    // 将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量)
    // 插入成功时返回 true,如果此队列已满,则抛IllegalStateException。
    boolean add(E e);

  
    boolean offer(E e);

   //将指定的元素插入此队列的尾部,如果该队列已满,则一直等到(阻塞)
    void put(E e) throws InterruptedException;

     //将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量)
   // 将指定的元素插入此队列的尾部,如果该队列已满,
   //则在到达指定的等待时间之前等待可用的空间,该方法可中断
    boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException;

   //获取并移除此队列的头部,如果没有元素则等待(阻塞),
   //直到有元素将唤醒等待线程执行该操作  
    E take() throws InterruptedException;

    //获取并移除此队列的头部,在指定的等待时间前一直等到获取元素,超过时间方法将结束
    E poll(long timeout, TimeUnit unit)
        throws InterruptedException;

    //从此队列中移除指定元素的单个实例(如果存在)。
    boolean remove(Object o);

   
}

ArrayBlockingQueue

构造器
    
    /** The queued items */
    final Object[] items;

    /** items index for next take, poll, peek or remove */
    int takeIndex;

    /** items index for next put, offer, or add */
    int putIndex;

    /** Number of elements in the queue */
    int count;

    /*
     * Concurrency control uses the classic two-condition algorithm
     * found in any textbook.
     */

    /** Main lock guarding all access */
    final ReentrantLock lock;

    /** Condition for waiting takes */
    private final Condition notEmpty;

    /** Condition for waiting puts */
    private final Condition notFull;

	public ArrayBlockingQueue(int capacity, boolean fair) {
        if (capacity <= 0)
            throw new IllegalArgumentException();
        this.items = new Object[capacity];
        lock = new ReentrantLock(fair);
        notEmpty = lock.newCondition();
        notFull =  lock.newCondition();
    }
非阻塞方法
offer(E)
    public boolean offer(E e) {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            // 当队列元素个数与数组长度相等时,无法添加元素
            if (count == items.length)
                return false;
            else {
                enqueue(e);
                return true;
            }
        } finally {
            lock.unlock();
        }
    }

items: final Object[] items;

offer(E, long, java.util.concurrent.TimeUnit)
    public boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException {

        checkNotNull(e);
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            // 等待一段时间
            while (count == items.length) {
                if (nanos <= 0)
                    return false;
                nanos = notFull.awaitNanos(nanos);
            }
            // 入队列
            enqueue(e);
            return true;
        } finally {
            lock.unlock();
        }
    }

enqueue(E)

入队操作

   private void enqueue(E x) {
        // assert lock.getHoldCount() == 1;
        // assert items[putIndex] == null;
       	//  获取当前数组
        final Object[] items = this.items;
         // 通过putIndex索引对数组进行赋值
        items[putIndex] = x;
        //  索引自增,如果已是最后一个位置,重新设置 putIndex = 0;
        if (++putIndex == items.length)
            putIndex = 0;
        //  队列中元素数量加1
        count++;
        //  唤醒调用take()方法的线程,执行元素获取操作。
        notEmpty.signal();
    }
// items index for next put, offer, or add
int putIndex;
// Condition for waiting takes
private final Condition notEmpty;
阻塞方法
阻塞式插入元素:put(E e)
    public void put(E e) throws InterruptedException {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            // 当队列元素个数与数组长度相等时,无法添加元素
            while (count == items.length)
                 // 将当前调用线程挂起,添加到notFull条件队列中等待唤醒
                notFull.await();
            // 如果队列没有满直接添加
            enqueue(e);
        } finally {
            lock.unlock();
        }
    }

put方法是一个阻塞的方法,如果队列元素已满,那么当前线程将会被notFull condition条件对象挂起并添加到等待队列中,直到队列又重新满足notfull未满条件,即有队列元素出队了时,才会被唤醒,继续执行put添加操作。

但如果队列元素本身就没有满,那么就直接调用入队enqueue(e)方法将元素加入到数组队列中。

线程执行存在两种情况:

  • 队列已满,那么新到来的执行put操作的线程将被添加到notFull的条件队列中等待;
  • 队列未满,当有线程执行了移除队列元素操作,移除成功同时唤醒put线程
阻塞式删除:take()
    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            // 队列长度为0
            while (count == 0)
                // 阻塞
                notEmpty.await();
            // 如果队列有元素执行删除操作 
            return dequeue();
        } finally {
            lock.unlock();
        }
    } 
	/** Condition for waiting takes */
    private final Condition notEmpty;

dequeue():删除队头元素并返回

    private E dequeue() {
        // assert lock.getHoldCount() == 1;
        // assert items[takeIndex] != null;
        // 获取当前数组
        final Object[] items = this.items;
        // 获取要删除的对象
        @SuppressWarnings("unchecked")
        E x = (E) items[takeIndex];
        //将数组中takeIndex索引位置设置为null(删除)
        items[takeIndex] = null;
         //takeIndex索引加1并判断是否与数组长度相等,
         //如果相等说明已到尽头,恢复为0
        if (++takeIndex == items.length)
            takeIndex = 0;
        //数组元素个数减1
        count--;
        if (itrs != null)
            //同时更新迭代器中的元素数据
            itrs.elementDequeued();
        //删除了元素说明队列有空位,唤醒notFull条件对象添加线程,执行添加操作
        notFull.signal();
        return x;
    }

take方法执行时,队列中有元素就删除,队列为空就阻塞等待(注意:这个阻塞是可以中断的),并且是加入notEmpty条件队列等待。

如果有新的线程通过put方法添加数据时,那么同时也会唤醒take线程,即在入队方法(enqueue)中唤醒notEmpty condition条件等待队列中的线程执行移除元素操作。

如果有新的线程通过take方法移除数据时,在出队方法(dequeue)中唤醒notFull condition条件等待队列中的线程,去执行插入元素操作。

总结
  1. 当执行put操作时,如果队列满了,就加入到notFull的等待队列中,此时若有任务通过take操作移除元素时,会唤醒notFull condition条件等待队列中的线程,执行put操作

  2. 当执行take操作时,如果队列为空,就加入到notEmpty的等待队列中,此时若有任务通过put操作插入元素时,会唤醒notEmpty condition条件等待队列中的线程,执行take操作

Week 3

Two-phase Termination(两阶段终止)模式

  • 定义:

    1. 发出信号,告知正在运行的线程将被终止。
    2. 接收到此信号的线程,做完善后工作,停止运行。
  • 核心思想:既要保证线程灵活的切换运行状态,又要保证线程优雅的处理完当前任务

Thread.interrupt()

If this thread is blocked in an invocation of the wait(), wait(long), or wait(long, int) methods of the Object class, or of the join(), join(long), join(long, int), sleep(long), or sleep(long, int), methods of this class, then its interrupt status will be cleared and it will receive an InterruptedException.

interrupt() 方法是将一个处于等待状态的线程唤醒,只不过这种唤醒方式会导致线程会抛出InterruptedException异常,从而导致其终止运行,但只要此异常被捕获并处理,那么线程依然可以继续运行。

线程池关闭

ThreadPoolExcutor

ThreadPoolExecutor#shutdownNow

    public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            // 将线程池的状态改变为“STOP”
            advanceRunState(STOP);
            // 对线程池中的所有线程都进行中止(interrupt)操作
            interruptWorkers();
            tasks = drainQueue();
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        return tasks;
    }
interruptWorkers
    private void interruptWorkers() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers)
                w.interruptIfStarted();
        } finally {
            mainLock.unlock();
        }
    }
	// Worker
  void interruptIfStarted() {
            Thread t;
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                }
            }
        }
Worker线程
 private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        /**
         * This class will never be serialized, but we provide a
         * serialVersionUID to suppress a javac warning.
         */
        private static final long serialVersionUID = 6138294804551838833L;

        /** Thread this worker is running in.  Null if factory fails. */
        final Thread thread;
        /** Initial task to run.  Possibly null. */
        Runnable firstTask;
        /** Per-thread task counter */
        volatile long completedTasks;
     
     ...
   }
Worker.run()
public void run() {
      runWorker(this);
}

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        task.run();
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }
ThreadPoolExecutor#getTask
private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            // 这一步返回null(rs = STOP)
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);

            // Are workers subject to culling?
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }
// 正常逻辑退出  
completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
ThreadPoolExecutor#processWorkerExit
 private void processWorkerExit(Worker w, boolean completedAbruptly) {
     	// false
        if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
            decrementWorkerCount();

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            completedTaskCount += w.completedTasks;
            // 从workers中移除当前Worker
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }

        tryTerminate();

        int c = ctl.get();
        if (runStateLessThan(c, STOP)) {
            if (!completedAbruptly) {
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                if (workerCountOf(c) >= min)
                    return; // replacement not needed
            }
            addWorker(null, false);
        }
    }

ThreadPoolExecutor#shutdown

    public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            // 设置线程池状态SHUTDOWN
            advanceRunState(SHUTDOWN);
            interruptIdleWorkers();
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }
ThreadPoolExecutor#interruptIdleWorkers(boolean)
private void interruptIdleWorkers(boolean onlyOne) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers) {
                Thread t = w.thread;
                if (!t.isInterrupted() && w.tryLock()) {
                    try {
                        t.interrupt();
                    } catch (SecurityException ignore) {
                    } finally {
                        w.unlock();
                    }
                }
                if (onlyOne)
                    break;
            }
        } finally {
            mainLock.unlock();
        }
}

总结

  1. shutdown 方法执行后,线程池就会拒绝接收新的任务,但是会等待线程池中正在执行的任务和已经进入阻塞队列的任务都执行完之后,才最终关闭线程池

  2. shutdownNow方法就比较暴力了,线程池,阻塞队列里的任务通通不允许再执行了,不过会返回阻塞队列中没执行完的任务,也算留有余地。

Week 4

Promise模式

Promise模式是一种异步编程模式. 其核心的是, 执行任务A(例如烧水)时, 其执行结果可通过一个promise对象来获取。因而并不需要同步等待任务A结束再去执行任务B。任务B可以与任务A同时执行,它需要任务A执行的结果时, 通过这个promise对象获取即可(例如去听蜂鸣声)。

在程序中,这意味着程序会运行更快,因为Promise可以避免不必要的等待, 提高对并发的支持。

Promise模式主要由4部分构成: 1:Promisor、 2:Executor、 3:Promise, 4:Result。

  1. Executor需要在Promisor的create()方法中去执行。
  2. create()的返回值就是Promise。
  3. Result则是Promise中get()方法的返回值。

辅助类:

/**
 * 泡茶
 *
 * @author ajin
 */
public class BoilWater {

    boolean status = false;

    public boolean isStatus() {
        return status;
    }

    public void setStatus(boolean status) {
        this.status = status;
    }
}

/**
 * 准备茶杯和茶叶
 *
 * @author ajin
 */
public class TeaAndCup {

    boolean status = false;

    public boolean isStatus() {
        return status;
    }

    public void setStatus(boolean status) {
        this.status = status;
    }
}

Promisor类

public class Promisor {

    public static Future<Object> create(long startTime) {
        // 1. 定义任务
        FutureTask<Object> futureTask = new FutureTask<>(() -> {
            System.out.println("开始烧水,当前用时" + (System.currentTimeMillis() - startTime) + "ms");
            BoilWater boilWater = new BoilWater();
            Thread.sleep(15000);
            boilWater.setStatus(true);
            return boilWater;
        });
        // 2. Executor执行
        new Thread(futureTask).start();
        // 3. 返回Promise
        return futureTask;
    }

}

这里和我们图上画的,Promisor.create()方法的职责是一致的

public static void main(String[] args) throws InterruptedException, ExecutionException {
        long startTime = System.currentTimeMillis();
        // 获取Promise
        Future<Object> promise = Promisor.create(startTime);

        System.out.println("开始准备茶杯茶叶,需要3分钟,当前用时:" + (System.currentTimeMillis() - startTime) + "ms");
        TeaAndCup teaAndCup = new TeaAndCup();
        Thread.sleep(3000);
        teaAndCup.setStatus(true);
        System.out.println("结束茶杯茶叶的准备,当前用时:" + (System.currentTimeMillis() - startTime) + "ms");

        if (!promise.isDone()) {
            System.out.println("茶杯茶叶结束,等待烧水完成");
        }

        // 获取执行结果(可能阻塞)
        BoilWater boilWater = (BoilWater) promise.get();
        System.out.println("获取到烧水完成信号,当前用时:" + (System.currentTimeMillis() - startTime) + "ms");

        System.out.println("准备工作完成,开始泡茶");

        System.out.println("一共用时:" + (System.currentTimeMillis() - startTime) + "ms");


    }

开始准备茶杯茶叶,需要3分钟,当前用时:87ms 开始烧水,当前用时88ms 结束茶杯茶叶的准备,当前用时:3099ms 茶杯茶叶结束,等待烧水完成 获取到烧水完成信号,当前用时:15104ms 准备工作完成,开始泡茶 一共用时:15104ms

Promise模式在FutureTask源码中的应用场景

FutureTask继承关系

  • 父类:RunnableFuture
    • Runnable
    • Future

构造器

成员变量:

    private volatile int state;
    private static final int NEW          = 0;
    private static final int COMPLETING   = 1;
    private static final int NORMAL       = 2;
    private static final int EXCEPTIONAL  = 3;
    private static final int CANCELLED    = 4;
    private static final int INTERRUPTING = 5;
    private static final int INTERRUPTED  = 6;

    /** The underlying callable; nulled out after running */
    private Callable<V> callable;
    /** The result to return or exception to throw from get() */
    private Object outcome; // non-volatile, protected by state reads/writes
    /** The thread running the callable; CASed during run() */
    private volatile Thread runner;
    /** Treiber stack of waiting threads */
    private volatile WaitNode waiters;
    /**
     * Creates a {@code FutureTask} that will, upon running, execute the
     * given {@code Callable}.
     *
     * @param  callable the callable task
     * @throws NullPointerException if the callable is null
     */
    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }

    /**
     * Creates a {@code FutureTask} that will, upon running, execute the
     * given {@code Runnable}, and arrange that {@code get} will return the
     * given result on successful completion.
     *
     * @param runnable the runnable task
     * @param result the result to return on successful completion. If
     * you don't need a particular result, consider using
     * constructions of the form:
     * {@code Future<?> f = new FutureTask<Void>(runnable, null)}
     * @throws NullPointerException if the runnable is null
     */
    public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;       // ensure visibility of callable
    }

Runnale 和Callable的比较:

虽然Callable与Runnable很相似,但是Callable可以抛出异常。而更重要的区别是,Callable中的call()方法相比Runnable中的run()方法,前者有返回值,而后者没有。

@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

适配器的使用也需要好好看下:将Runnable适配成Callable,先组合Runnable,再在Callable.call()方法中调用Runnable.run()

    static final class RunnableAdapter<T> implements Callable<T> {
        final Runnable task;
        final T result;
        RunnableAdapter(Runnable task, T result) {
            this.task = task;
            this.result = result;
        }
        public T call() {
            task.run();
            return result;
        }
    }

FutureTask#run()

    public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            // 重点关注Callable
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    // 核心
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    // 核心
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }
FutureTask#set(V)

把传入的callable的返回值,在线程安全的前提下,赋值给了FutureTask的成员变量outcome。

这就意味着,启动Future会通过Callable来获取一个结果,并把这个结果放到成员变量outcome,等待着获取。

    protected void set(V v) {
        // 将状态从NEW 改成 COMPLETING
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = v;
            UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
            finishCompletion();
        }
    }

此时我们理解了,为什么要在构造方法中创建一个Callable对象?

因为Callable对象,可以获取返回值,并且存到变量outcome中。

private Object outcome; // non-volatile, protected by state reads/writes

用一张图来描述FutureTask.run()方法执行逻辑:

![](liutianruo-2019-go-go-go.oss-cn-shanghai.aliyuncs.com/note/14. FutureTask.run()理解.png)

FutureTask.get()

获取结果。

    public V get() throws InterruptedException, ExecutionException {
        int s = state;
        // 如果尚未完成,当前线程则等待
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        return report(s);
    }
FutureTask#awaitDone

等待线程执行完成。

 private int awaitDone(boolean timed, long nanos)
        throws InterruptedException {
        final long deadline = timed ? System.nanoTime() + nanos : 0L;
        WaitNode q = null;
        boolean queued = false;
        for (;;) {
            if (Thread.interrupted()) {
                removeWaiter(q);
                throw new InterruptedException();
            }

            int s = state;
            // 线程完成,直接返回
            if (s > COMPLETING) {
                if (q != null)
                    q.thread = null;
                return s;
            }
            else if (s == COMPLETING) // cannot time out yet
                Thread.yield();
            else if (q == null)
                q = new WaitNode();
            else if (!queued)
                queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                                                     q.next = waiters, q);
            else if (timed) {
                nanos = deadline - System.nanoTime();
                if (nanos <= 0L) {
                    removeWaiter(q);
                    return state;
                }
                LockSupport.parkNanos(this, nanos);
            }
            else
                // 等待
                LockSupport.park(this);
        }
    }
FutureTask.report(int)
   private V report(int s) throws ExecutionException {
        Object x = outcome;
        if (s == NORMAL)
            return (V)x;
        if (s >= CANCELLED)
            throw new CancellationException();
        throw new ExecutionException((Throwable)x);
    }

![](liutianruo-2019-go-go-go.oss-cn-shanghai.aliyuncs.com/note/15. FutureTask理解.png)

总结

  1. Promise模式适用场景: 需要异步启动线程提升性能,又要获取异步线程的返回结果

  2. Promise模式的构成:1:Promisor、2:Executor、3:Promise、4:Result。

  3. Promise模式在Java中实现:通过FutureTask的构建与get()方法的调用。

Week 5

生产者-消费者模式

生产者和消费者模式中有三个重要角色,生产者、任务队列、消费者。生产者提交任务到队列,消费者从队列中取出任务进行处理。

现在流行的微服务系统开发中,经常会用到的MQ中间件也是生产者和消费者模式。

过饱问题

单位时间内,生产者生产的速度大于消费者消费的速度,导致任务不断堆积到阻塞队列中,队列堆满只是时间问题。

必要:我们要合理的设置队列大小,不能因为生产者生产任务的速度过快,导致阻塞队列被塞满!

场景一

1、消费者每天处理的量比生产者生产的少;如生产者每天1万条,消费者每天只能消费5千条。

2、解决办法:消费者加机器

3、原因:生产者没法限流,因为要一天内处理完,只能消费者加机器

场景二

1、消费者每天处理的量比生产者生产的多。

2、系统高峰期生产者速度太快,把队列塞爆了

3、解决办法:适当的加大队列

4、原因:消费者一天的消费能力已经高于生产者,那说明一天之内肯定能处理完,保证高峰期别把队列塞满就好

场景三

1、消费者每天处理的量比生产者生产的多

2、条件有限或其他原因,队列没法设置特别大

3、系统高峰期生产者速度太快,把队列塞爆了

4、解决办法:生产者限流

5、原因:消费者一天的消费能力高于生产者,说明一天内能处理完,队列又太小,那只能限流生产者,让高峰期塞队列的速度慢点

JDK原生线程池中的生产者与消费者模式

线程池基本概念

  1. Executor:代表线程池的接口,有个execute()方法,扔进去一个Runnable类型对象,就可以分配一个线程给你执行
  2. ExecutorService:这是Executor的子接口,相当于是一个线程池的接口,有销毁线程池等方法
  3. Executors:线程池的辅助工具类,辅助入口类,可以通过Executors来快捷的创建你需要的线程池
  4. ThreadPoolExecutor:这是ExecutorService的实现类,这才是正儿八经代表一个线程池的类,一般在Executors里创建线程池的时候,内部都是直接创建一个ThreadPoolExecutor的实例对象返回的,然后同时给设置了各种默认参数
ThreadPoolExecutor核心参数理解
  • corePoolSize:线程池里的核心线程数量
  • maximumPoolSize:线程池里允许有的最大线程数量
  • keepAliveTime:如果线程数量大于corePoolSize的时候,多出来的线程会等待指定的时间之后就被释放掉,这个就是用来设置空闲线程等待时间的
  • unit:这个是上面那个keepAliveTime的单位
  • workQueue:这个是说,通过ThreadPoolExecutor.execute()方法扔进来的Runnable工作任务,如果所有线程都繁忙,会进入一个队列里面去排队,这就是那个队列(阻塞队列)
  • threadFactory:如果需要创建新的线程放入线程池的时候,就是通过这个线程工厂来创建的
  • handler:假如说上面那个workQueue是有固定大小的,如果往队列里扔的任务数量超过了队列大小,咋办?就用这个handler来处理(拒绝策略)

提交任务到线程池

public void execute(Runnable command) {
    	// 如果任务为null,抛出NPE异常
        if (command == null)
            throw new NullPointerException();
       	// 原子变量ctl共同存储 线程状态+线程个数。说白了就是用一个int型变量存储两个数,高3位表示线程状态,后面29位表示线程的个数
        int c = ctl.get();
    
    	// 判断当前线程数 是否小于 核心线程数, 如果小于,开启新的线程执行任务
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
    	// 如果线程池处于RUNNING状态,添加任务到阻塞队列
        if (isRunning(c) && workQueue.offer(command)) {//成功添加任务
             //重新获取下ctl的值,因为把任务添加到队列时,线程的状态可能已经改变,这里重新获取下
            int recheck = ctl.get();
           	// 线程状态不再是RUNNING状态,则移除任务
            if (!isRunning(recheck) && remove(command))
                // 拒绝执行
                reject(command);
            else if (workerCountOf(recheck) == 0)
                // 如果当前线程池一个线程也没有,添加一个线程
                addWorker(null, false);
        }
    	// 如果队列满了,则添加新的线程(非核心线程),如果新增失败,执行拒绝策略
        else if (!addWorker(command, false))// 如果此时不是RUNNING状态,则新增Work线程失败,直接执行下面的拒绝策略
            reject(command);
    }

消费任务

Worker线程用来消费任务。

    private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
ThreadPoolExecutor#runWorker(Worker)
  • ThreadPoolExecutor#getTask
  • task.run()

task来源

如果提交任务后,触发了新的Worker线程的创建,则Worker线程会将该任务作为自己的firstTask来执行。

 Thread wt = Thread.currentThread();
        // Worker的第一个任务
        Runnable task = w.firstTask;
        w.firstTask = null;// 将firstTask指向null
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {

如果firstTask=null,则调用getTask()方法获取task

getTask核心源码

try {
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }

Week 6

Active Object模式

Active Object模式,主动对象模式

一种异步编程模式,就是将方法的调用和方法的执行分开,放在两个线程中执行。

同步调用与异步调用

System.gc()场景

通常,我们想自己控制垃圾回收时,可能会调用System.gc()这个方法,但是我们也都不清楚什么时候会进行垃圾回收,调用完之后只是给jvm发了个垃圾回收的请求,并不意味着马上就会进行一轮垃圾回收,具体什么时候执行垃圾回收,要看jvm自己的安排。

Active Object模式

主动对象模式,核心思想,就是通过将调用和执行过程分离,实现异步编程模式

这样做有两个优点,第一提高了系统的并发能力和吞吐量;第二降低了系统的耦合性,如果具体的执行逻辑要修改,不会影响到调用方,维护起来比较方便。

为了简化异步调用的复杂性,主动对象模式分离了方法的执行和调用。使用这个模型,无论一个对象中是否有独立的线程进行异步调用,客户端从外部访问它时,感觉是一样的。

![](liutianruo-2019-go-go-go.oss-cn-shanghai.aliyuncs.com/note/17.Act… Object模式.png)

1)Future

在主动对象模式中,客户端提交任务后,Proxy会立马返回一个Future对象。客户端拿到这个对象,在需要时就可以通过Future对象来获取任务执行结果。

这个组件所需的功能,可以由jdk的java.util.concurrent包下的Futrue类来实现。

2)MethodRequest

当请求发给Proxy时,Proxy不会直接处理这个请求,而是会将请求参数等上下文信息封装为一个MethodRequest对象。

说白了,MethodRequest就是用于封装异步任务的。可以用jdk的java.lang.Runnale或者concurrent包下的Callable,具体是用Runnale还是Callable,看你是否需要返回值。

Callable是有返回值的

Runnable/Callable

3)ActivationQueue

ActivationQueue是主动对象模式中的任务缓冲区。

说白了,就是一块内存存储空间,最好是一个有顺序的队列。

主要用途就是缓存不能马上被执行的MethodRequest对象,然后等待工作线程空闲时,从其中读取出任务来执行。

实现这个任务缓冲区的选择比较多,jdk帮我们实现了一些队列可以直接使用,比如java.util.concurrent包下的LinkedBlockingQueue。

阻塞队列 BlockingQueue

4)Scheduler

Scheduler即调度器。

Scheduler之于主动对象模式,就像人的大脑,电脑的CPU。控制着MethodRequest对象的执行时机和过程

这个组件你可以使用ThreadPoolExecutor,就是线程池,当然如果你有其他需求,也可以自己实现并发包下的ExecutorService接口。

ThreadPoolExecutor 线程池

5)Servant:

Servant实现了Proxy所暴露的异步方法。并且负责执行Proxy所暴露的异步方法对应的任务(任务执行)

主动对象模式在线程池源码中的应用

我们再把图片放出来:

![](liutianruo-2019-go-go-go.oss-cn-shanghai.aliyuncs.com/note/17.Act… Object模式.png)

组件Future可用jdk的concurrent包下的Future类来实现;

MethodRequest可用jdk的java.lang.Runnale或者concurrent包下的Callable类来实现;

ActivationQueue可用concurrent包下的LinkedBlockingQueue来实现;

Scheduler组件可用ThreadPoolExecutor来实现调度功能。

线程池中的对应理解

请求上下文封装(MethodRequest)

Runnable 、Calllable

任务提交

1)void execute(Runnable command);

2) Future submit(Callable task);

3) Future submit(Runnable task, T result);

4)Future<?> submit(Runnable task);

最终底层实现:execute(Runnable command)方法

返回Future对象

    public <T> Future<T> submit(Runnable task, T result) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task, result);
        execute(ftask);
        return ftask;
    }

    /**
     * @throws RejectedExecutionException {@inheritDoc}
     * @throws NullPointerException       {@inheritDoc}
     */
    public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }

提交任务即可返回Future对象

ActivationQueue缓冲区

BlockingQueue

提交和执行分离

客户端不需要去关注具体的执行过程的细节,只需要关注任务本身,如提交任务、获取结果、取消任务。

Week 7

线程池

线程的创建

线程的优先性:线程对象本身以及线程的调用栈都是要占用内存的,而操作系统的内存是有限的,这决定了我们能创建的线程数也是有限制的,而无限制的创建线程会使内存不断消耗最终超过内存上限从而报错。

能创建多少线程数是有一个计算公式的:可创建的线程数 =(进程的最大内存 – JVM分配的内存 – 操作系统保留的内存)/ 线程栈大小

在Java语言中,当我们每创建一个线程的时候,Java虚拟机就会在JVM内存中创建出一个Thread对象,与此同时创建一个操作系统的线程,最终在系统底层映射的是操作系统的本地线程(Native Thread),在windows系统中是1对1映射(即一个Java线程映射一个操作系统线程),在Linux系统是N对M映射(即多个Java线程映射多个操作系统线程,N与M不完全相等)

操作系统的线程使用的内存并不是JVM分配的内存,而是系统中剩余的内存。

如果创建线程超出限制,则会抛出异常:java.lang.OutOfMemoryError: unable to create new native thread

线程池优点:通过线程池可以避免重复创建线程,实现线程复用(资源有限)

线程池的创建

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

只有一个线程的线程池,如果提交超过一个任务到线程池中,那么任务会被保存在队列中。等工作线程空闲了就从队列中取出其他任务进行执行。获取任务遵循队列的先进先出原则

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      // SynchronousQueue是一个缓存值为1的阻塞队列,通俗的说,它根本就没有缓冲任务的能力
                                      new SynchronousQueue<Runnable>());
    }

理论上能创建Integer.MAX_VALUE个线程(线程数可变)

如果有空闲的线程能够被复用,就还是优先使用可被复用的线程。

当目前所有的线程都处于工作状态,但是仍然有新任务被提交了,那么就会创建新的线程来调度新任务。

没有空闲线程时,则会创建新线程

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

带固定数量线程的线程池:线程池中的线程数量从线程池一开始创建就固定不变

    public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
        return new DelegatedScheduledExecutorService
            (new ScheduledThreadPoolExecutor(1));
    }

只有一个线程的可定时调度任务的线程池 :ScheduledExecutorService

   public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

可指定核心线程数的可定时调度任务的线程池

线程池的核心是工作线程和任务队列

ThreadPoolExecutor核心参数

corePoolSize

含义:核心线程的个数

maximumPoolSize

含义:最大线程数

作用:当核心线程数被任务占满,且其中的任务缓冲队列也满了的时候,让新提交的任务仍旧可以执行,不至于立马就被拒绝。保证线程池可以通过创建新工作线程(非核心线程)的方式来接受并执行任务。

思考:已经有corePoolSize参数了,为啥还要来一个maximumPoolSize???

这是一种“弹性设计”,可以让线程池在应对突发流量时稳稳当当。

如果没有设置maximumPoolSize,只设置了一个corePoolSize,在系统平时的任务不多,比如每秒执行10个以内的任务的低并发场景中,是没啥大问题的。

然而,如果每个月初月末流量突增,达到每秒100以上的并发时。我们总不能把corePoolSize设置成原先的10倍吧!如果只是为了应对短暂的高峰流量,就把常规状态下的线程翻了10倍。那么在平常没有那么多的任务可以执行的情况下,创建大量的工作线程并保持常态,就是典型的资源浪费。

maximumPoolSize,本质上也是一种“伸缩思想

keepAliveTime

含义:超过核心线程池(线程总数小于等于maximumPoolSize)并且处于空闲状态的那些线程的存活时间

这部分线程给他们分配了一个keepAliveTime参数,意义在于,当没有可处理的任务时,让它们别急着回收,而是先等会儿,看有没有任务来了,如果有就去执行任务,否则就等达到了keepAliveTime之后,对工作线程进行回收。

unit

含义:keepAliveTime的时间单位(TimeUnit类型)

一般而言,我们给线程池的keepAliveTime指定的时间单位是秒或者分

workQueue

含义:用于保存提交的待执行状态的任务的阻塞队列

  • 基于数据的有界队列ArrayBlockingQueue
  • 基于链表的无界队列LinkedBlockingQueue
  • 有且只有一个元素的同步队列SynchronousQueue
  • 优先级队列PriorityBlockingQueue

通过一个队列,让等待执行的任务先进行排队,给线程池提供了一个缓冲的空间,能够让线程池在一定的程度内接受任务,而不是直接就拒绝任务的提交

“生产者-消费者模式“

ThreadFactory

含义:创建线程的工厂,它的作用是当需要增加工作线程时,不需要通过new Thread的方式创建,而是通过ThreadFactory接口的实现类的newThread方法来创建

public interface ThreadFactory {

    /**
     * Constructs a new {@code Thread}.  Implementations may also initialize
     * priority, name, daemon status, {@code ThreadGroup}, etc.
     *
     * @param r a runnable to be executed by new thread instance
     * @return constructed thread, or {@code null} if the request to
     *         create a thread is rejected
     */
    Thread newThread(Runnable r);
}

RejectedExecutionHandler

含义:线程池的拒绝策略

线程池自定义创建

指定线程数量

根据任务关注点的不同,将任务划分为:CPU密集型(或者叫计算密集型)、I/O密集型两大类

CPU密集型

对于CPU密集型的任务,由于CPU计算速度很快,任务在短时间内就能够通过CPU超强的计算能力执行完成,因此我们可以设置核心线程数corePoolSize为N(CPU个数)+1,之所以要设置为CPU个数加1,主要原因在于为了防止某些情况下出现等待情况导致没有线程可用,比如说发生了缺页中断时,就会出现等待的情况。因此设置一个额外的线程,可以保证继续使用CPU时间片。

IO密集型

而对于I/O密集型的任务,可以为最大线程数多设置一些线程。原因在于相比CPU密集型任务,I/O密集型任务在执行过程中由于等待I/O结果花费的时间要明显大于CPU计算所花费的时间,而且处于I/O等待状态的线程并不会消耗CPU资源,因此可以多设置一些线程

一般情况下,我们将其设置为CPU个数的倍数,常见的玩儿法是设置为**N(CPU个数)2*。

对于I/O密集型任务,还要注意核心线程数不用设置的很大,原因在于I/O操作本身会导致上下文切换的发生,尤其是阻塞式I/O。因此建议将I/O密集型的核心线程数corePoolSize限制为1,最大线程数maximumPoolSize设置为N(CPU个数)*2。

当线程池中只要一个线程的时候,能够从容应对提交的任务,此时的上下文切换相当少。然后随着任务逐渐增加,再慢慢的增加线程数量至最大线程数。这样做既不浪费资源,还很灵活的支持了任务增加的场景。

工作队列

ThreadFactory

参考Netty、Tomcat,它们都提供了优秀的自定义的线程工厂的实现

线程池拒绝策略

一般来说,直接使用AbortPolicy抛出异常即可,但是如果说我们要求即便触发了拒绝策略,任务也得执行完成不能丢弃,那么选择CallerRunsPolicy拒绝策略即可。

如果说这几种拒绝策略都满足不了我们的需求的话,就可以自定义拒绝策略,只需要实现RejectedExecutionHandler接口即可实现自定义的拒绝策略。

IO密集型线程池样例:

作为Spring Bean交给Spring IoC容器管理

Week 8

ThreadLocal

变量共享会存在线程不安全的问题,如果变量不共享,则会不会出现线程不安全问题呢?

让每个线程都拥有自己的对象副本,也就不存在多线程变量的共享问题

ThreadLocal基本原理

ThreadLocal底层数据结构:

每个线程Thead里的ThreadLocalMap里可以存储多个ThreadLocal本地化对象,且每一个ThreadLocal本地化对象是通过自己的threadLocalHashCode来计算数组下标,分配到下标对应Entry数组中,从而可以进行本地化对象获取和设置操作。

内存泄漏问题

只要Thread对象被垃圾回收,那么ThreadLocalMap就能被回收,所以就不会出现内存泄漏的情况

当thread线程执行完任务退出之后,该线程里所持有的ThreadLocalMap的对象也就没有了强引用,那么由于ThreadLocalMap没有强引用,所以就可以被JDK垃圾回收器回收了,那么ThreadLocalMap里面所包含ThreadLocal也就回收掉

线程池场景就不一样了

线程池里的核心线程Thread执行完任务之后,是不会退出的,可以循环使用的,那就说明线程池里每个核心线程Thread对应的ThreadLocalMap一直是强引用关系,所以线程Thread对应的ThreadLocal是不会自动回收的。

ThreadLocalMap中的Entry中的key是属于WeakReference弱引用,随着JDK的垃圾回收ThreadLocal可以自动被回收

在JDK触发垃圾回收之后,对应的ThreadLocal确实可以被垃圾回收掉,变为null值,但是被自动回收的ThreadLocal所对应value值是不能被自动回收的。(对应的value还是被Entry引用着

线程池的核心线程Thread是循环利用的,每个线程Thread所对应的ThreadLoalMap被强引用着,所以每个线程Thread的ThreadLoalMap不能被回收,但是ThreadLoalMap里含有多个ThreadLocal-value的Entry,虽然ThreadLocal-key是弱引用可以被垃圾回收器自动回收,但是ThreadLocal对应的value是不能被回收的,所以说有内存泄露的情况可能性

        private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                // threadLocal=null
                return getEntryAfterMiss(key, i, e);
        }

假设ThreadLocal被清空了,是null了,就会走到getEntryAfterMiss方法

       private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

            while (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == key)
                    return e;
                if (k == null)
                    // 把当前线程的ThreadLocal(null)所对应的value设置为null,同时把对应的Entry也设置为null
                    // 同时遍历所有的为ThreadLocal为null的value和对应的Entry都设置为null
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

只有在调用ThreadLocal的get、set、remove方法的时候才会触发expungeStaleEntry方法的执行,才会把被自动垃圾回收的ThreadLocal为null所对应的value和Entry才会设置为null。

正常的情况是不会出现内存泄露的,但是如果我们没有调用ThreadLocal对应的set、get、remove方法就不会把对应的value和Entry设置为null,这样就可能会出现内存泄露情况。

那如何避免内存泄露的情况呢?那就是我们在不使用的时候就调用一下ThreadLocal的remove方法,来加快垃圾回收,避免内存泄露。

开源框架对ThreadLocal的使用

zuul

ZuulServlet
 @Override
    public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
        try {
            init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);

            // Marks this request as having passed through the "Zuul engine", as opposed to servlets
            // explicitly bound in web.xml, for which requests will not have the same data attached
            RequestContext context = RequestContext.getCurrentContext();
            context.setZuulEngineRan();

            try {
                preRoute();
            } catch (ZuulException e) {
                error(e);
                postRoute();
                return;
            }
            try {
                route();
            } catch (ZuulException e) {
                error(e);
                postRoute();
                return;
            }
            try {
                postRoute();
            } catch (ZuulException e) {
                error(e);
                return;
            }

        } catch (Throwable e) {
            error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
        } finally {
            // 避免内存泄漏
            RequestContext.getCurrentContext().unset();
        }
    }
init

com.netflix.zuul.ZuulRunner#init

public void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {

        RequestContext ctx = RequestContext.getCurrentContext();
        if (bufferRequests) {
            // 基于ThreadLocal的RequestContext用来保存请求和响应的信息,确保只对当前线程生效,保证了线程安全
            ctx.setRequest(new HttpServletRequestWrapper(servletRequest));
        } else {
            ctx.setRequest(servletRequest);
        }

        ctx.setResponse(new HttpServletResponseWrapper(servletResponse));
    }

com.netflix.zuul.context.RequestContext#getCurrentContext

   public static RequestContext getCurrentContext() {
        if (testContext != null) return testContext;

        RequestContext context = threadLocal.get();
        return context;
    }

	 protected static final ThreadLocal<? extends RequestContext> threadLocal = new ThreadLocal<RequestContext>() {
        @Override
        protected RequestContext initialValue() {
            try {
                return contextClass.newInstance();
            } catch (Throwable e) {
                throw new RuntimeException(e);
            }
        }
    };

Spring事务管理

  • org.springframework.transaction.interceptor.TransactionInterceptor

TransactionInterceptor

	@Override
	@Nullable
	public Object invoke(MethodInvocation invocation) throws Throwable {
		// Work out the target class: may be {@code null}.
		// The TransactionAttributeSource should be passed the target class
		// as well as the method, which may be from an interface.
		Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);

		// Adapt to TransactionAspectSupport's invokeWithinTransaction...
		return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
	}
TransactionAspectSupport#invokeWithinTransaction
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
  • createTransactionIfNecessary

    • TransactionAspectSupport#prepareTransactionInfo

      	TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification);
      		if (txAttr != null) {
      			// We need a transaction for this method...
      			if (logger.isTraceEnabled()) {
      				logger.trace("Getting transaction for [" + txInfo.getJoinpointIdentification() + "]");
      			}
      			// The transaction manager will flag an error if an incompatible tx already exists.
      			txInfo.newTransactionStatus(status);
      		}
      		else {
      			// The TransactionInfo.hasTransaction() method will return false. We created it only
      			// to preserve the integrity of the ThreadLocal stack maintained in this class.
      			if (logger.isTraceEnabled()) {
      				logger.trace("Don't need to create transaction for [" + joinpointIdentification +
      						"]: This method isn't transactional.");
      			}
      		}
      
      		// We always bind the TransactionInfo to the thread, even if we didn't create
      		// a new transaction here. This guarantees that the TransactionInfo stack
      		// will be managed correctly even if no transaction was created by this aspect.
      		txInfo.bindToThread();
      		return txInfo;
      

      txInfo.bindToThread()

      org.springframework.transaction.interceptor.TransactionAspectSupport.TransactionInfo#bindToThread

      		private void bindToThread() {
      			// Expose current TransactionStatus, preserving any existing TransactionStatus
      			// for restoration after this transaction is complete.
      			this.oldTransactionInfo = transactionInfoHolder.get();
      			transactionInfoHolder.set(this);
      		}
      
      	private static final ThreadLocal<TransactionInfo> transactionInfoHolder =
      			new NamedThreadLocal<>("Current aspect-driven transaction");
      

      Spring的事务管理是把事务信息组成一个TransactionInfo对象之后,

      然后把这些信息都保存到了当前线程里的ThreadLocal里,这样再执行后面事务的提交和回滚的时候,都是从当前线程里获取的事务信息的,

      这也说明了spring的事务管理是不能跨线程执行的,否则找不到对应的事务信息啦,最后Spring的事务管理器在处理完一个事务之后,也会把当前的线程所持有的事务信息清理掉

      TransactionAspectSupport#invokeWithinTransaction

      • cleanupTransactionInfo(txInfo);

        	protected void cleanupTransactionInfo(@Nullable TransactionInfo txInfo) {
        		if (txInfo != null) {
        			txInfo.restoreThreadLocalStatus();
        		}
        	}
        	private void restoreThreadLocalStatus() {
        		// Use stack to restore old transaction TransactionInfo.
        		// Will be null if none was set.
        		transactionInfoHolder.set(this.oldTransactionInfo);
        	}
        

Week 9

串行化线程封闭模式

线程封闭就是仅在同一个线程内访问对象,不与其他线程共享。实现了这种效果的技术,统一称为线程封闭(thread confinement),是一种常见的线程安全设计策略。

Week10

主仆模式(Master-Slave)

主要就是为了引出fork join

核心就是一个基于分而治之思想的设计模式。

将一个任务(统计所有打完疫苗的人员名单)分解为若干等同的子任务(统计各部门内所有打完疫苗的人员名单),并由专门的工作者线程(各部门的HR)来并行的执行这些子任务。

而上级部门交待给集团HR的任务结果,是通过整合各个子任务的处理结果而形成的。

而且这些拆分、汇总相关的处理细节,对于上级部门来说是不可见的。这种工作模式即提高了效率,又实现了细节的隐藏。

Master分工

Master:负责原始任务的拆分、并对子任务进行分发和对子任务的结果进行汇总统计。主要方法如下:

  • analysis:这是Master对外暴露的方法,用于接收原始的任务,并返回处理结果

  • createAndStartWorkers:创建并启动Slave,等待Master分配任务

  • dispatchTask:将任务进行拆分,并分配给各个Slave去执行

  • gatherer:将各个子任务的处理结果进行采集合并,形成原始任务的处理结果。

Slave分工

负责对子任务进行处理

ForkJoinPool

java.util.concurrent.ForkJoinPool

since JDK 1.7

分而治之

  1. 任务分解
  2. 并行执行
  3. 逻辑递归
  4. 结果聚合

构造方法


    /**
     * Creates a {@code ForkJoinPool} with the given parameters, without
     * any security checks or parameter validation.  Invoked directly by
     * makeCommonPool.
     */
    private ForkJoinPool(int parallelism,
                         ForkJoinWorkerThreadFactory factory,
                         UncaughtExceptionHandler handler,
                         int mode,
                         String workerNamePrefix) {
        this.workerNamePrefix = workerNamePrefix;
        this.factory = factory;
        this.ueh = handler;
        this.config = (parallelism & SMASK) | mode;
        long np = (long)(-parallelism); // offset ctl counts
        this.ctl = ((np << AC_SHIFT) & AC_MASK) | ((np << TC_SHIFT) & TC_MASK);
    }
  • parallelism:并行数,决定同时允许多少线程同时执行,默认是CPU的核数
  • mode:任务队列的工作模式,有FIFO,LIFO两种,默认LIFO

任务提交

java.util.concurrent.ForkJoinPool#submit(java.lang.Runnable)

底层实现:java.util.concurrent.ForkJoinPool#externalSubmit

ForkJoinPool#externalSubmit(ForkJoinTask)
  • 实例化workQueues

    volatile WorkQueue[] workQueues;

  • 创建一个无附属线程的workQueque,与workQueues中的指针关联,并将task入队,workQueque的top指针递增1

  • 创建一个ForkJoinWorkerThread实例并为其绑定一个新的workQueque进行绑定(此workQueque没数据),并调用其start方法。

ForkJoinWorkerThread

runWorker(WorkQueue)
    final void runWorker(WorkQueue w) {
        w.growArray();                   // allocate queue
        int seed = w.hint;               // initially holds randomization hint
        int r = (seed == 0) ? 1 : seed;  // avoid 0 for xorShift
        for (ForkJoinTask<?> t;;) {
            if ((t = scan(w, r)) != null)
                w.runTask(t);
            else if (!awaitWork(w, r))
                break;
            r ^= r << 13; r ^= r >>> 17; r ^= r << 5; // xorshift
        }
    }

最核心的机制就是提供了窃取功能的双端队列,由于窃取机制的存在,极大的利用了线程资源,不会有空闲的资源存在。并且通过双端队列,出队和入队的实现方式,也避免了锁的争用,并保证了线程安全

Week11

pipeline

Pipeline:流水线

Java 8 Stream API就是Pipeline模式的实现。

Week12

半同步-半异步模式

半同步-半异步:half-sync/half-async

AsyncTask: 用于接收用户的请求,将这些请求要执行的逻辑放入队列之中,也就是将用户的请求异步化。

SyncTask: 用于处理用户请求的执行逻辑。

Queue: 对用户的请求进行缓冲,并保证用户请求的执行的顺序性