Java并发编程(四)

1、线程上下文切换

  线程的上下文切换:任务的状态保存及在加载。

  • 上下文:线程切换时CPU寄存器和程序计数器所保存的当前线程信息。
  • 寄存器:CPU内部容量较小但速度很快的内存区域。寄存器通过对常用值的快速访问来加快计算机程序运行的速度。
  • 程序计数器:一个专门的寄存器,用于表明指令序列中CPU正在执行的位置。存储的值为正在执行的指令的位置或下一个将被执行的指令的位置。

1.1 上下文切换

  上下文切换:内核在CPU上对进程或线程进行切换。切换流程如下:‘’

  (1)挂起一个进程,将这个进程在CPU中的状态存储于内存的PCB(进程控制块)中。

  (2)在PCB中检索下一个进程的上下文并将其在CPU的寄存器中恢复。

  (3)跳转到程序计数器所指向的位置并恢复该进程。

1.2 引起线程上下文切换的原因

  (1)当前正在执行的任务完成,系统的CPU正常调度下一个任务。

  (2)当前正在执行的任务遇到I/O等阻塞操作,调度器挂起此任务,继续调度下一个任务。

  (3)多个任务并发抢占锁资源,当前任务没有抢到锁资源,被调度器挂起,继续调度下一个任务。

  (4)用户代码挂起当前任务,如执行sleep方法,让出CPU。

  (5)硬件中断。

 

2、Java阻塞队列

  队列是一种只允许在表的前端进行删除操作,在表的后端进行插入操作的线性表。

  阻塞是指操作队列的线程的一种状态。线程阻塞有如下两种情况:

  • 消费者阻塞:在队列为空时,消费者端的线程都会被自动阻塞,直到数据放入队列,消费者线程会被自动唤醒并消费数据。如图。

  • 生产者阻塞:在队列已满且没有可用空间时,生产者端的线程都会被自动阻塞,直到队列中有空的位置腾出,线程会被自动唤醒并生产数据。如图。

2.1 阻塞队列的主要操作

2.1.1 插入操作

  (1)public abstract bollean add(E param):指定的元素插入队列中,成功返回true,如果没有可用空间,则抛出IllegalStateException。源码如下:

    public boolean add(E e) {
        if (offer(e))
            return true;
        else
            throw new IllegalStateException("Queue full");
    }

  (2)public abstract bollean offer(E param):将指定元素插入队列中,在成功时返回true,如果当前没有可用的空间,则返回false。源码如下:

   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();
        }
    }

  enqueue(e)方法源码如下:

    private void enqueue(E x) {
        // assert lock.getHoldCount() == 1;
        // assert items[putIndex] == null;
        final Object[] items = this.items;
        items[putIndex] = x;
        if (++putIndex == items.length)
            putIndex = 0;
        count++;
        notEmpty.signal();
    }

  (3)public boolean offer(E e, long timeout, TimeUnit unit ) throws InterruptedException:将指定的元素插入队列中,可以设定等待时间,如果超时,则返回false。源码如下:

    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();
        }
    }

  (4)public void put(E e) throws InterruptedException:将指定元素插入队列,如果队列已经满了,则阻塞、等待可用队列空间的释放,直到有可用的队列空间释放且插入成功为止,源码如下:

    public void put(E e) throws InterruptedException {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == items.length)
                notFull.await();
            enqueue(e);
        } finally {
            lock.unlock();
        }
    }

2.1.2 插入操作

  (1)poll():取走队列队首的对象,如果取不到,则返回null。源码如下:

   public E poll() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return (count == 0) ? null : dequeue();
        } finally {
            lock.unlock();
        }
    }

  dequeue()源码如下:

    private E dequeue() {
        // assert lock.getHoldCount() == 1;
        // assert items[takeIndex] != null;
        final Object[] items = this.items;
        @SuppressWarnings("unchecked")
        E x = (E) items[takeIndex];
        items[takeIndex] = null;
        if (++takeIndex == items.length)
            takeIndex = 0;
        count--;
        if (itrs != null)
            itrs.elementDequeued();
        notFull.signal();
        return x;
    }

  (2)poll(long timeout, TimeUnit unit):取走队列队首的对象如果在指定的时间内有数据可取,则返回队列中的数据,若超时,返回null。

  (3)take():取走队列队首的对象,如果队列为空,则进入阻塞状态等待,直到队列中有新的数据被加入,取出新加入的数据。源码如下:

    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0)
                notEmpty.await();
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

  (4)drainTo(Collection collection):一次性批量获取所有数据,也可以指定获取数据的个数。源码如下:

     public int drainTo(Collection<? super E> c, int maxElements) {
        checkNotNull(c);
        if (c == this)
            throw new IllegalArgumentException();
        if (maxElements <= 0)
            return 0;
        final Object[] items = this.items;
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            int n = Math.min(maxElements, count);
            int take = takeIndex;
            int i = 0;
            try {
                while (i < n) {
                    @SuppressWarnings("unchecked")
                    E x = (E) items[take];
                    c.add(x);
                    items[take] = null;
                    if (++take == items.length)
                        take = 0;
                    i++;
                }
                return n;
            } finally {
                // Restore invariants even if c.add() threw
                if (i > 0) {
                    count -= i;
                    takeIndex = take;
                    if (itrs != null) {
                        if (count == 0)
                            itrs.queueIsEmpty();
                        else if (i > take)
                            itrs.takeIndexWrapped();
                    }
                    for (; i > 0 && lock.hasWaiters(notFull); i--)
                        notFull.signal();
                }
            }
        } finally {
            lock.unlock();
        }
    }

2.2 Java中的阻塞队列实现

  

 

  (1)ArrayBlockingQueue:基于数组结构实现的有界阻塞队列,ArrayBlockingQueue队列按照先进先出原则对元素进行排序,在默认情况下不保证元素操作的公平性。

  (2)LinkedBlockingQueue:基于链表结构实现的有界阻塞队列,LinkedBlockingQueue按照先进先出原则对元素进行排序。LinkedBlockingQueue对生产者端和消费者端分别采用了两个独立的锁来控制数据同步,队列的并发性能较高。

  (3)PriorityBlockingQueue:一个支持优先级的无界队列。元素在默认情况下采用升序排列,可以自定义实现compareTo方法来指定元素进行排序规则。如果两个元素的优先级相同,则不能保证该元素的存储和访问顺序。

class Data implements Comparable<Data> {
    private String id;
    private Integer number;

    public Integer getNumber() {
        return number;
    }
    public void setNumber(Integer number) {
        this.number = number;
    }
    @Override
    public int compareTo(Data o) {
        return this.number.compareTo(o.getNumber());
    }
}

final PriorityBlockingQueue<Data> queue = new PriorityBlockingQueue<Data>();

  (4)DelayQueue:支持延时获取元素的无界阻塞队列,底层使用PriorityBlockingQueue实现。DelayQueue队列中的元素必须实现Delayed接口,该接口定义了在创建元素是该元素的延迟时间,在内部通过为每个元素的操作加锁来保障数据的一致性。只有在延迟时间到后才能从队列中提取元素。

  DelayQueue适用于以下场景:

  • 缓存系统的设计:可以用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelayQueue,一旦能从DelayQueue中获取元素,则表示缓存的有效期到了。
  •  定时任务调度:使用DelayQueue保存即将执行的任务和执行时间,一旦从DelayQueue中获取元素,就表示任务开始执行。

  (5)SynchronousQueue:一个不存储元素的阻塞队列。SynchronousQueue中的每个put操作都必须等待一个take操作完成,否则不能继续添加元素。

  (6)LinkedTransferQueue:基于链表结构实现的无界阻塞队列。

  相对于其他阻塞队列,LinkedTransferQueue多了transfer、tryTransfer和tryTransfer(E e, long timeout, TimeUnit unit)方法。

  • transfer方法:如果当前有消费者正在等待接收元素,transfer方法就会直接把生产者传入的元素投递给消费者并返回true。如果没有消费者在等待接收元素,transfer方法会将元素存放在队列的尾部(tail)节点,直到被消费后才返回。
  • tryTransfer方法:尝试是否能将生产者传入的元素直接传给消费者,如果没有消费者等待接收元素,则返回false。
  • tryTransfer(E e, long timeout, TimeUnit unit)方法:尝试是否能将生产者传入的元素直接传给消费者,如果没有消费者,则等待指定时间,超时返回false。

  (7)LinkedBlockingDeque:基于链表结构实现的双向阻塞队列,可以在队列的两端分别执行插入和移除元素操作。可以减少一半的锁资源竞争,提高队列的操作效率。相比于其他队列,LinkedBlockingDeque多了addFirst、addLast、offerFirst、offerLast、peekFirst、peekLast等方法。其中以First结尾的方法表示在队列头部执行插入、获取、移除操作;以Last结尾的方法表示在队列的尾部执行插入、获取、移除操作。

 

posted @ 2020-07-11 17:26  jet-software  阅读(153)  评论(0编辑  收藏  举报