多线程的并发艺术

https://www.cnblogs.com/paddix/p/5374810.html

上下文切换:即使单核的处理器也支持多线程执行代码,CPU通过给每个线程分配CPU时间片来实现这个机制。时间片是CPU分配给各个线程的执行时间,因为时间片非常短,所以CPU是通过不停切换线程执行,让我们感觉到多个线程同时执行

CPU通过时间片分配算法循环执行任务,当前一个任务执行一个时间片以后,会切换到下一个任务。但是切换以前会保存上一个任务的状态。以便下回切换会这个任务的时候,可以加载这个任务的状态。所以任务从保存再到加载的过程就是一场上下文切换。

二、

volatile的应用

使用volatile关键字修饰的变量,java线程内存模型保证所有线程看到的该变量的值是一致的。

处理器操作内存中的值,是将内存中的变量的副本拷贝到缓存中的,然后处理缓存中的该值,最后回写到内存中。

使用volatile修饰的变量,在被修改值后会立即会写到内存中,这个回写的操作会使其他CPU里面缓存的该值无效。

缓存一致性原则使每个处理器通过嗅探在总线上传播的数据来检测自己缓存的数据是不是已经过期了,当处理器发现自己缓存行对应的内存地址被修改了,就将当前处理器的缓存行置为无效。缓存一致性会阻止同时修改由两个以上处理器缓存的内存区域数据

synchronize的应用

synchronize使用的锁信息是存储在对象头中的。Mark Word 在运行阶段,Mark Word中的内容会随着锁标志为的变化而变化。

无锁状态,偏向锁,轻量级锁,重量级锁。锁只能升级不能降级

偏向锁

当一个线程尝试获取偏向锁时,会校验对象头中的锁标志为是否为1|01,如果是,表明是偏向锁。尝试使用cas,将mark Word更改为,当前的线程ID,如果不成功,校验当前的偏向锁id是否是当前线程,如果是获取锁成功。如果不是,表明这个偏向锁存在竞争,当到达全局安全点的时候,将获得锁的线程挂起,锁升级为轻量级锁。然后被阻塞在安全带的线程继续向下执行代码。

 (1)访问Mark Word中偏向锁的标识是否设置成1,锁标志位是否为01——确认为可偏向状态。

  (2)如果为可偏向状态,则测试线程ID是否指向当前线程,如果是,进入步骤(5),否则进入步骤(3)。

  (3)如果线程ID并未指向当前线程,则通过CAS操作竞争锁。如果竞争成功,则将Mark Word中线程ID设置为当前线程ID,然后执行(5);如果竞争失败,执行(4)。

  (4)如果CAS获取偏向锁失败,则表示有竞争。当到达全局安全点(safepoint)时获得偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码。

  (5)执行同步代

偏向锁实行的是出现竞争释放锁的策略。锁的撤销需要等到全局安全点上(在这个时间点上没有字节码指令的执行正在进行GC)。在这个安全点上会首先暂停拥有该偏向锁的线程,然后校验该线程是否活着的状态,如果不是,则置为无锁状态。如果活着则遍历存储这Mark Word的栈,遍历偏向对象的锁记录,栈中的marKWord 要么置为无锁状态,要么偏向其他线程。最后唤醒暂停的线程。

可以使用参数,关闭偏量锁,:-XX:-UseBiasedLocking=false

轻量级锁

线程尝试获取轻量级锁之前,判断锁位标识是不是0|01,如果是1|01表明是偏向锁。当前线程的栈锁记录。将对象头内容存储到栈中开辟出来的存储锁记录空间,然后使用cas尝试将对象头中的Mark Word替换为指向当前锁记录空间地址,如果成功,则获取到锁,将锁标志为置为00,表名轻量级锁。

轻量级解锁时,将存储锁记录空间内容替换回对象头中,如果成功,没有竞争存在,如果失败,存在竞争,膨胀为重量级锁,并唤醒等待的线程。

(1)在代码进入同步块的时候,如果同步对象锁状态为无锁状态(锁标志位为“01”状态,是否为偏向锁为“0”),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,官方称之为 Displaced Mark Word。

  (2)拷贝对象头中的Mark Word复制到锁记录中。

  (3)拷贝成功后,虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针,并将Lock record里的owner指针指向object mark word。如果更新成功,则执行步骤(4),否则执行步骤(5)。

  (4)如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位设置为“00”,即表示此对象处于轻量级锁定状态。

  (5)如果这个更新操作失败了,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行。否则说明多个线程竞争锁,轻量级锁就要膨胀为重量级锁,锁标志的状态值变为“10”,Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。 而当前线程便尝试使用自旋来获取锁,自旋就是为了不让线程阻塞,而采用循环去获取锁的过程。

原子操作

总线锁,当处理器操作共享内存的时候,处理器提供LOCK#信号,当表的处理来申请通过总线访问内存的时候会被阻塞

缓存锁,频繁使用内存会缓存到高速缓存里,被缓存到高速缓存中的内存,在LOCK操作期间被锁定,使用缓存一致性保证原子性。

缓存一致性阻止同时修改由两个以上处理器缓存的内存区域数据

使用CAS操作实现原子一致性

CAS有ABA问题,使用版本号解决。

只能保证一个共享变量的原子操作

三、内存模型

1、命令式编程中线程之间通信机制:共享内存,消息传递

共享内存:线程之间共享程序的公共状态,通过读——写内存中的公共状态进行隐式通信

消息传递:线程之间通过发送消息显示通信

同步是指程序中用于控制不同线程操作发生的先后顺序机制。

共享内存程序员必须显示的控制某个方法或某段代码在个线程中互斥执行,所以同步机制是显示的

消息传递,接收消息线程在发送消息线程之后执行,所以同步机制是隐式的。

java并发采用内存共享模型。在java中,所有的实例域,静态域或者数组变量存储在堆内存中,堆内存在线程之间共享。局异常量,异常处理,不会在堆内存中共享,他们没有内存可见性问题,不受内存模型影响

JMM:java内存模型

JMM决定一个线程对共享变量的写入何时对另一个线程可见。线程之间共享内存变量,同时每个线程都有一个私有的本地内存(涵盖缓存,写缓冲区,寄存器),本地内存存储了该线程以读写共享变量的副本

每一个CPU,都对应一个寄存器,解决CPU速度与堆内存读写速度不匹配问题,缓存器级别为L1,L2,L3,每个内存肯定对应一个L3寄存器,L2,L1,可能每个CPU对应一个,也可能共享。线程在CUP获得执行的时间片,与CUP对应的寄存器,复制线程需要的内存值到寄存器中,可能线程共建一个寄存器,也可能不共享。

重排序

编译重排序,编译器在不改变单线程语义的前提下会重安排语句的执行顺序

处理器重排序:指令级并行重排序,现代处理器采用了指令级并行技术来将多条指令重叠执行。内存系统的重排序,由于处理器使用读写缓冲区,这使得加载和存储看上去是乱序的

JMM的编译器重排序规则会禁止特定类型的编译器重排序。JMM的处理器重排序规则会要求java编译器生产指令集序列时,插入特定类型的内存屏障指令,通过内存屏障指令禁止特定类型的处理器从排序

JMM是语言级的内存模型,它确保在不同的编译器以及不同的处理器平台上,通过禁止特定类型的编译器重排序和处理器重排序

来对程序员提供一致的内存可见性

happens-before规则:

程序顺序规则:一个线程中的每个操作,happens-before于该线程中任意后续操作

监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁

Volatile变量规则:对一个Volatile域的写,happens-before于后续都这个变量的读

传递性:如果Ahappens-before B, 且B happens-before C, 则A happens-before C

数据依赖性:针对单个处理器执行的指令序列和单个线程中执行的操作,不同处理器,不同线程之间的数据依赖性不被处理器和编译器考虑

as-if-serial语义:不管怎么重排序,单线程程序的执行结果不能被改变

顺序一致性内存模型是一个理论参考模型,在设计的时候处理器内存模型和编程语言内存模型都会以顺序一致性模型为参照

顺序一致性内存模型的两大特征

1)一个线程的所有操作必须按照程序的顺序执行

2)所有的程序都只能看到一个单一的操作执行顺序。在顺序一致性内存模型中每个操作都必须是原子的且立即对所有的线程可见。

Volatile的内存语义

volatile变量自身具有下列特性

可见性:对一个volatile变量的读,总是能看到对这个volatile变量最后的写入

原子性:对任意单个volatile变量的读写具有原子性,但类似volatile++这种复合操作不具有原子性

volatile写和读与锁的释放获取有相同的内存效果

volatile写的内存语义:对volatile的写,JMM会把该变量对应的本地内存都刷新到主内存。通过加入内存屏障实现内存语义,防止应该在volatile前面执行的变量被重排序到volatile写后面执行,如果不加入内存pi这样屏障,应该刷新到主内存的变量不能被及时的刷新进去。内存屏障是编译器加的

volatile读的内存语义:对volatile的读,JMM会把该变量对应的本地内存全部共享变量变为无效,从主内存直接读取。通过加入内存屏障实现内存语义。加入内存屏障,防止应该在volatile变量后面操作的共享变量重排序到volatile变量之前,这样操作的共享变量不是主内存中最新的bian变量值。

锁的内存语义

锁可以让临界区域互斥执行

当线程释放的时候, JMM会把该线程对应的本地内存共享变量刷新到主内存

当线程获得锁的时候,JMM会把该线程对应的本地内存置为无效,从而使得被监控器保护的临界区域必须从主内存中读取共享变量

锁的内存语义的实现:

公平锁加锁方法首先读volatile变量state

公平锁释放锁最后的时候回写volatile变量state

非公平锁的锁释放也是回写volatile变量state

非公平锁加锁:加锁方法以原子操作方式更新state变量,compareAndSet()方法简称为CAS。对该方法说明如下:如果当前的值等于预期的值,则以原子方式将同步状态设置为给定的给更新值,此方法具有volatile读与写的内存语义

编译器不会对volatile读与volatile读后面的任意内存操作重排序,编译器不会对volatile写与volatile写前面的任意内存操作重排序

锁的释放和获取至少有两种方式:1)利用volatile变量的写读具备的内存语义

2)利用CAS锁附带的volatile读与volatile写的内存语义

final域的内存语义

编译器与处理器要遵循两个重排序规则

1)在构造器函数内对一个final域的写入与随后把这个构造器对象的引用赋值个一个引用变量,两个操作之间不能重排序

2)初次读一个包含final域的对象的引用,与随后次初次读这个final域,这两个操作之间不能重排序

写final域的重排序规则:JMM禁止编译器把final域的写重排序到构造函数之外,编译器会在final域的写之后,构造函数return之前插入StoreStore屏障,这个屏障禁止处理器把final域的写重排序到构造函数之外。

对final域的重排序规则:在一个线程中,初次读对象引用与初次读该对象包含的final域,JMM禁止处理器重排序这两个操作。编译器会在读final域的前面加入LoadLoad屏障。     初次对对象引用与初次对该对象包含的final域两个操作之间存在间接依赖关系,所以编译器不会重排序两个操作

final域为引用类型:对于引用类型,写final域的重排序规则对编译器和处理器增加如下约束,在构造器内对一个final引用对象的成员域的写入与随后在构造函数外把这个构造对象的引用赋值给一个引用变量,不能重排序

安全的双重检验锁定:基于volatile对象建立单例模式,用volatile修饰类对象变量

private volatile static Instance instance;
   
        public static Instance getInstance () {
            if (instance == null) {
                Synchronized (Instance.class) {
                    if (instance == null) 
                    instance = new Instance;
                    return instance;
                }
            }
            return instance;
        }

写volatile将写前操作,重排序到写后面,所以不会出现还没完成实例化,就已经有分配堆地址,造成instance不为空。

JAVA并发编程基础

线程是操作系统调度的最小单元

设置线程优先级时,针对频繁阻塞(休眠或I/O操作)的线程需要设置高优先级,而偏重计算(需要较多CPU时间或者偏运算)的线程设置低优先级确保CPU不会被独占

线程状态:

NEW:初始状态,线程被构建,但是还没有调用start()方法

RUNNABLE:运行状态,java线程将操作系统中运行与就绪状态的线程笼统的称为运行中

BLOCKED:阻塞状态,表示线程阻塞于锁

WAITING:等待状态,表示线程进入等待状态,进入该状态表示当前线程需要其他线程做出一些特定动作(唤醒或中断)

TIME_WAITING:超时等待状态,这个状态不同于WAITING,这个状态可以在指定时间自行返回

TERMINATED:终止状态,表明当前线程执行完毕

线程状态,是线程的生命流程,有线程不一定有锁,有锁才有线程停顿点

线程初始化完成以后,调用start()方法启动线程,start()方法的含义是:当前线程同步告诉java虚拟机,只要线程规划器空闲,则立即启动调用了start()方法的线程。

中断可以理解为线程的一个标识位属性,它表示一个运行中的线程是否被其他线程执行中断操作。其他线程通过调用该线程的interrupt()方法对其进行中断操作。线程通过检查自身的中断属性来进行响应,调用isInterrupt()来判断是否被中断。也可以使用interrupted()静态方法对当前线程进行复位。如果该线程已经终结(运行结束),即使调用过interrupt()方法,再调用该线程对象的isInterrupt()方法会返回false;

许多声明抛出InterruptException的方法(例如Thread.sleep(long mills))这些方法在抛出InterruptException之前会先将该线程的中断标识位清除,此时调用isInterrupt()方法回返回false

暂停:suspend(),在调后线程不会释放占用的资源,占着资源进入睡眠,容易引发死锁。不建议使用

恢复:resume() 不建议使用

终止:stop(),终结一个线程的时候不保证资源被正确释放,程序可能工作在不确定状态。不建议使用

安全的终止线程:interrupt,cancel()。使线程终止的时候有机会清理资源。

任意对象都拥有自己的监视器,当这个对象被同步块或同步方法调用时,执行方法的线程必须先获得该对象的监视器才能进入同步块或同步方法内部,而没有获得监视器的线程阻塞在同步块,或者同步方法的入库,进入blocked状态

等待/通知机制

一个线程修改一个对象的值,而另一个线程感知到变化,前者是生产者,后者是消费者

wait():调用该方法的线程进入WAITING状态,只有等待另外线程的通知或者中断才能返回,wait()方法会释放对象的锁

wait(long )超时等待一段时间,参数为豪秒,如果没有通知就超时返回

wait(long,int)对超时等待更细化控制,到纳秒

notify()通知一个在对象上等待的线程,使其从wait()方法返回,而返回的前提是获得对象的锁

notifyAll():通知所有等待在该对象上的线程,但调用该方法并不意味着放弃锁,而是正常从锁对象退出才放弃锁

使用wait(),notify和notifyAll时需要先对调用对象加锁。调用wait()方法后,线程状态由RUNNING变为WAITING,并将当前线程放置到对象的等待队列

notify和notifyAll方法调用以后,等待线程依旧不会从wait()返回,需要调用notify和notifyAll方法的线程释放之后,等待线程才有机会从wait返回。被notify或者notifyAll唤醒的线程移到同步队列,线程状态由WAITING转换为blocked

从wait返回的前提是获得了对象的锁

等待方(消费者),通知方(生产者)

管道输入输出流主要用于线程之间的数据传输,而传输的媒介是内存

Thread.join():如果线程A调用thread.join(),当前线程A等待Thread执行完才能继续执行

线程池本质:使用一个线程安全工作队列连接工作者线程和客户端线程,客户端线程将任务放入工作队列后返回。而工作者线程则不断地从工资队列上取出工作并执行。当工作队队列为空时,所有的工作者线程等待在工作队列上。当有客户端提交一个任务
后会通知任意一个工作者线程。

Java中的锁

在finally块中释放锁,保证锁获取以后能正常释放。不要在try中获得锁,以防止获得锁过程中报错,锁被无故释放了

Lock接口提供了synchronized关键字不具备的特性如下:

尝试非阻塞获取锁:当前线程尝试获得锁,如果这时刻锁没有别别的线程获取,则获取锁成功

能被中断的获取锁:当获取到锁的线程被中断时,中断异常被抛出,同时释放锁。(synchronize关键字也能响应锁,不能响应的是,如果线程阻塞在队列,则不能从队列退出,继续等待获得锁以后才能响应中断。Lock接口提供的锁,线程在阻塞在队列中能响应中断,从队列中退出)

超时获得锁:在指定截止时间如果没有获得锁,则返回

Lock 接口定义了获取锁,释放锁的基本操作,其中api中的方法:

void lock():获得锁,当获得锁后返回

void lockInterruptibly() throws InterruptedExcption:可响应中断的获取锁

boolean tryLock():尝试非阻塞的获得锁,使用该方法获取锁,立即返回结果,成功或者失败

boolean tyrLock(long time, TimeUtil util) throws IntrruptedException:超时获取锁,能响应中断三种情况:超时范围内获得锁;超时范围内被中断;超时结束没有获得锁。

void unLock():释放锁

Condition newCondition():获取等待通知的组件,当前组件和当前的锁绑定,当前线程获得锁后,该线程才能调用组件的wait方法。调用以后线程释放锁

Lock接口的实现,基本上都是通过聚合一个同步器的子类来完成线程的访问控制。

队列同步器:AbstractQueuedSynchronize,是用来构建锁或者其他同步组件的基础框架。它通过一个int成员变量表示同步状态,通过内置的FIFO队列管理阻塞线程排队

同步器本身没有实现接口方法,主要使用方式是继承,提供了修改状态的方法。实现锁的本质就是对状态的修改获取。简化并提供了锁的实现,并提供了可扩展性。

同步器内部实现具体逻辑:

当线程获取锁失败会进入一个内部的FIFO队列,而每个线程都是一个节点,存放包含当前线程的引用,线程状态,前驱后继节点。当锁被释放时会唤醒首节点线程,尝试获取锁(获取同步状态)

在同步器中包含两个节点的引用,一个是首节点,一个是尾节点,当线程加入到队列是使用CAS安全的加入到尾节点。

独占式同步状态的获取与释放:尝试获取同步器状态,成功,则说明获取锁成功,失败,则将该线程包装成为节点。并使用cas线程安全的添加到等待队列的尾节点,该节点在等待队列中自旋死循环获取同步状态,如果获取失败则阻塞节点中的线程,线程的唤醒需要前驱节点,或者线程被中断。只有出现异常情况才会使得节点从队列中删掉。直接释放状态,因为只有头结点获取了状态

共享式同步状态的获取与释放:尝试获取同步器状态,成功,则获取锁成功,失败,创建节点,该节点自旋死循环获取同步状态。释放状态的时候需要cas释状态,因为可能多个线程同时释放状态。

超时等待独占式获取同步状态:如果节点的前驱节点为头节点,则尝试获取同步状态,否则,循环自旋等待,如果超时时间点小于0返回false,如果,小于某个很小的时间,则循环自旋等待(是为了防止过时),否则睡眠

重入锁:ReentrantLock,获取请求锁线程,当同步状态为0则直接更改状态,否则 判断与持有锁线程是否一致,一致则同步状态加1,获取锁成功,否则获取锁失败。释放锁,首先判断,当前线程是否为获取锁线程,然后状态值减1,直到同步状态返回值为0则是否锁成功,否则都会返回false,释放成功,更新当前线程为空(非公平锁过程)

公平锁,是按照请求的先后顺序来获取锁,FIFO。

公平锁获取过程:当同步状态为0,如果当前线程没有前节点,则cas更改状态,否则返回fals

重入锁直接返回获取更改状态结果。

读写锁:ReentrantReadWriteLock。如果同步状态被读锁获取,则后续读锁,都可以获取同步状态。这时如果来了写锁则阻塞。

线程获得写锁,阻塞其他的写锁与读锁。获取同步状态的写锁,可以再获取读锁,这时释放写锁,完成锁降级。

写锁的获取:写锁是可重入锁,如果同步状态已经被读锁获取,或者已获取写锁非当前线程则获取同步状态失败。

读锁是共享锁。

Condition对象:Condition定义了等待通知方法,当前线程调用这些方法时,需要提前获取到condition关联对象的锁,condition对象是由Lock对象创建出来的,condition依赖于Lock对象。

ConditionObject是AbstractQueuedSynchronized的内部类,每个condition对象都包含一个等待队列,等待队列都是FIFO队列,如果一个线程调用condition.await()方法,该线程释放锁,并且线程状态变为等待状态。唤醒后续同步队列节点,线程构造成节点加入到condition等待队列的尾部,相当于同步队列的首节点,变为等待队列的尾节点。线程肯定是先获得condition关联的锁对象的,所以加入尾节点不使用cas方法。在object监视模型上,每个对象都有同步队列与等待队列。

线程调用condition.singal()方法,将等待队列的首节点调用enq(Node node)方法无限循环cas加入到同步队列尾节点,调用acquireQueued()方法(尝试获取锁),加入到获取同步状态的竞争中去,当该线程获取到锁,线程从await()状态返回

ConcurrentLinkedQueue

入队队列,ConcurrentLinkedQueue是一个无限列表,增加元素就是插入队尾节点。主要使用tail节点完成加入元素设计

首节点head元素值为空,队列为空时head节点等于tail节点,插入第一个元素,tail节点的next节点指向第一个元素。插入第二个元素时tail节点的next节点为空,tail节点指向第二个元素。tail节点不总是指向尾节点,如果tail节点的next节点为空,插入元素则将tail节点的next指向元素,如果next节点不为空,则tail节点指向元素。从代码看也是在不停的通过tail节点寻找到尾节点,然后cas插入。如果tail节点一直指向尾节点,则需要一直更新tail节点影响效率,如果一直不更新tail节点则,每次寻找尾节点都需要很长时间。目前的设计,是tail离尾节点最多1位。

 

取出元素,FIFO队列,如果首节点为空,则取值为他的next节点,如果next节点也为空,则说明队列为空,如果next节点有值,则取出元素值,并将head节点赋值成第三节点值,head节点的next节点指向第四节点值,与放入元素类似,head节点与真正的最近的节点间隔最大1位。

阻塞队列

当队列满时会阻塞插入操作的线程,当队列为空时,会阻塞取值的线程

add(e)插入操作,队列满抛出异常      offer(e) 返回boolean值,插入操作成功或失败,put(e)队列满则阻塞线程   offer(e,time, unit),队列满则阻塞到超时为止

remove(e)移除操作,队列空抛出异常,poll()成功返回值,失败返回空,take()队列空一直阻塞线程 ,poll(e,time, unit)队列空阻塞到超时为止

ArrayBlockingQueue:数组结构有界阻塞队列   对元素FIFO排序,范围线程是否公平靠配置

LinkedBlockingQueue:链表结构有界阻塞队列

PriorityBlockingQueue:支持优先级排序的无界阻塞队列

DelayQueue:一个使用优先级队列实现无界阻塞队列

SynchronousQueue:一个不存储元素的阻塞队列      每一个put操作都必须等待一个get操作

LinkedTransferQueue:一个链表结构的无界阻塞队列

LinkedBlockingDeque:链表组成的双向阻塞队列

阻塞队列的实现原理:可重入锁reentrantLock 与condition等待队列

Fork/Join框架

工作窃取算法:完成任务队列的线程从未完成任务线程的任务队列中取任务,叫做工作窃取,双向链表,一个从头取任务,一个从尾取任务

优点:利用线程并行计算,减少线程之间的精准

缺点:存在竞争,如果双端链表只剩下一个任务。消耗系统资源。(建立双端链表)

RecursiveAction :用于没有返回结果的任务

RecursiveTake:用于有返回结果的任务

原子更新基础类型:底层使用unsafe.compareAndSwapInt(this, valueoffset, expect, update); native方法

unsafe.compareAndSwapLong(this, valueoffset, expect, update);

线程池的工作原理

当提交一个新任务到线程池时,线程池的流程:

  1. 如果当前运行的线程少于corePoolSize(基本线程大小),则创建新线程来执行任务。(执行这一步需要获取全局锁,调用线程池的prestartAllCoreThreads方法,线程池会提前创建并启动所有的基本线程)
  2. 如果运行的线程等于或多于corePollSize,则将任务加入BlockingQueue(阻塞队列可以是有界的也可以使用无界的,如果是无界队列,那么maximumPoolSize参数无意义。
  3. 如果无法将任务加入BlockingQueue(队列已满),则创建新的线程来处理任务。(执行这一步需要获取全局锁)
  4. 如果创建新线程将导致当前运行的线程数超过maximumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法。

  5. new  ThreadPoolExcutor(corePoolSize(基础线程大小), maximumPoolSize(线程池最大数量), keepAliveTime(工作线程空闲后存活时间), milliseconds(TimeUtils), runnableTaskQueue(阻塞队列), handler(RejectExecutionHandle饱和策略));------创建线程池

  6. 使用execute和submit方法提交任务线程,execute方法没有返回值,submit方法返回future类型

Executor框架

在jvm线程模型中,java线程会被一对一映射为本地操作系统线程,java线程启动时会创建一个本地操作系统线程。java多线程程序将任务分解,使用用户级的调度器(Executor框架)将这些任务映射为固定数量的线程,在底层,操作系统将这些线程映射到处理器上

ThreadPoolExecutor是线程核心实现类,用来执行被提交的任务

3中类型的ThreadPoolExecutor:

           FixedThreadPool固定线程数的线程池,corePoolSize与maximumPoolSize都被设置成这个固定的参数,keepAliveTime为0表示空闲线程立即终止,阻塞队列使用LinkedBlockingQueue(容量为Integer.MAX_VALUE)无界阻塞队列。适用于负载较重的服务器

          SingleThreadExecutor 适用于保证顺序执行任务,corePoolSize与maximumPoolSize设置值相同为1。keepAliveTime为0表示空闲线程立即终止,阻塞队列使用LinkedBlockingQueue(容量为Integer.MAX_VALUE)无界阻塞队列。

          cachedThreadPool适用于执行短期异步小任务,或者负载较轻的服务器。 corePoolSize值为0,maximumPoolSize值为Integer.MAX_VALUE。keepAliveTime为60秒。阻塞队列为SynchronousQueue(没有容量,每个插入动作必须等待另一个线程的移除操作)。如果线程池中没有空闲线程,或者初始线程池中线程中线程为空,cachedThreadPool直接创建一个新线程。

 

 

scheduledThreadPoolExecutor是一个实现类,可以在给定的延迟后运行命令,或者定期执行任务

 

Future接口或者FutureTask实现类,代表异步计算结果(FutureTask实现了Runnable接口,它也可以提交来被执行)

Runnable 或者Callable的接口实现类,可以被ThreadPoolExecutor或者ScheduledThreadPoolExecutor执行。

         Runnabel不会返回结果,Callable可以返回结果

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值