简介
- 引入多线程的原因
- 提高资源利用率,提升系统吞吐量
- 公平性
- 简化开发工作
- 使用多线程的优势
- 提高多CPU利用率
- 方便建模
- 简化对异步事件的处理
- 改善传统GUI使用“主事件循环”带来的响应慢问题,现代GUI使用事件分发线程
- 使用多线程带来的问题
- 安全性问题
- 活跃性问题
- 性能问题
- 在应用程序之外的线程中调用应用程序代码的例子
- Timer (使任务在稍后时刻运行或者周期执行的类)
- Servlet
- 远程方法调用RMI
- Swing和AWT
线程状态转换
线程共有5种状态:创建状态New, 就绪状态Runnable,运行状态Running, 阻塞状态Blocked, 销毁状态Dead
新建状态: 线程对象被创建,如Thread t = new Thread();
就绪状态: 线程对象被创建后,其他线程调用该对象的start()方法,从而启动了该线程。处于就绪状态的线程才有可能被CPU调度
运行状态:线程获得CPU执行权执行中的状态。
阻塞状态:阻塞情况分三种:
- 等待阻塞:通过调用线程的wait()方法,让线程等待某工作完成,直到调用notify()方法唤醒或等待时间到才能转换为就绪状态。
- 同步阻塞: 线程获取synchronized同步锁失败,进入同步阻塞状态,直到其他线程释放该同步锁才有可能获得该同步锁。
- 其他阻塞: 通过调用线程的sleep()或join()或发起I/O请求时,线程会进入阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、I/O处理完毕时线程才重新转入就绪状态。
死亡状态:线程run方法执行结束或者出现异常结束都使该线程进入死亡状态,结束生命周期。
java实现多线程三种方式
-
继承Thread类实现多线程:
因为java不支持多继承,所以使用该种方式具有局限性而且扩展性不高,并且对于每一个new UseThread()出来的线程对象不能共享其中的普通成员属性值。一般不推荐使用。 -
实现Runnable接口实现多线程:
因为一个类只能有一个父类,但是可以实现多个接口,因此使用接口实现多线程的扩展性高;同时当我们调用new Thread(Runnable)创建的多个线程是基于同一个Runnable对象时,这几个线程都共享Runnable对象中的资源,但此时需要考虑线程安全问题。 -
通过线程池创建线程。
run方法与start方法的区别。
start()方法作用是启动一个新线程,start()方法不能被重复调用.
run()方法可以看成是普通的成员方法,只是这个方法会在调用start()方法后由线程自动调用;如果我们不使用start而是直接调用run()方法的话与执行普通成员方法没有区别,也不会创建新的线程。
synchronized关键字
实现同步的原理
在java中,每一个对象都有且只有一个同步锁(类本身也是对象)。当我们调用某个对象被synchronized修饰的方法或进入synchronized包括的代码块时,就获取到了该对象的同步锁,而其他线程此时再想获取同步锁就会被阻塞,也就是说不同线程对同步锁访问时互斥的,一个时刻只能有一个线程获取到同步锁,这就是synchronized能实现同步的原理。
两条规则
- 当一个线程执行某对象的synchronized方法或代码块时,其他线程对该对象的任何synchronized方法和synchronized代码块的访问将被阻塞。
- 当一个线程执行某对象的synchronized方法或代码块时,其他线程仍可访问其他非同步方法、非同步代码块。
同步方法与同步代码块
synchronized修饰方法,则此时的同步锁是调用该方法的对象,synchronized(obj)代码块的同步锁则可以是指定的任意对象,并且加上了synchronized需要进行同步判断性能比非同步方法要降低,因此一般使用同步代码块,可以更精确控制临界区,减小同步开销。
类锁与实例锁的区别:
实例锁属于实例对象,不同实例可以访问各自的同步方法不受影响,当某个类只有一个对象即单例时,该对象锁等同于类锁。
类锁是加在类对象上的锁,无论该类有多少个实例都共享该锁,所有线程即便持有不同的实例都只能互斥访问加了类锁的同步代码,实现类锁可以使用static synchronized修饰方法或者加在类的class上——synchronized(Class.class) 或者加在类的classloader对象上synchronized(Class.class.getClassLoader())
Object中定义的线程相关方法:等待与唤醒——wait()、notify()、notifyAll()
-
wait()、notify()、notifyAll()等方法定义在Object类中,而不是Thread类中,因为这些方法的实现原理都是依赖于同步锁,而同步锁是由对象持有的。
-
wait() 让调用wait的当前线程处于“等待(阻塞)状态”,直到其他线程调用此对象的 notify()方法或notifyAll()方法,调用wait()方法会使当前线程放弃它持有的锁。
-
wait(long timeout)让调用当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify()方法或notifyAll()方法,或者超过指定的时间量”
-
wait(long timeout,intnanos)让调用wait的当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的notify()方法或notifyAll()方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量”
-
notify() 唤醒在此对象监视器上等待的单个线程。
-
notifyAll() 唤醒在此对象监视器上等待的所有线程。
-
调用某对象的wait方法前提是已获得该对象的同步锁,并且在其他线程调用了该对象的notify()方法后,先前被阻塞的线程也要再次获得该对象的同步锁才能继续运行。
Thread中定义的常用方法
线程让步yield()、线程睡眠sleep()、等待线程终止join()、线程中断interrupt()、interrupted()、isInterrupted()
-
yield()的作用是让步。让步是指当前线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;但是不保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到“运行状态”继续运行!
-
sleep()方法同样用于阻塞线程,它与wati方法的区别是执行sleep的线程是由运行状态转为睡眠阻塞状态,同时sleep必须指定睡眠时长,等待时间到了自动唤醒,由阻塞状态转变为就绪状态,并且sleep方法不会放弃同步锁。而wait方法是有运行状态转为等待阻塞状态,唤醒条件可以是等待时间到或者其他线程来唤醒,阻塞同时放弃同步锁资源。
-
join()方法的作用是等待子线程终止————阻塞当前线程直到子线程t完成(此处的t由t.join()决定)
join方法的实现是通过判断判断子线程是否还存活,如果存活调用“主线程”的wait方法,将主线程阻塞起来,只有当子线程运行结束才会唤醒主线程 。 -
线程中断interrupt():
中断是一种线程之间的协作机制,通过中断是不能直接终止另一个线程的,而只是设置了一个中断标志,由被中断线程本身处理中断,如何处理由具体的业务需求决定,可以终止线程,也可以不做任何处理。
中断例子举例:当我们使用杀毒软件进行杀毒扫描时,会新开一个线程进行扫描,此时我们点击鼠标取消扫描,此时相当于产生了一个中断信号去告诉扫描线程终止扫描任务。
在Java中断模型中,每个线程对象里都有一个boolean类型的标识,代表着是否有中断请求(该请求可以来自所有线程,包括被中断的线程本身)。例如,当线程t1想中断线程t2,只需要在线程t1中将线程t2对象的中断标识置为true,然后线程2可以选择在合适的时候处理该中断请求,甚至可以不理会该请求,就像这个线程没有被中断一样。 -
interrupt()方法、isInterrupted()方法、interrupted()方法的区别:
void interrupt():中断一个线程
boolean interrupted(): 测试当前线程是否已经中断,并且清除中断状态
boolean isInterrupted():测试当前线程是否已经中断,不改变中断状态。 -
java中中断分为三种情况:1.如果被中断的线程处于阻塞状态,调用interrupt()进行中断时,首先会将中断状态设置为true,然后由于线程是阻塞状态,此时会清除中断状态,即重新设置为false,最后抛出一个InterruptedException异常。
-
如果线程被阻塞在一个Selector选择器中,那么通过interrupt()中断该线程时,线程中断标记会置为true,并从选择操作中返回。
-
如果中断一个普通的就绪状态线程,那么只会将中断标记设置为true,已经终止的线程不会对中断产生任何响应。
-
守护线程:
java中分两类线程:用户线程User Thread 和守护线程Daemon Thread,守护线程的作用是为用户线程的运行提供一定的服务,只要JVM中还有一个用户线程未结束,守护线程就全部工作,当JVM中剩余的线程全都是守护线程时,JVM结束退出。最常见的守护线程就是GC线程。
创建守护线程可以在start一个线程之前设置线程对象属于守护线程,或者在守护线程下新建任意新线程,都会是守护线程。
守护线程不能用来执行读写或者计算逻辑,因为当所有用户线程结束后守护线程和JVM也就退出了。
生产者/消费者模型
- 生产者消费者问题的提出是为了解决生产者和消费者之间速率不一致而产生的
- 首先从简化生产者消费者问题,只考虑只有一个生产者和一个消费者,使用一个阻塞队列作为缓冲区。生产者与消费者直接可以并发运行,此时的缓冲区作为二者通信的途径,因此缓冲区属于临界区,往其中添加与取出元素需要进行互斥保证,当缓冲队列满时生产者线程阻塞;当缓冲队列空时消费者线程阻塞。
原子类
原子类定义
原子类是指 java.util.concurrent.atomic包下的类,这些类的作用是对相应的数据类型进行原子操作,举个例子讲:对于整型变量i(共享资源),如果执行i++这个操作,在多线程下没有同步就是非线程安全的,因为i++可以看出先获取i的值,然后将i+1,最后将i+1的结果写回i,其中每个步骤都可能被打断导致线程安全问题发生。当然我们可以在业务上使用synchronized实现对i的同步访问,但是这样做的效率不是最优的,而且需要程序员能正确的使用同步,原子类就是用来解决这些相关问题的。
java.util.concurrent包中的所有类都使用了原子变量而非同步,这是因为使用原子变量能直接实现无等待算法,而类似ConcurrentHashMap使用ReentrantLock在需要时进行锁定,使用原子变量来维护等待锁定的线程队列。
原子类实现原理
-
volatile关键字:保证了变量在所有工作内存可见,即内存可见性,并且限制了指令不能进行重排序。
-
compareAndSet(int expect, int update):判断当前值是否等于expect,如果是则可以更新当前值,否则失败,这里是两个操作步骤,但是在硬件级别上提供了原子性的指令序列,因此该操作保证了原子性。
对应到硬件级别就是CAS机制——compare and swap(比较并交换), CAS操作包含三个操作数:内存位置、预期原值和新值。当内存位置的值与预期原值相同则用新值写回内存位置,否则不做操作。利用CAS可以实现无阻塞算法。
原子类分类:
按照操作的数据类型,原子类可以分为以下四种类型:
-
基本类型: AtomicInteger, AtomicLong, AtomicBoolean ;
-
数组类型: AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray ;
-
引用类型: AtomicReference(存在ABA问题),AtomicStampedRerence(带时间戳解决了ABA问题),AtomicMarkableReference ;
-
对象的属性修改类型: AtomicIntegerFieldUpdater, AtomicLongFieldUpdater, AtomicReferenceFieldUpdater(全是抽象类,基于反射原理实现,作用是将指定对象的指定成员属性绑定到该更新器上,由更新器实现对成员变量的原子操作)
线程安全性
- 线程安全性定义
当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且再主调代码中不需要任何额外的同步或者协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。
正确性是指某个类的行为与其规范一致。
2. 非线程安全如何转变为线程安全
不在线程间共享状态变量
将状态变量修改为不可变的常量
在访问状态变量时使用同步
3. 竞态条件
由于不恰当的执行时序而出现不正确的结果,如常见的“先检查后操作”、“读取——修改——写入”。
要保持状态的一致性,就需要在单个原子操作中更新所有相关的状态变量。
并发与内存有序性
单个线程下,所有的操作在本线程观察都是有序的,而在并发情况下,从另一个线程观察本线程,那么所有的操作都是无序的,这是Java程序有序性的一个特点。
Java程序的乱序执行包括以下三种情况:
- 编译器的静态优化可以乱序执行代码指令
- JVM的动态优化可以打乱代码的执行顺序
- 硬件可以乱序执行来优化性能
由于Java的内存被划分为主内存和工作内存,主内存和工作内存存在不同步,因此乱序执行带来的一个重要影响是一个线程产生的修改结果可能对另一个线程来说是不可见的。
为了明确知道一个线程对内存的修改对另一个线程可见,Java内存模型定义了一些规则来保证有序性(happends-before原则):
- 程序次序规则:在同一个线程中,执行顺序按照控制流顺序执行。
- 管程锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作。
- volatile规则:对一个volatile变量的写操作先行发生于后面对这个变量的读操作。
- 线程启动规则:Thread对象的start()方法先行发生于该线程的每一个动作。
- 线程终止规则:线程中所有操作先行发生于对该线程的终止检测。
- 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生。
- 对象终结规则:对象的初始化完成先行发生于它的finalize()方法执行。
- 传递性规则:如果操作A先行发生于操作B,操作B先行发生于操作C,那么操作A先行发生于操作C。
在并发操作中,常使用valatile和synchronized来保障有序性。
synchronized 与 ReentrantLock
synchronized的优点是使用了java对象的内置锁,因此使用上非常方便,但是synchronized方便的同时带来了很多限制,如等待锁而阻塞的过程中无法中断该线程、尝试获取锁时没有超时机制、只支持非公平锁。
为此Java5引入了java.util.concurrent包来增强锁机制。可以用来替换synchronized的是ReentrantLock类。
ReentranLock的使用形式如下:
Lock lock = new ReentrantLock();
lock.lock();
try {
//doSomething
} finally {
lock.unlock();
}
公平锁与非公平锁:
ReentrantLock fairLock = new ReentrantLock(true); //公平锁
ReentrantLock unfairLock = new ReentrantLock(false); //非公平锁
使用ReentranLock实现中断的例子:
public class ReentrantLockInterruptDemo {
private static ReentrantLock lock1 = new ReentrantLock();
private static ReentrantLock lock2 = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread() {
public void run() {
try {
lock1.lockInterruptibly(); //使用lockInterruptibly方法,当线程被中断时抛出异常中断线程
Thread.sleep(100);
System.out.println("t1 thread get the lock1");
lock2.lockInterruptibly();
System.out.println("t1 thread get the lock2");
} catch (InterruptedException e) {
System.out.println("t1 interrupted");
}
}
};
Thread t2 = new Thread() {
public void run() {
try {
lock2.lockInterruptibly();
Thread.sleep(100);
System.out.println("t2 thread get the lock2");
lock1.lockInterruptibly();
System.out.println("t2 thread get the lock1");
} catch (InterruptedException e) {
System.out.println("t2 interrupted");
}
}
};
t1.start();
t2.start();
Thread.sleep(1000);
t1.interrupt();
t2.interrupt();
}
}
为获取锁操作设置超时时间:
public class ReentrantLockTimeoutDemo {
private static ReentrantLock lock1 = new ReentrantLock();
private static ReentrantLock lock2 = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread() {
public void run() {
if (lock1.tryLock()) { //tryLock()方法尝试获取锁,立即返回不管能否获取到锁
try {
Thread.sleep(1000);
if (lock2.tryLock(100, TimeUnit.MILLISECONDS)) {//带超时时间的tryLock,获取到了锁或者在指定时间内获取不到才返回
System.out
.println("t1 has get the lock1 and lock2");
} else {
lock1.unlock();
}
} catch (Exception e) {
e.printStackTrace();
}
} else {
System.out.println("do nothing");
}
}
};
Thread t2 = new Thread() {
public void run() {
if (lock2.tryLock()) {
try {
Thread.sleep(100);
if (lock1.tryLock(1000, TimeUnit.MILLISECONDS)) {
System.out
.println("t2 has get the lock2 and lock1");
} else {
lock2.unlock();
}
} catch (Exception e) {
e.printStackTrace();
}
} else {
System.out.println("do nothing");
}
}
};
t1.start();
t2.start();
}
}
条件变量
条件变量可以设置等待某个事件发生时才继续执行操作,比如生产者消费者模型中从缓冲队列中取出元素需要等待队列非空,从队列中添加元素需要等待队列非满。
条件变量的一般使用形式:
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition(); //一个lock可以同时关联多个条件变量
lock.lock();
try {
while(/*条件非真*/) {
condition.await();
}
//继续执行业务操作
} finally {
lock.unlock();
}
使用condition实现生产者消费者的例子:
public class ProducerConsumerDemo {
public static void main(String[] args) {
Queue q = new Queue(10, 0);
ExecutorService executor = Executors.newCachedThreadPool();
executor.execute(new Producer(q));
executor.execute(new Consumer(q));
executor.shutdown();
}
}
//缓冲队列
class Queue {
private int capacity;
private int size;
private Lock lock = new ReentrantLock();
private Condition fullCondition = lock.newCondition();
private Condition emptyCondition = lock.newCondition();
public Queue(int capacity, int size) {
this.capacity = capacity;
this.size = size;
}
public void add() {
lock.lock();
try {
if (size == capacity) {
System.out.println("producer must to wait...");
fullCondition.await();
}
size += 1;
System.out.println(Thread.currentThread().getName()
+ " add a product into queue,size is : " + size);
emptyCondition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void remove() {
lock.lock();
try {
if (size == 0) {
System.out.println("consumer must to wait...");
emptyCondition.await();
}
size -= 1;
System.out.println(Thread.currentThread().getName()
+ " remove a product from queue,size is: " + size);
fullCondition.signal();// 每次消费完成后都尝试唤醒生产者,此方法开销较大并且多生产者/消费者情况下仍会发生死锁
} catch (Exception e) {
} finally {
lock.unlock();
}
}
}
//生产者任务
class Producer implements Runnable {
private Queue queue;
public Producer(Queue queue) {
this.queue = queue;
}
public void run() {
for (int i = 0; i < 20; i++) {
queue.add();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//消费者任务
class Consumer implements Runnable {
private Queue queue;
public Consumer(Queue queue) {
this.queue = queue;
}
public void run() {
for (int i = 0; i < 20; i++) {
queue.remove();
try {
Thread.sleep(600);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
CountDownLatch 与 CyclicBarrier
-
CountDownLatch:通过共享锁实现,可以设置多个线程等待某些事件执行完成。使用await()方法使线程等待,直到其他线程调用countDown()方法将countDownLatch的数值减为0,等待的线程才能继续向下执行。
CountDownLatch的计数器在初始化指定后就不能被重置。
-
CyclicBarrier:通过独占锁实现,可以设置一组线程相互等待,直到到达某个公共屏障点后所有线程才能继续向下执行。
CyclicBarrier在释放等待线程后可以被重用。
多线程模式
多线程依据交互方式可以分为如下几种模式:
- 无交互模式:在多线程程序中,多个线程可以并发执行,但是线程之间不需要处理共享数据,也不需要进行动作协调,各个线程独自完成自己线程中的工作。
- 基于共享容器协同的多线程模式:多个线程存在访问共享数据的情况,比如生产者/消费者模型,此时对访问共享数据的操作需要有所保护和控制。
- 通过事件协同的多线程模式:多个线程之间存在动作协调的情况,比如一个线程需要等待另一个线程完成某个操作,改变某个状态才能继续执行自己的工作,类似于IO处理线程需要等待数据的到来才能进行数据处理。
避免死锁的经验
- 原子性地获取所需的多个锁,即一次性封锁。
- 按照全局统一顺序依次获取锁。即顺序封锁。 (类比操作系统死锁预防、死锁避免、死锁检测和死锁恢复)
JUC工具类源码学习
BlockingQueue接口
BlockingQueue接口抽象了阻塞队列应具有的行为,阻塞队列相比其他队列而言最突出的特点是当队列为空时,获取元素的线程会等待队列变为非空,当队列满时,添加元素的线程会等待队列非满,因此阻塞队列常用于生产者消费者场景。
BlockingQueue继承了Queue接口:
public interface Queue<E> extends Collection<E> {
boolean add(E e);
boolean offer(E e);
E remove();
E poll();
E element();
E peek();
}
BlockingQueue接口定义如下:
public interface BlockingQueue<E> extends Queue<E> {
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;
int remainingCapacity();
boolean remove(Object o);
public boolean contains(Object o);
// //移除队列中所有元素,并添加到给定collection c中
int drainTo(Collection<? super E> c);
//移除队列中给定数量的可用元素,并将这些元素添加到collection中,如果队列中剩余元素小于指定数量则把所有元素移到collection中
int drainTo(Collection<? super E> c, int maxElements);
}
从接口定义可以看出Blocking对添加、删除、检查都提供了几种不同的方法选择。
抛出异常 | 返回特殊值 | 阻塞 | 超时退出 | |
---|---|---|---|---|
插入 | add(E e) | offer(E e) | put(E e) | offer(E e,long time,TimeUnit unit) |
移除 | remove() | poll() | take() | poll(long time,TimeUnit unit) |
检查 | element() | peek() | —— | —— |
JUC包下提供了7个阻塞队列实现:
- ArrayBlockingQueue:基于数组实现的有界阻塞队列
- LinkedBlockingQueue:基于链表实现的有界阻塞队列
- PriorityBlockingQueue:支持优先级排序的无界阻塞队列
- DelayQueue:使用优先级队列实现的无界阻塞队列
- SynchronousQueue:不存储元素的阻塞队列
- LinkedTransferQueue:基于链表的无界阻塞队列
- LinkedBlockingDeque:基于链表实现的双向阻塞队列
ArrayBlockingQueue
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
final Object[] items; //存储队列元素
int takeIndex; //下一个出队的元素索引
int putIndex; //下一个入队的元素索引
int count; //当前队列元素个数
final ReentrantLock lock; //互斥锁保证所有操作互斥进行
private final Condition notEmpty; //条件变量,指示从队列中取元素时需要等待
private final Condition notFull;//添加元素时需要等待
//内部私有辅助方法,实现入队索引的循环移动
final int inc(int i) {
return (++i == items.length) ? 0 : i;
}
//出队索引循环移动
final int dec(int i) {
return ((i == 0) ? items.length : i) - 1;
}
//类型转换方法
@SuppressWarnings("unchecked")
static <E> E cast(Object item) {
return (E) item;
}
//返回给定索引的元素但不移出队列
final E itemAt(int i) {
return this.<E>cast(items[i]);
}
//检查元素如果为空抛出NullPointerException
private static void checkNotNull(Object v) {
if (v == null)
throw new NullPointerException();
}
//元素入队:入队索引向前移动1位,元素计数加1,并且尝试唤醒阻塞在notEmpty条件变量上的某个线程,只有当持有锁时才能调用到该方法
private void insert(E x) {
items[putIndex] = x;
putIndex = inc(putIndex);
++count;
notEmpty.signal();
}
//元素出队:出队索引向前移动1位,元素计数减1,并且尝试唤醒阻塞在notFull条件变量上的某个线程,只有当持有锁时才能调用到该方法
private E extract() {
final Object[] items = this.items;
E x = this.<E>cast(items[takeIndex]);
items[takeIndex] = null;
takeIndex = inc(takeIndex);
--count;
notFull.signal();
return x;
}
//删除给定索引的元素,对remove和iterator.remove方法有效,只有当持有锁时才会被调用
void removeAt(int i) {
final Object[] items = this.items;
//如果要删除的元素刚好是下一个出队的元素,直接将该位置置为null,出队索引向前移动一位,元素计数减1,执行notFull.signal()
if (i == takeIndex) {
items[takeIndex] = null;
takeIndex = inc(takeIndex);
} else {
//否则将待删除元素前的所有元素依次后移一位
for (;;) {
int nexti = inc(i);
if (nexti != putIndex) {
items[i] = items[nexti];
i = nexti;
} else {
items[i] = null;
putIndex = i;
break;
}
}
}
--count;
notFull.signal();
}
//给定队列容量的构造函数,默认持有的是非公平锁
public ArrayBlockingQueue(int capacity) {
this(capacity, false);
}
//给定队列容量和指定使用公平锁或非公平锁控制并发
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();
}
//构造一个含有给定Collection所有元素的阻塞队列
public ArrayBlockingQueue(int capacity, boolean fair,
Collection<? extends E> c) {
this(capacity, fair);
final ReentrantLock lock = this.lock;
lock.lock(); //这里加锁是为了保证可见性而不是为了互斥
try {
int i = 0;
try {
for (E e : c) {
checkNotNull(e);
items[i++] = e;
}
} catch (ArrayIndexOutOfBoundsException ex) {
throw new IllegalArgumentException();
}
count = i;
putIndex = (i == capacity) ? 0 : i;
} finally {
lock.unlock();
}
}
public boolean add(E e) {
return super.add(e);
}
//入队操作,如果队列已满返回false
public boolean offer(E e) {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count == items.length)
return false;
else {
insert(e);
return true;
}
} finally {
lock.unlock();
}
}
//入队操作,如果队列已满会调用notFull.await阻塞,直到其他线程消费元素后执行notFull.signal()
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
insert(e);
} finally {
lock.unlock();
}
}
//可以指定timeout超时时间,当队列满时会等待timeout时间,timeout<0时等同于offer(e)直接返回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);
}
insert(e);
return true;
} finally {
lock.unlock();
}
}
//出队操作,队列为空时返回null
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return (count == 0) ? null : extract();
} finally {
lock.unlock();
}
}
//出队操作,队列为空时会阻塞直到其他线程往队列中添加元素
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return extract();
} finally {
lock.unlock();
}
}
//可以指定timeout超时时间,当队列为空时会等待timeout时间,timeout<0时等同于poll(e)直接返回null
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0) {
if (nanos <= 0)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
return extract();
} finally {
lock.unlock();
}
}
//返回队头元素,队列为空时返回null
public E peek() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return (count == 0) ? null : itemAt(takeIndex);
} finally {
lock.unlock();
}
}
//返回队列当前元素个数
public int size() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
//返回队列剩余空位个数
public int remainingCapacity() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return items.length - count;
} finally {
lock.unlock();
}
}
//从当前队列的队尾元素遍历直到匹配到Object,移除,匹配不成功返回false
public boolean remove(Object o) {
if (o == null) return false;
final Object[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lock();
try {
for (int i = takeIndex, k = count; k > 0; i = inc(i), k--) {
if (o.equals(items[i])) {
removeAt(i);
return true;
}
}
return false;
} finally {
lock.unlock();
}
}
//判断队列中是否包含给定对象
public boolean contains(Object o) {
if (o == null) return false;
final Object[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lock();
try {
for (int i = takeIndex, k = count; k > 0; i = inc(i), k--)
if (o.equals(items[i]))
return true;
return false;
} finally {
lock.unlock();
}
}
//按出队的顺序返回队列中所有元素构成的元素
public Object[] toArray() {
final Object[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lock();
try {
final int count = this.count;
Object[] a = new Object[count];
for (int i = takeIndex, k = 0; k < count; i = inc(i), k++)
a[k] = items[i];
return a;
} finally {
lock.unlock();
}
}
//按出队顺序返回队列元素构成的数据,返回的数组运行时类型是给定的数组类型T
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
final Object[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lock();
try {
final int count = this.count;
final int len = a.length;
//若T[]长度小于队列元素个数则新建一个新数组,长度等于队列元素个数
if (len < count)
a = (T[])java.lang.reflect.Array.newInstance(
a.getClass().getComponentType(), count);
for (int i = takeIndex, k = 0; k < count; i = inc(i), k++)
a[k] = (T) items[i];
if (len > count)
a[count] = null;
return a;
} finally {
lock.unlock();
}
}
public String toString() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
int k = count;
if (k == 0)
return "[]";
//循环中拼接字符串使用StringBuilder优化性能
StringBuilder sb = new StringBuilder();
sb.append('[');
for (int i = takeIndex; ; i = inc(i)) {
Object e = items[i];
sb.append(e == this ? "(this Collection)" : e);
if (--k == 0)
return sb.append(']').toString();
sb.append(',').append(' ');
}
} finally {
lock.unlock();
}
}
//清空队列并唤醒所有阻塞的生产线程
public void clear() {
final Object[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lock();
try {
for (int i = takeIndex, k = count; k > 0; i = inc(i), k--)
items[i] = null;
count = 0;
putIndex = 0;
takeIndex = 0;
notFull.signalAll();
} finally {
lock.unlock();
}
}
//将队列中所有元素移到Collection中
public int drainTo(Collection<? super E> c) {
checkNotNull(c);
if (c == this)
throw new IllegalArgumentException();
final Object[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lock();
try {
int i = takeIndex;
int n = 0;
int max = count;
while (n < max) {
c.add(this.<E>cast(items[i]));
items[i] = null;
i = inc(i);
++n;
}
if (n > 0) {
count = 0;
putIndex = 0;
takeIndex = 0;
notFull.signalAll();
}
return n;
} finally {
lock.unlock();
}
}
//从队尾元素开始移动maxElements个元素到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 i = takeIndex;
int n = 0;
int max = (maxElements < count) ? maxElements : count; //取maxElements和count中的最大值
while (n < max) {
c.add(this.<E>cast(items[i]));
items[i] = null;
i = inc(i);
++n;
}
if (n > 0) {
count -= n;
takeIndex = i;
notFull.signalAll();
}
return n;
} finally {
lock.unlock();
}
}
//返回队列的迭代器对象
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E> {
private int remaining; // 剩余没有遍历的元素个数
private int nextIndex; // 下一个遍历元素的索引
private E nextItem; // 下一个遍历的元素
private E lastItem; // 上一个遍历的元素
private int lastRet; //上一个遍历元素的索引如果没有则返回-1
Itr() {
final ReentrantLock lock = ArrayBlockingQueue.this.lock;
lock.lock();
try {
lastRet = -1;
if ((remaining = count) > 0)
nextItem = itemAt(nextIndex = takeIndex); //构造迭代器的时候记录了nextItem元素
} finally {
lock.unlock();
}
}
public boolean hasNext() {
return remaining > 0;
}
/**
* next方法做了一些特殊的约定:获取迭代器对象后其他线程 * 可能对队列进行了操作改变了队列里的内容,为了迭代时不 * 返回null,在调用next()时取出元素后会判断是否为空,如 * 果为空则返回旧值(只有获取第一个遍历的元素并且原本队 * 尾元素在其他线程被移除了的时候会发生,并且之后跳过为
* null的元素
*/
public E next() {
final ReentrantLock lock = ArrayBlockingQueue.this.lock;
lock.lock();
try {
if (remaining <= 0)
throw new NoSuchElementException();
lastRet = nextIndex;
E x = itemAt(nextIndex);
if (x == null) {
x = nextItem; //返回旧值
lastItem = null;
}
else
lastItem = x;
//跳过为null的元素
while (--remaining > 0 && (nextItem = itemAt(nextIndex = inc(nextIndex))) == null)
;
return x;
} finally {
lock.unlock();
}
}
//ArrayBlockingQueue的迭代子支持迭代过程中移除元素,不支持fail-fast机制
public void remove() {
final ReentrantLock lock = ArrayBlockingQueue.this.lock;
lock.lock();
try {
int i = lastRet;
if (i == -1)
throw new IllegalStateException();
lastRet = -1;
E x = lastItem;
lastItem = null;
//考虑迭代的队列可能已经发生了变化,仅在当前元素仍在原位时执行removeAt操作
if (x != null && x == items[i]) {
boolean removingHead = (i == takeIndex);
removeAt(i);
if (!removingHead)
nextIndex = dec(nextIndex);
}
} finally {
lock.unlock();
}
}
}
}
ArrayBlocking的源码还是很好读懂的,整体看既有对称性,也可以看出有固定的“套路”为了并发安全,所以所有操作都利用ReentrantLock进行了互斥,比如对于入队操作来说,基本套路是参数校验、获取锁、判断队列是否已满、入队列、队头指针移动、元素计数加1、唤醒阻塞在notEmpty上的某一个线程。大体的流程都是如此,所不同的是一些细节的处理和约定,比如offer方法是优先考虑获取锁的,不会被中断,队列满时直接return false,add底层是调用了offer方法,但是返回值做了拦截,队列满时不再返回false而是抛出IllegalStateExceptions,而put方法支持中断等。