在多线程并发编程中synchronized和volatile都扮演着重要的角色,volatile是轻量级的 synchronized,可见性的意思是当一个线程 修改一个共享变量时,另外一个线程能读到这个修改的值。
1自旋锁是采用让当前线程不停地的在循环体内执行实现的,当循环的条件被其他线程改变时 才能进入临界区。
2偏向锁(Biased Locking):是为了在无锁竞争的情况下避免在锁获取过程中执行
不必要的CAS原子指令,因为CAS原子指令虽然相对于重量级锁来说开销比较小
但还是存在非常可观的本地延迟。
java对象头主要有三部分组成
1,Mark Word
2,指向类的指针
3,数组长度(只有数组对象才有)
Hotspot虚拟机的对象头主要包括两部分数据:Mark Word(标记字段)、Klass Pointer(类型指针)。
其中Klass Point是是对象指向它的类元数据的指针,
虚拟机通过这个指针来确定这个对象是哪个类的实例,
Mark Word用于存储对象自身的运行时数据,它是实现轻量级锁和偏向锁的关键。
synchronized的可重入性地址
实现原理:
任何一个对象都一个Monitor(监视器)与之关联,当且一个Monitor被持有后,
它将处于锁定状态。而没有获 取到监视器(执行该方法)的线程将会被阻塞在同步块和同步方法的入口处,
进入BLOCKED状态。
Synchronized在JVM里的实现都是基于进入和退出Monitor对象
来实现方法同步和代码块同步,虽然具体实现细节不一样,但是都可以通过成对的
MonitorEnter和MonitorExit指令来实现。MonitorEnter指令插入
在同步代码块的开始位置,当代码执行到该指令时,将会尝试获取该
对象Monitor的所有权,即尝试获得该对象的锁,而monitorExit指令
则插入在方法结束处和异常处,JVM保证每个MonitorEnter必须有对应的MonitorExit。
由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存
(有些地方称为栈空间),用于存储线程私有的数据,线程与主内存中的变量操作必须
通过工作内存间接完成,主要过程是将变量从主内存拷贝的每个线程各自的工作内存空间,
然后对变量进行操作,操作完成后再将变量写回主内存,如果存在两个线程同时对一个
主内存中的实例对象的变量进行操作就有可能诱发线程安全问题。
JMM是围绕着程序执行的原子性、有序性、可见性展开的.
原子性指的是一个操作是不可中断的.
可见性指的是当一个线程修改了某个共享变量的值,其他线程是否能够马上得知这个修改的值.
java内存区域:程序计数器,方法区,java栈,java堆,本地方法区。
java内存模型:
Java内存模型(即Java Memory Model,简称JMM)本身是一种抽象的概念,并不真实存在,它描述的是一组规则
或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。由
于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方称为栈空间),用于存
储线程私有的数据,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访
问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝的自己的工作内存空
间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,工作内存中存储着主
内存中的变量副本拷贝,前面说过,工作内存是每个线程的私有数据区域,因此不同的线程间无法访问对方的工作
内存,线程间的通信(传值)必须通过主内存来完成,其简要访问过程如下图:
volatile
1关于volatile的可见性作用,我们必须意识到被volatile修饰的变量对
所有线程总数立即可见的,对volatile变量的所有写操作总是能立刻
反应到其他线程中,但是对于volatile变量运算操作在多线程环境并不保证安全性
2禁止指令重排序优化
我们知道的类的加载世纪由虚拟机决定,但是类的加载过程包括加载,验证,准备,解析,初始化。
验证阶段:检验类的结构是否正确
准备阶段:对类的变量进行分配内存,并默认初始化
解析阶段:将二进制文件的符号引用(任何形式的字面值)解析为直接引用。
对象实例化分为三个阶段,1在堆上分配内存,2初始化(有父类先初始化),3将地址值赋给引用变量。
(初始化是赋值:Object obj=null, 实例化是用类创建一个实例,初始化只是实例化的一个步骤)
dcl
public class Singleton {
private volatile static Singleton singleton; //5 不允许重排序
private Singleton(){}
public static Singleton getInstance(){
if(singleton == null){ // 1
synchronized (Singleton.class){ // 2
if(singleton == null){ // 3
System.out.println("11");
singleton = new Singleton(); // 4
}
}
}
return singleton;
}
}
1.第一重是为了判断实例是否为空,不为空就不用加锁,为空就加锁,即使多线程的时候,也会只有一个线程
拿到锁。
2.当第一个获取锁的 线程 创建完成后singleton对象后,其他的线程在第二次判断singleton一定不会为null,
则直接返回已经创建好的singleton对象;
test:两个线程调用getInstance静态方法只会打印一次11,验证了2
单例模式的实现原理:为什么jvm中对象只需要实例化一次,别的线程可以重复使用同一个实例化singleton对象?
实例化对象还存活在堆内存中,没有被gc.
java CAS操作
CAS(Compare-And-Swap) 算法保证数据变量的原子性
CAS 算法是硬件对于并发操作的支持
CAS 包含了三个操作数:
①内存值 V
②预估值 A
③更新值 B
如果V值等于A值,则将V的值设为B。若V值和A值不同,则说明已经有其他线程做了更新,则啥都不做
对于i++操作可以使用AtomicInteger进行原子操作
private static AtomicInteger atomicInteger= new AtomicInteger(10);
boolean b= atomicInteger.compareAndSet(10,12);//true---比较并设置
atomicIntegerget();//放回当前的值--12
JDK中提供了AtomicStampedReference类来解决ABA这个问题.
重入锁:ReentrantLock
代码段:
Lock lock = new ReentrantLock();即该锁可以支持一个线程对资源重复加锁(重入锁)
lock.lock();
try{
//临界区......
}finally{
lock.unlock();
}
//注意unlock()操作必须在finally代码块中,这样
可以确保即使临界区执行抛出异常,线程最终也能正常释放锁
ReentrantLock同时也支持公平锁与非公平锁。
所谓的公平与非公平指的是在请求先后顺序上,先对锁进行请求的就一定先获取到锁,
那么这就是公平锁,反之,如果对于锁的获取并没有时间上的先后顺序,
如后请求的线程可能先获取到锁,这就是非公平锁。
AbstractQueuedSynchronizer又称为队列同步器(后面简称AQS)
它是用来构建锁或其他同步组件的基础框架,内部通过一个int类型的成员变量state
来控制同步状态,当state=0时,则说明没有任何线程占有共享资源的锁,
当state=1时,则说明有线程目前正在使用共享变量,其他线程必须加入同步队列进行等待
ReentrantLock内部存在3个实现类,分别是Sync、NonfairSync、FairSync,
其中Sync继承自AQS实现了解锁tryRelease()方法,而NonfairSync(非公平锁)、
FairSync(公平锁)则继承自Sync,实现了获取锁的tryAcquire()方法。
关系:
AbstractQueuedSynchronizer:抽象类,AQS框架核心类,其内部以虚拟队列的方式管理线程的锁获取
与锁释放,其中获取锁(tryAcquire方法)和释放锁(tryRelease方法)并没有提供默认实现,需要子类重写两个方法实现具体逻辑,
目的是使开发人员可以自由定义获取锁以及释放锁的方式。
Node:AbstractQueuedSynchronizer 的内部类,用于构建虚拟队列(链表双向链表),管理需要获取锁的线程。
Sync:抽象类,是ReentrantLock的内部类,继承自AbstractQueuedSynchronizer,
实现了释放锁的操作(tryRelease()方法),并提供了lock抽象方法,由其子类实现。
NonfairSync:是ReentrantLock的内部类,继承自Sync,非公平锁的实现类。
FairSync:是ReentrantLock的内部类,继承自Sync,公平锁的实现类。
ReentrantLock:实现了Lock接口的,其内部类有Sync、NonfairSync、FairSync,
在创建时可以根据fair参数决定创建NonfairSync(默认非公平锁)还是FairSync。
AQS实现上分为两种模式,即共享模式(Semaphore)与独占模式(ReentrantLock)
ReetrantLock中非公平锁
static final class NonfairSync extends Sync {
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
unsafe中的park和unpark方法:调用 park后,线程将一直阻塞直到超时或者中断等条件出现。
unpark可以终止一个挂起的线程,使其恢复正常。
Java对线程的挂起操作被封装在 LockSupport类中,LockSupport类中有各种版本park方法,
其底层实现最终还是使用Unsafe.park()方法和Unsafe.unpark()方法
公平锁:
static final class FairSync extends Sync {
final void lock() {
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//注意!!这里先判断同步队列是否存在结点,存在就先执行同步队列中结点的线程,当前线程进入等待状态
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
获取同步状态这一点是区分公平锁,和非公平所的重点。
在JDK 1.6之后,虚拟机对于synchronized关键字进行整体优化后,在性能上synchronized与ReentrantLock已没有明显差距,
因此在使用选择上,需要根据场景而定,大部分情况下我们依然建议是synchronized关键字,
原因之一是使用方便语义清晰,二是性能上虚拟机已为我们自动优化。
而ReentrantLock提供了多样化的同步特性,如超时获取锁、可以被中断获取锁(synchronized的同步是不能中断的)、
等待唤醒机制的多个条件变量(Condition)等,因此当我们确实需要使用到这些功能是,可以选择ReentrantLock
Condition 接口描述了可能会与锁有关联的条件变量。
在 Condition 对象中,与 wait、notify 和 notifyAll 方法对应的分别是 await、signal 和 signalAll。
Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得 Condition 实例,请使用其 newCondition() 方法。
mdzz这里的代码自己一直搞不清,菜的一逼。
举个栗子吃
生产者: 往一个公共的盒子里面放苹果
消费者:从公共的盒子里面取苹果
盒子:盒子的容量不能超过5(理解为第三方,类似于mq存放消息队列的地方,这里是存放地址的地方)
wait()和notify()只能用在它们被调用的同步块中执行(生产者消费者模式)
然后根据栗子用await替换wait,signal替换notify
public class TestBox {
public int apple=0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void incre(){
lock.lock();
try {
if(apple==5){
condition.await();
}
System.out.println("开始生产苹果");
apple++;
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void decre(){
lock.lock();
try {
if(apple==0){
condition.await();
}
System.out.println("开始消费苹果");
apple--;
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
TestBox testBox=new TestBox();
Producer producer=new Producer(testBox);
Customer customer=new Customer(testBox);
Thread t1 =new Thread(producer);
Thread t2=new Thread(customer);
t1.start();
t2.start();
}
}
注意:在使用Condition前必须获得锁,Condition的具体实现类是AQS的内部类ConditionObject.
在实现类ConditionObject中有两个结点分别是firstWaiter和lastWaiter,firstWaiter代表等待队列第一个等待结点,
lastWaiter代表等待队列最后一个等待结点.
AQS中只能存在一个同步队列,但可拥有多个等待队列。
信号量-Semaphore 其本质上是一个“共享锁
为了简单起见我们假设停车场仅有5个停车位,一开始停车场没有车辆所有车位全部空着,
然后先后到来三辆车,停车场车位够,安排进去停车,然后又来三辆,这个时候由于只有两个停车位,
所有只能停两辆,其余一辆必须在外面候着,直到停车场有空车位,当然以后每来一辆都需要在外面候着。
当停车场有车开出去,里面有空位了,则安排一辆车进去(至于是哪辆 要看选择的机制是公平还是非公平)。
从程序角度看,停车场就相当于信号量Semaphore,其中许可数为5,车辆就相对线程。当来
一辆车时,许可数就会减 1 ,当停车场没有车位了(许可书 == 0 ),其他来的车辆需要在外面等候着。
如果有一辆车开出停车场,许可数 + 1,然后放进来一辆车。
号量Semaphore是一个非负整数(>=1)。当一个线程想要访问某个共享资源时,它必须要先获取Semaphore,
当Semaphore >0时,获取该资源并使Semaphore – 1。如果Semaphore值 = 0,则表示全部的共享资源
已经被其他线程全部占用,线程必须要等待其他线程释放资源。当线程释放资源时,Semaphore则+1
private Semaphore semaphore=new Semaphore(3);
public Semaphore(int permits) {
sync = new NonfairSync(permits);//构造出来给公平锁
}
NonfairSync(int permits) {
super(permits);
}
Sync(int permits) {
setState(permits);//初始化三个许可
}
//执行过程
当一个线程请求到来时,如果state值代表的许可数足够使用,那么请求线程将会获得同步状态即对共享资源的访
问权,并更新state的值(一般是对state值减1),但如果state值代表的许可数已为0,
则请求线程将无法获取同步状态,线程将被加入到同步队列并阻塞,直到其他线程释放同步
状态(一般是对state值加1)才可能获取对共享资源的访问权。
acquire() 从该信号量获取许可证,阻止直到可用 |
|
CountDownLatch一种同步辅助,允许一个或多个线程等待在其他线程中执行的一组操作完成。
CountDownLatch是通过一个计数器来实现的,当我们在new 一个CountDownLatch对象的时候需要带入该计数
器值,该值就表示了线程的数量。每当一个线程完成自己的任务后,计数器的值就会减1。
当计数器的值变为0时,就表示所有的线程均已经完成了任务,然后就可以恢复等待的线程继续执行了。
countDown()//减少锁存器的计数,如果计数达到零,释放所有等待的线程。
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero --一直减到零
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
老板进入会议室等待5个人全部到达会议室才会开会。所以这里有两种线程老板等待开会线程、员工到达会议室。
所以必须等员工员工的线程都执行完,老板的线程才被唤醒执行。
CyclicBarrier 它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。
在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。最终要得就是await()方法
通俗讲:这一组线程到了一定的屏障,然后就开始执行别的线程
比如我们开会只有等所有的人到齐了才会开会。
public class CyclicBarrierTest implements Runnable{
private static CyclicBarrier cyclicBarrier=new CyclicBarrier(2);
/**
* 结果:
来了一个人
来了一个人
来了一个人
来了一个人
来了一个人
好了开始
*/
@Override
public void run() {
try {
System.out.println("来了一个人");
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
cyclicBarrier=new CyclicBarrier(5,new Runnable() {
@Override
public void run() {
System.out.println("好了开始");
}
});
for(int i=0;i<5;i++){
new Thread(new CyclicBarrierTest()).start();
}
}
}
读写锁ReentrantReadWriteLock
实现接口ReadWriteLock,该接口维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。只要没有 writer,读取锁可以由多个 reader 线程同时保持。写入锁是独占的。
public interface ReadWriteLock {
Lock readLock();
Lock writeLock();
}
ReentrantReadWriteLock实际上只有一个锁,只是在获取读取锁和写入锁的方式上不一样而已,
它的读写锁其实就是两个类:ReadLock、writeLock,这两个类都是lock实现。
写写/读写 需要“互斥”
读读不需要互斥,意思是一个线程写,多个线程进行读,写锁是独占的,进行只读的时候可以考虑不加锁的情况。
八进制通常使用0开头表示,十六进制通常使用0x表示
threadlocal源码分析:
介绍:
该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其get 或 set 方法)的
每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal实例通常是类中的 private static 字段,
它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。
get():返回此线程局部变量的当前线程副本中的值。
initialValue():返回此线程局部变量的当前线程的“初始值”。
remove():移除此线程局部变量当前线程的值。
set(T value):将此线程局部变量的当前线程副本中的值设置为指定值。
ThreadLocal内部还有一个静态内部类ThreadLocalMap,ThreadLocalMap提供了一种用键值对方式存储每一个线程的变量副本的方法,key为当前ThreadLocal对象,value则是对应线程的变量副本。
用法:多开几个线程,线程之间没有影响
new Runnable(){//匿名内部类,对象后面加上大括号和覆盖方法
@Override
public void run() {
}
};
ThreadLocal<Integer> seqCount=new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
return 0;
}
};
public int nextSeq(){
seqCount.set(seqCount.get() + 1);
return seqCount.get();
}
public static void main(String[] args) {
SeqCount seqCount = new SeqCount();
SeqThread thread1 = new SeqThread(seqCount);
SeqThread thread2 = new SeqThread(seqCount);
thread1.start();
thread2.start();
}
private static class SeqThread extends Thread{
private SeqCount seqCount;
SeqThread(SeqCount seqCount){
this.seqCount = seqCount;
}
public void run() {
for(int i = 0 ; i < 3 ; i++){
System.out.println(Thread.currentThread().getName() +
" seqCount :" + seqCount.nextSeq());
}
}
Thread-1 seqCount :1
Thread-0 seqCount :1
Thread-0 seqCount :2
Thread-0 seqCount :3
Thread-1 seqCount :2
Thread-1 seqCount :3
---------每一个线程都输出123,线程之间没有相互影响,变量副本对应的值是独立的
内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;
内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,
但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
线程池
线程池可以简单看做是一组线程的集合,通过使用线程池,我们可以方便的复用线程,避免了频繁创建和销毁线程所带来的开销。
public interface Executor {
void execute(Runnable command);
}
Executor,任务的执行者,线程池框架中几乎所有类都直接或者间接实现Executor接口,它是线程池框架的基础。
Executor提供了一种将“任务提交”与“任务执行”分离开来的机制,它仅提供了一个Execute()方法用来执行已经提交的Runnable任务。
ExecutorService 接口在其父类接口基础上,声明了包含但不限于shutdown、submit、invokeAll、invokeAny 等方法。
ScheduledExecutorService 接口:
则是声明了一些和定时任务相关的方法,比如 schedule和scheduleAtFixedRate。
AbstractExecutorService抽象类:
实现ExecutorService接口,为其提供默认实现。AbstractExecutorService除了实现ExecutorService接口
外,还提供了newTaskFor()方法返回一个RunnableFuture,在运行的时候,它将调用底层可调用任务,
作为 Future 任务,它将生成可调用的结果作为其结果,并为底层任务提供取消操作。
ScheduledThreadPoolExecutor:
继承ThreadPoolExecutor并且实现ScheduledExecutorService接口,
是两者的集大成者,相当于提供了“延迟”和“周期执行”功能的ThreadPoolExecutor。
Future
作为异步计算的顶层接口,Future对具体的Runnable或者Callable任务提供了三种操作:执行任务的取消、查询任务是否完成、获取任务的执行结果。其接口定义如下
RunnableFuture
继承Future、Runnable两个接口,为两者的合体,即所谓的Runnable的Future。提供了一个run()方法可以完成Future并允许访问其结果。
public interface RunnableFuture<V> extends Runnable, Future<V> {
//在未被取消的情况下,将此 Future 设置为计算的结果
void run();
}
FutureTask
实现RunnableFuture接口,既可以作为Runnable被执行,也可以作为Future得到Callable的返回值。
public class TestCallable implements Callable<Integer>{
@Override
public Integer call() throws Exception {
return 1;
}
public static void main(String[] args) {
TestCallable re=new TestCallable();
//1.执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。
FutureTask<Integer> task=new FutureTask(re);
Thread thread = new Thread(task);
thread.start();
try {
Integer integer = task.get();
System.out.println(integer);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
ThreadPoolExecutor代表着鼎鼎大名的线程池
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
* corePoolSize 核心线程当提交一个新的任务到线程池,如果当前线程池运行的线程数(包括闲置的线
程)小于核心线程数,
* 则会创建一个新的线程作为核心线程来执行该任务。
*
* maxiMumPoolSize 线程池允许最大的线程数 线程池允许最大的线程数,当提交一个新的任务到线程池,
如果当前线程池运行的线程数(包括闲置的线程)
* 大于corePoolSize,小于maximumPoolSize,并且等待队列满的时候,会创建一个新的线程来处理该任
务。
*
* keepAliveTime 当线程池中线程数量大于corePoolSize时,闲置线程最长可以存活的时间。
* unit 时间单位
* workQueue 保存任务的队列,当池中线程数大于corePoolSize时,新来的任务保存到该队列。
* threadFactory:线程工厂,线程池中的线程都是通过这个工厂创建的。
* handler:
RejectedExecutionHandler,线程池的拒绝策略。所谓拒绝策略,是指将任务添加到线程池中时,线程池拒
绝该任务所采取的相应策略。当向线程池中提交任务时,如果此时线程池中的线程已经饱和了,而且阻塞队列也
已经满了,则线程池会选择一种拒绝策略来处理该任务。
线程池提供了四种拒绝策略:
AbortPolicy:直接抛出异常,默认策略;
CallerRunsPolicy:用调用者所在的线程来执行任务;
DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
DiscardPolicy:直接丢弃任务;
当然我们也可以实现自己的拒绝策略,例如记录日志等等,实现RejectedExecutionHandler接口即可。
FixedThreadPool,可重用固定线程数的线程池.
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
SingleThreadExecutor是使用单个worker线程的Executor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
任务提交:Executor.execute()、ExecutorService.submit()。其中ExecutorService.submit()可以获取该任务执行的Future。
ScheduledThreadPoolExecutor解析
ScheduledThreadPoolExecutor提供了如下四个方法,也就是四个调度器:
schedule(Callable callable, long delay, TimeUnit unit) :创建并执行在给定延迟后启用的ScheduledFuture。
schedule(Runnable command, long delay, TimeUnit unit) :创建并执行在给定延迟后启用的一次性操作。
scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) 创建并
执行在给定的初始延迟之后,随后以给定的时间段首先启用的周期性动作; 那就是执行将在initialDelay之后开
始,然后是initialDelay+period ,然后是initialDelay + 2 * period ,等等。
ScheduledExecutorService scheduledExecutorService = Executors.scheduleAtFixedRate(2);
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(i++);
}
},1,2,TimeUnit.SECONDS);
------------------------------------------------------------------------------------------
scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) :创
建并执行一个在给定初始延迟后首次启用的定期操作,
随后,在每一次执行终止和下一次执行开始之间都存在给定的延迟。
也就是初始化延迟initialDelay执行,然后间隔period继续执行。
ScheduledExecutorService scheduledExecutorService = Executors.scheduleWithFixedDelay(2);
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(i++);
}
},1,2,TimeUnit.SECONDS);//输出1=0 1 2 3 4 5......
阻塞队列
阻塞队列是一个在队列基础上又支持了两个附加操作的队列。
2个附加操作:阻塞添加 阻塞删除
阻塞添加
所谓的阻塞添加是指当阻塞队列元素已满时,队列会阻塞加入元素的线程,直队列元素不满时才重新唤醒线程执行元素加入操作。
阻塞删除
阻塞删除是指在队列元素为空时,删除队列元素的线程将被阻塞,直到队列不为空再执行删除操作(一般都会返回被删除的元素)
BlockingQueue
插入方法:
add(E e) : 添加成功返回true,失败抛IllegalStateException异常
offer(E e) : 成功返回 true,如果此队列已满,则返回 false。
put(E e) :将元素插入此队列的尾部,如果该队列已满,则一直阻塞
删除方法:
remove(Object o) :移除指定元素,成功返回true,失败返回false
poll() : 获取并移除此队列的头元素,若队列为空,则返回 null
take():获取并移除此队列头元素,若没有元素则一直阻塞。
检查方法
element() :获取但不移除此队列的头元素,没有元素则抛异常
peek() :获取但不移除此队列的头;若队列为空,则返回 null。
两个实现类ArrayBlockingQueue和LinkedBlockingQueue
ArrayBlockingQueue<Object> queue= new ArrayBlockingQueue<>(1);//默认是非公平阻塞队列
ArrayBlockingQueue<Object> queue= new ArrayBlockingQueue<>(1,true)//公平阻塞队列
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();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
实现原理:
ArrayBlockingQueue 一个有界的blocking queue由数组支持,ArrayBlockingQueue是有界的初始化必须指定大小。
take() put()方法底层加上了锁。为什么会阻塞,因为队列满了回到用Contidition的await()方法尽心等待,等队列take()时候会被
唤醒。
//入队操作
private void enqueue(E x) {
//获取当前数组
final Object[] items = this.items;
//通过putIndex索引对数组进行赋值
items[putIndex] = x;
//索引自增,如果已是最后一个位置,重新设置 putIndex = 0;
if (++putIndex == items.length)
putIndex = 0;
count++;//队列中元素数量加1
//唤醒调用take()方法的线程,执行元素获取操作。
notEmpty.signal();
}
//++putIndex == items.length //putIndex==数组长度,putIndex的为零
这是因为ArrayBlockingQueue 里面的takeIndex 总是从前面开始取出元素,putIndex总是从后面去除元素。
putIndex==数组长度时候,已经放不下数据了,下次只能从新开始方法数据了
LinkedBlockingQueue
初始时侯头节点和尾节点的值是null
private E dequeue() {
Node<E> h = head;//获取头节点
Node<E> first = h.next;//获取头节点的下一个节点
h.next = h; // help GC 字节的下一个节点指向自己
head = first;更换头节点
E x = first.item;//获取删除节点的值
first.item = null; //将头节点的值设置为null,这样将第一个节点变为null值的头节点,删除第一个节点
return x; //返回删除节点的值
}
LinkedBlockingQueue是一个基于链表的阻塞队列,其内部维持一个基于链表的数据队列。
可以是有界的也可以是无界的(Integer.MAX_VALUE),当添加速度大于移除速度时,在无界的情况下,可能会造成内存溢出等问题。(如果存在添加速度大于删除速度时候,有可能会内存溢出)
LinkedBlockingQueue内部分别使用了takeLock 和 putLock 对并发进行控制,也就是说,添加和删除操作并不是互斥操作,可以同时进行,这样也就可以大大提高吞吐量。
队列的链表移除其实就是移除头部节点,后面的带数据的节点。
DelayQueue
一个无限的阻塞队列,其中delay元素只能在其延迟到期时才被使用。
DelayQueue是一个支持延时获取元素的无界阻塞队列。里面的元素全部都是“可延期”的元素,列头的元素是最
先“到期”的元素,如果队列里面没有元素到期,是不能从列头获取元素的,哪怕有元素也不行。也就是说只有在延
迟期到时才能够从队列中取元素。
DelayQueue主要用于两个方面:
缓存:清掉缓存中超时的缓存数据
任务超时处理
PriorityQueue
PriorityBlockingQueue是一个支持优先级的无界阻塞队列。默认情况下元素采用自然顺序升序排序,当然我们
也可以通过构造函数来指定Comparator来对元素进行排序。需要注意的是PriorityBlockingQueue不能保证同优
先级元素的顺序。
public PriorityQueue(int initialCapacity,
Comparator<? super E> comparator) {
// Note: This restriction of at least one is not actually needed,
// but continues for 1.5 compatibility
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.queue = new Object[initialCapacity];
this.comparator = comparator;
}
demo:
@Test
public void t3()throws Exception{
PriorityQueue<Integer> queue = new PriorityQueue<>(7, new Mycompratble());
queue.add(2);
queue.add(1);
queue.add(6);
queue.add(23);
Iterator<Integer> iterator = queue.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
System.out.println(); //23 6 2 1
}
}
class Mycompratble implements Comparator<Integer>{
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
}
具体分析add方法
public boolean add(E e) {
return offer(e);
}
//modcount这个优先队列在结构上被修改的次数
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
modCount++;
int i = size;
if (i >= queue.length)
grow(i + 1);//扩容
size = i + 1;//元素数量加1
if (i == 0)
queue[0] = e;
else
siftUp(i, e);//调整二叉堆的结构--源码
return true;
}
//poll()移除二叉堆的元素,不断的调整