精尽java并发

在多线程并发编程中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()

从该信号量获取许可证,阻止直到可用

public void acquire() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

 

 

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()移除二叉堆的元素,不断的调整

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值