文章目录
1 java多线程实现
1.1 继承Thread类
public class ThreadTest extends Thread {
@Override
public void run() {
System.out.println(this.getName() + " is running");
}
public static void main(String[] args) {
ThreadTest threadTest = new ThreadTest();
threadTest.start();
}
}
1.2 实现Runnable接口
public class Runnabletest implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "is running");
}
public static void main(String[] args) {
Runnabletest runnabletest = new Runnabletest();
Thread thread = new Thread(runnabletest);
thread.start();
}
}
1.3 实现Callable接口
public class Mycallable implements Callable {
@Override
public Object call() throws Exception {
int i = 0;
for (i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "is running");
}
return i;
}
public static void main(String[] args) throws Exception {
Mycallable mycallable = new Mycallable();
FutureTask futureTask = new FutureTask(mycallable);
Thread thread = new Thread(futureTask);
thread.start();
System.out.println(futureTask.get());
}
}
2 线程池
- 通过Executors的方法创建出来的线程池都实现了ExecutorSerivice接口,常用的四种线程池如下
//固定数目的线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
//创建一个可缓存的线程池,调用execute将重用以前构成的线程(如果线程可用)。
//如果没有可用的线程,则创建一个新线程并添加到池中。终止并从缓存中移出那些已有60秒钟未被使用的线程。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
//创建一个支持定时及周期性的任务执行的线程池
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
//创建只有一个线程的线程池
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
-
不建议使用Executors创建线程池,建议通过ThreadPoolExecutor构造函数进行创建,原因如下:
- newCachedThreadPool和newScheduledThreadPool允许创建的最大线程数为Integer.MAX_VALUE,可能会创建大量线程导致OOM。
- newFixedThreadPool和newSingleThreadExecutor的任务队列new LinkedBlockingQueue()允许请求的队列长度为Integer.MAX_VALUE,可能会积压大量任务导致OOM。
-
线程池的组成
- 线程池管理器:用于创建并管理线程池
- 工作线程:线程池中线程
- 任务接口:每个任务必须实现的接口,用于工作线程调度其运行
- 任务队列:用于存放待处理的任务
-
ThreadPoolExecutor线程池核心类
/** * @param corePoolSize :指定线程池中的线程数量 * @param maximumPoolSize :指定线程池中最大的线程数量 * @param keepAliveTime :超过corePoolSize的线程等待任务的时间,如果超过这个时间就会销毁 * @param unit :keepAliveTime的单位 * @param workQueue :任务队列,被提交但未执行的任务 * @param threadFactory :线程工厂 * @param handler :拒绝策略 */ 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.acc = System.getSecurityManager() == null ? null : AccessController.getContext(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }
-
拒绝策略,实现了RejectedExecutionHandler接口.当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize时,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略
- AbortPolicy:直接抛出异常。
- CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务。
- DiscardPolicy:丢弃任务,但是不抛出异常。
- DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务
-
BlockingQueue,
- ArrayBlockingQueue(int i):规定大小的BlockingQueue,其构造必须指定大小。其所含的对象是FIFO顺序排序的。
- LinkedBlockingQueue()或者(int i):大小不固定的BlockingQueue,若其构造时指定大小,生成的BlockingQueue有大小限制,不指定大小,其大小有Integer.MAX_VALUE来决定。其所含的对象是FIFO顺序排序的。
- PriorityBlockingQueue()或者(int i):类似于LinkedBlockingQueue,但是其所含对象的排序不是FIFO,而是依据对象的自然顺序或者构造函数的Comparator决定。
- SynchronizedQueue():特殊的BlockingQueue,对其的操作必须是放和取交替完成。
3 线程生命周期状态
-
线程的生命周期包含:new(创建)、runnable(就绪)、running(运行)、blocked(阻塞)、dead(死亡)
-
new(创建):实例化一个线程对象
-
runnable(就绪):调用start方法
-
running(运行):就绪状态的线程获得了cpu调度,执行run方法
-
blocked(阻塞):因为某些原因执行线程让出CPU时间片,阻塞的线程需要重新回到runnable的状态才能获得cpu调度。阻塞分为以下三种情况
- 等待阻塞:调用wait方法,jvm将线程放入等待队列
- 同步阻塞:获取对象的同步缩时,如果当前对象的同步锁被其他线程占用,jvm将线程放入锁池
- 其他阻塞:调用sleep/join方法等
-
dead(死亡):线程终止则线程状态为dead状态。终止线程的四种方式如下:
-
正常运行结束
-
使用退出标志退出线程
-
调用interrupt()方法,对于阻塞中的线程需要捕获InterruptedException并通过break跳出循环状态;对于running状态则判断中断标识isInterrupted();代码示例如下:
public class ThreadTest extends Thread { @Override public void run() { while (!isInterrupted()) { System.out.println(this.getName() + " is running"); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); break; } } interrupted(); } public static void main(String[] args) throws Exception { ThreadTest threadTest = new ThreadTest(); threadTest.start(); Thread.sleep(3000); threadTest.interrupt(); System.out.println(threadTest.isInterrupted()); } }
补充:Thread类中interrupt()、interrupted()和isInterrupted()的用法:
-
interrupt(): 其作用是中断此线程,实际上只是给线程设置一个中断标志,线程仍会继续运行。
interrupted():作用是测试当前线程是否被中断(检查中断标志),返回一个boolean并清除中断状态。
**isInterrupted()**:作用是测试当前线程是否被中断(检查中断标志),返回一个boolean
4. stop方法终止:线程不安全
4 wait与sleep的区别
- wait方法属于object类,sleep方法属于Thread类
- sleep方法线程不会释放对象锁;wait方法线程会释放对象锁,需要调用notify方法才能由阻塞状态进入runnable状态。
5 start与run的区别
- Thread的start方法创建了一个新线程,并且新线程处于runnable状态。
- Thread的run方法没有创建一个新线程,主线程直接执行run方法。
6 进程和线程的区别
- 进程是资源分配的最小单元,每个进程都有独立的代码和数据空间(上下文),进程切换开销大。一个进程包含多个线程。
- 线程是CPU调度的最小单元,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。拥有独立的程序计数器是为了保证线程切换后能回到正确的执行位置。
7 线程的基本方法
- 线程等待(wait)
- Object类中的方法,调用此方法线程变成阻塞状态,移入等待队列,释放对象锁。需要调用notify或notifyAll方法才能将此线程的状态变成runnable状态。
- 线程睡眠(sleep)
- 当前线程睡眠,进入阻塞状态,但不会释放对象锁,睡眠时间结束后直接转为runnable状态
- 线程让步(yield)
- 当前线程让出CPU执行时间片,并重新和其他线程一起竞争CPU时间片
- interrupt(线程中断)
- 调用interrupt方法并不会中断线程的执行,而是更新一个中断标识,在线程的run方法内部通过Thread.isInterrupted()方法优雅的中断线程。当线程处于阻塞状态时使用线程中断会抛出InterruptedException异常,需要捕获异常并使用break跳出循环才能结束线程。
- 线程加入(join)
- 在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。当主线程需要在子线程执行结束后才能结束时,则使用join方法。
- 线程唤醒(notify notifyAll)
- Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。 直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程。
8 上下文切换
- CPU上下文切换,就是先把前一个任务的CPU上下文(也就是CPU寄存器和程序计数器)保存起来,然后加载新任务的上下文,到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务。
9 线程同步
-
线程同步指当线程发出一个功能调用时,在没有得到结果之前,该调用就不会返回,其他线程也不能调用该方法。
-
线程同步中可能存在安全隐患的解决方法
- 同步代码块:使用 synchronized(Obj obj) 对需要完整执行的语句进行“包裹”,使用共享资源的对象来作为 obj
- 同步方法:在方法的定义里使用 synchronized关键字 。
- 使用同步锁:创建私有的 ReetrantLock 对象,调用 lock() 方法,同步执行体执行完毕之后,需要用 unlock() 释放锁。
- 死锁:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放
10 顺序打印ABC10遍
- 基于Semaphore
public class Work implements Runnable {
private String key;
private Semaphore current;
private Semaphore next;
private Integer count;
public Work( Semaphore current, Semaphore next, String key,Integer count) {
this.key = key;
this.current = current;
this.next = next;
this.count = count;
}
@Override
public void run() {
for (int i = 0; i < count; i++) {
try {
//获取当前的锁
current.acquire();
System.out.println(i+","+key);
//释放next的锁
next.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Semaphore a = new Semaphore(1);
Semaphore b = new Semaphore(0);
Semaphore c = new Semaphore(0);
ExecutorService poolService = Executors.newFixedThreadPool(3);
Integer count=10;
poolService.execute(new Work(a,b,"A",count));
poolService.execute(new Work(b,c,"B",count));
poolService.execute(new Work(c,a,"C",count));
try {
Thread.sleep(3000);
poolService.shutdown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 基于Synchronized
public class SynchronizedWorker implements Runnable {
private String key;
private Integer count;
private Object lock;
private volatile Integer state ;
public SynchronizedWorker(String key, Integer count, Object lock,Integer state) {
this.key = key;
this.count = count;
this.lock = lock;
this.state =state;
}
@Override
public void run() {
for (int i = 0; i < count; i++) {
if (key == "A") {
synchronized (lock) {
System.out.println("A获取到锁");
while (state != 1) {
try {
lock.wait();
System.out.println("A释放锁,,,"+state);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(i + "," + key);
state = 2;
lock.notify();
System.out.println("将阻塞中的线程重新唤起");
}
} else if (key == "B") {
synchronized (lock) {
System.out.println("B获取到锁");
while (state != 2) {
try {
lock.wait();
System.out.println("B释放锁,,,"+state);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(i + "," + key);
state = 3;
lock.notifyAll();
System.out.println("将阻塞中的线程重新唤起");
}
} else if (key == "C") {
synchronized (lock) {
System.out.println("C获取到锁");
while (state != 3) {
try {
lock.wait();
System.out.println("C释放锁,,,"+state);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(i + "," + key);
state = 1;
lock.notifyAll();
System.out.println("将阻塞中的线程重新唤起");
}
}
}
}
public static void main(String[] args) {
ExecutorService poolService = Executors.newFixedThreadPool(3);
Integer count = 10;
Integer state= new Integer(1);
Object lock = new Object();
poolService.execute(new SynchronizedWorker("A", count, lock,1));
poolService.execute(new SynchronizedWorker("B", count, lock,2));
poolService.execute(new SynchronizedWorker("C", count, lock,3));
try {
Thread.sleep(3000);
poolService.shutdown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class SynchronizedWorker implements Runnable {
private String key;
private Integer count;
private Object lock;
private int state;
private static volatile int targetstate=1;
public SynchronizedWorker(String key, Integer count, Object lock, Integer state) {
this.key = key;
this.count = count;
this.lock = lock;
this.state = state;
}
@Override
public void run() {
for (int i = 0; i < count; i++) {
synchronized (lock) {
while (state != targetstate) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(i + "," + key);
targetstate ++;
if (targetstate>3){
targetstate=1;
}
lock.notifyAll();
}
}
}
public static void main(String[] args) {
ExecutorService poolService = Executors.newFixedThreadPool(3);
Integer count = 10;
Object lock = new Object();
poolService.execute(new SynchronizedWorker("A", count, lock, 1));
poolService.execute(new SynchronizedWorker("B", count, lock, 2));
poolService.execute(new SynchronizedWorker("C", count, lock, 3));
try {
Thread.sleep(3000);
poolService.shutdown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 基于ReentrantLock和condition
public class PrintABC {
private static ReentrantLock lock = new ReentrantLock();
private static Condition conditionA = lock.newCondition();
private static Condition conditionB = lock.newCondition();
private static Condition conditionC = lock.newCondition();
private static volatile int state = 1;
public static void main(String[] args) throws InterruptedException {
ExecutorService poolService = Executors.newFixedThreadPool(3);
Integer count = 10;
poolService.execute(new Worker("A", count, 1, lock, conditionA, conditionB));
poolService.execute(new Worker("B", count, 2, lock, conditionB, conditionC));
poolService.execute(new Worker("C", count, 3, lock, conditionC, conditionA));
Thread.sleep(5000);
poolService.shutdownNow();
}
public static class Worker implements Runnable {
private String key;
private Integer count;
private Lock lock;
private Condition current;
private Condition next;
private int targetState;
public Worker(String key, Integer count, int targetState, Lock lock, Condition cur, Condition next) {
this.key = key;
this.count = count;
this.lock = lock;
this.current = cur;
this.next = next;
this.targetState = targetState;
}
@Override
public void run() {
this.lock.lock();
try {
for (int i = 0; i < count; i++) {
while (state != targetState) {
current.await();
System.out.println(Thread.currentThread().getName()+"释放对象锁");
}
System.out.println(i + "," + key);
state++;
if (state > 3) {
state = 1;
}
next.signal();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
this.lock.unlock();
}
}
}
}
11 java锁
11.1乐观锁vs悲观锁
-
对于同一个数据的并发操作,悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。Java中,synchronized关键字和Lock的实现类都是悲观锁。
-
乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作。乐观锁通过CAS算法实现
-
CAS全称 Compare And Swap(比较与交换),是一种无锁算法。在不使用锁的情况下实现多线程之间的变量同步。java.util.concurrent包中的原子类就是通过CAS来实现了乐观锁,如:AtomicInteger。
CAS算法涉及到三个操作数:
- 需要读写的内存值 V。
- 进行比较的值 A。
- 要写入的新值 B。
只有V和A相等的情况下才会将B值更新到内存中。
-
CAS算法存在的三大问题:
-
ABA问题:CAS需要在操作值的时候检查内存值是否发生变化,没有发生变化才会更新内存值。但是如果内存值原来是A,后来变成了B,然后又变成了A,那么CAS进行检查时会发现值没有发生变化,但是实际上是有变化的。ABA问题的解决思路就是在变量前面添加版本号,每次变量更新的时候都把版本号加一。
-
CAS操作如果一直不成功,则会一直自旋消耗CPU资源
-
只能保证一个共享变量的原子操作
-
11.2 自旋锁和适应性自旋锁
自旋锁:是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。通过自旋可以避免线程切换的资源消耗,同时需要给自旋设定一定的退出条件,比如自选等待时间、自旋次数等,通过设定退出自旋条件避免线程长期自旋占用CPU资源。
适应性自旋锁:自适应意味着自旋的时间(次数)不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也是很有可能再次成功,进而它将允许自旋等待持续相对更长的时间。如果对于某个锁,自旋很少成功获得过,那在以后尝试获取这个锁时将可能省略掉自旋过程,直接阻塞线程,避免浪费处理器资源。
11.3 synchronized同步锁
synchronized属于独占式的悲观锁,同时也属于可重入锁。
- synchronized的作用范围
- 作用于方法时,锁住的是对象的实例
- 作用于静态方法时,锁住的是Class实例,相当于一个类的全局锁
- 作用于一个对象实例时,锁住的是以该对象为锁的代码块
- synchronized核心组件
- WaitSet:调用wait方法等待阻塞的线程存放容器
- ContentionList:请求锁的线程存放位置
- EntryList:COntentionList中有资格成为候选资源的线程
- OnDeck:拥有竞争锁的权利的线程。
- owner:拥有锁资源的线程。
12 JAVA阻塞队列(BlockingQueue)
-
理解概念:阻塞
- 当队列中没有数据的情况下,消费端的线程都会被自动挂起直到有数据放入队列。
- 当队列中填满数据的情况下,生产端的线程都会被自动挂起,直到队列中有空余的位置线程被自动唤醒。
-
核心方法
方法分类 抛出异常 阻塞 返回特殊值 等待 插入 add(e) put(e) offer(e) offer(e, timeout, unit) 移除 remove() take() poll()、remove(e) poll(timeout, unit) 检查 element() 无 peek() 无 /** * 插入指定元素到队列中,如果队列中有空位置则直接插入并返回True,如果队列已满则抛出异常: IllegalStateException: Queue full;如果插入null值则抛出NullPointerException;底层调用offer(E e)方法 */ boolean add(E e); /** * 插入指定元素到队列中,如果队列中有空位置则直接插入并返回True,如果队列已满则返回false * 如果插入null值则抛出NullPointerException */ boolean offer(E e); /** *插入指定元素到队列中,如果队列已满则阻塞,如果插入null值则抛出NullPointerException */ void put(E e) throws InterruptedException; /** *插入指定元素到队列中,如果队列中有空位置则直接插入并返回True,如果队列已满则等待指定时长,如果还不能向队列中插入元素则返回false */ boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException; /** * */ E take() throws InterruptedException; /** * 取走队首的元素,如果队列为空,则等待指定时间,如果还没有取到队首元素则返回null,在等待时线程被中断则抛出中断异常 */ E poll(long timeout, TimeUnit unit) throws InterruptedException; /** *取走队首的元素,如果队列为空,则返回null,如果不为空则返回队首元素 */ E poll(); /** * 取走指定的元素,如果队列中不存在则返回null */ boolean remove(Object o); /** *取走队首的元素,如果队列为空,则抛出异常:NoSuchElementException */ E remove();
-
常见的阻塞队列
队列 | 介绍 |
---|---|
ArrayBlockingQueue | 一个由数组结构组成的有界阻塞队列 |
LinkedBlockingQueue | 一个由链表结构组成的有界阻塞队列 |
SynchronousQueue | 不存储元素的队列 |
-
ArrayBlockingQueue:一个用数组实现的有界阻塞队列。此队列按照先进先出(FIFO)的原则对元素进行排序。默认情况下不保证访问者公平的访问队列,所谓公平访问队列是指阻塞的所有生产者线程或消费者线程,当队列可用时,可以按照阻塞的先后顺序访问队列,即先阻塞的生产者线程,可以先往队列里插入元素,先阻塞的消费者线程,可以先从队列里获取元素。
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; /** Condition的等待/通知机制 进行线程通信 用于阻塞的添加、移除方法*/ private final Condition notEmpty; private final Condition notFull; /** * 需要指定初始化数组大小;默认为非公平访问队列 */ public ArrayBlockingQueue(int capacity) { this(capacity, false); } /** * @param capacity 初始化数组容量大小 * @param fair 是否为公平访问队列 */ 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(); } /** * 实例化ArrayBlockingQueue并放入一个集合 */ public ArrayBlockingQueue(int capacity, boolean fair, Collection<? extends E> c) { this(capacity, fair); final ReentrantLock lock = this.lock; lock.lock(); // Lock only for visibility, not mutual exclusion 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(); } } /** * put方法会导致线程阻塞 */ public void put(E e) throws InterruptedException { //判null checkNotNull(e); final ReentrantLock lock = this.lock; //可中断的锁,抛出中断异常 lock.lockInterruptibly(); try { //如果队列已满,则通过await方法使得当前线程等待阻塞并释放当前对象锁 while (count == items.length) notFull.await(); enqueue(e); } finally { //解除锁 lock.unlock(); } } private void enqueue(E x) { //往队列中添加元素 final Object[] items = this.items; items[putIndex] = x; if (++putIndex == items.length) putIndex = 0; count++; //通过signal()方法唤起notEmpty.await()方法导致的等待阻塞中的线程 notEmpty.signal(); } //从队列中取出队首元素 public E take() throws InterruptedException { final ReentrantLock lock = this.lock; //可中断的锁,抛出中断异常 lock.lockInterruptibly(); try { //如果队列为空,则通过notEmpty.await()使当前线程等待阻塞并释放对象锁 while (count == 0) notEmpty.await(); return dequeue(); } finally { //解除锁 lock.unlock(); } } private E dequeue() { // 取出队首元素 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(); //signal()方法唤起由notFull.await()方法导致等待阻塞的线程 notFull.signal(); return x; }
-
LinkedBlockingQueue 是一个用链表实现的有界阻塞队列。此队列的默认和最大长度为 Integer.MAX_VALUE。此队列按照先进先出的原则对元素进行排序。
public class LinkedBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, java.io.Serializable { //底层用链表维护队列 static class Node<E> { E item; Node<E> next; Node(E x) { item = x; } } /** 链表长度*/ private final int capacity; /** 当前元素个数 */ private final AtomicInteger count = new AtomicInteger(); /** * 头结点 */ transient Node<E> head; /** * 尾部节点 */ private transient Node<E> last; /** 获取元素锁 */ private final ReentrantLock takeLock = new ReentrantLock(); /** 获取元素的条件 */ private final Condition notEmpty = takeLock.newCondition(); /** 存放元素锁 */ private final ReentrantLock putLock = new ReentrantLock(); /** 存放元素条件 */ private final Condition notFull = putLock.newCondition(); /** * 默认队列大小为Integer.MAX_VALUE */ public LinkedBlockingQueue() { this(Integer.MAX_VALUE); } /** *指定队列大小 */ public LinkedBlockingQueue(int capacity) { if (capacity <= 0) throw new IllegalArgumentException(); this.capacity = capacity; last = head = new Node<E>(null); } /** * 初始化时存入集合 */ public LinkedBlockingQueue(Collection<? extends E> c) { this(Integer.MAX_VALUE); final ReentrantLock putLock = this.putLock; putLock.lock(); // Never contended, but necessary for visibility try { int n = 0; for (E e : c) { if (e == null) throw new NullPointerException(); if (n == capacity) throw new IllegalStateException("Queue full"); enqueue(new Node<E>(e)); ++n; } count.set(n); } finally { putLock.unlock(); } } public void put(E e) throws InterruptedException { //判断null if (e == null) throw new NullPointerException(); int c = -1; Node<E> node = new Node<E>(e); final ReentrantLock putLock = this.putLock; final AtomicInteger count = this.count; //可中断锁 putLock.lockInterruptibly(); try { //如果当前队列已满则调用await()方法使得当前线程阻塞并释放putLock锁 while (count.get() == capacity) { notFull.await(); } enqueue(node); c = count.getAndIncrement(); //如果队列没有满则调用signal()方法唤醒notFull.await()导致的等待阻塞的线程 if (c + 1 < capacity) notFull.signal(); } finally { putLock.unlock(); } if (c == 0) signalNotEmpty(); } public E take() throws InterruptedException { E x; int c = -1; final AtomicInteger count = this.count; final ReentrantLock takeLock = this.takeLock; //获取元素锁 takeLock.lockInterruptibly(); try { //如果队列为空,则通过await()方法使得当前线程等待阻塞 while (count.get() == 0) { notEmpty.await(); } x = dequeue(); c = count.getAndDecrement(); if (c > 1) notEmpty.signal(); } finally { takeLock.unlock(); } if (c == capacity) signalNotFull(); return x; }
-
SynchronousQueue是一个不存储元素的阻塞队列。每一个put操作必须等待一个take操作,否则不能继续添加元素。它支持公平访问队列。
-
ArrayBlockingQueue和LinkedBlockingQueue 的区别:
底层实现不同:ArrayBlockingQueue底层采用有界数组维护队列;LinkedBlockingQueue 使用链表维护队列。
锁的方式不同:ArrayBlockingQueue 获取数据和添加数据都是使用同一个锁对象;LinkedBlockingQueue 获取数据使用takeLock锁对象,添加数据使用putLock锁对象。
队列大小初始化方式不同:ArrayBlockingQueue是有界的,必须指定队列的大小; LinkedBlockingQueue是无界的,可以不指定队列的大小,但是默认是Integer.MAX_VALUE。当然也可以指定队列大小,从而成为有界的。
13 CountDownLatch和CyclicBarrier区别
- CountDownLatch和CyclicBarrier区别:
- countDownLatch是一个计数器,线程完成一个记录一个,计数器递减,只能只用一次
- CyclicBarrier的计数器更像一个阀门,需要所有线程都到达,然后继续执行,计数器递增,提供reset功能,可以多次使用
14 volatile
- volatile关键字的作用:保证了变量的可见性(visibility),但是不具备原子性。被volatile关键字修饰的变量,如果值发生了变更,其他线程立马可见,避免出现脏读的现象。
15 ThreadLocal
-
ThreadLocal叫做线程变量,该变量对其他线程而言是隔离的。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
-
应用场景
-
线程间数据隔离
-
进行事务操作,用于存储线程事务信息。
-
数据库连接,Session会话管理。
-
内存泄露
ThreadLocalMap的key值是ThreadLocal,如果ThreadLoacl为null则会被垃圾回收器回收,value值的生命周期和线程一样,此时会出现key为null,value不为null,导致内存不能及时回收,造成内存泄露。解决办法:使用完ThreadLocal后,执行remove操作。