Java并发编程:volatile关键字解析 - Matrix海子 - 博客园 (cnblogs.com)
在JVM底层volatile是采用“内存屏障”来实现的。
大家都知道,计算机在执行程序时,每条指令都是在CPU中执行的,而执行指令过程中,势必涉及到数据的读取和写入。由于程序运行过程中的临时数据是存放在主存(物理内存)当中的,这时就存在一个问题,由于CPU执行速度很快,而从内存读取数据和向内存写入数据的过程跟CPU执行指令的速度比起来要慢的多,因此如果任何时候对数据的操作都要通过和内存的交互来进行,会大大降低指令执行的速度。因此在CPU里面就有了高速缓存。
也就是,当程序在运行过程中,会将运算需要的数据从主存复制一份到CPU的高速缓存当中,那么CPU进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据,当运算结束之后,再将高速缓存中的数据刷新到主存当中。
举个简单的例子,比如下面的这段代码:i = i + 1; 当线程执行这个语句时,会先从主存当中读取i的值,然后复制一份到高速缓存当中,然后CPU执行指令对i进行加1操作,然后将数据写入高速缓存,最后将高速缓存中i最新的值刷新到主存当中。
如果有两个线程执行这段代码, 理想结果是i=2;
初始时,两个线程分别读取i的值存入各自所在的CPU的高速缓存当中,然后线程1进行加1操作,然后把i的最新值1写入到内存。此时线程2的高速缓存当中i的值还是0,进行加1操作之后,i的值为1,然后线程2把i的值写入内存。
最终结果i的值是1,而不是2。这就是著名的缓存一致性问题。通常称这种被多个线程访问的变量为共享变量。
在Java虚拟机规范中试图定义一种Java内存模型(Java Memory Model,JMM)来屏蔽各个硬件平台和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果。那么Java内存模型规定了哪些东西呢,它定义了程序中变量的访问规则,往大一点说是定义了程序执行的次序。注意,为了获得较好的执行性能,Java内存模型并没有限制执行引擎使用处理器的寄存器或者高速缓存来提升指令执行速度,也没有限制编译器对指令进行重排序。也就是说,在java内存模型中,也会存在缓存一致性问题和指令重排序的问题。
Java内存模型规定所有的变量都是存在主存当中(类似于前面说的物理内存),每个线程都有自己的工作内存(类似于前面的高速缓存)。线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作。并且每个线程不能访问其他线程的工作内存。
举个简单的例子:在java中,执行下面这个语句:
i = 10;
执行线程必须先在自己的工作线程中对变量i所在的缓存行进行赋值操作,然后再写入主存当中。而不是直接将数值10写入主存当中。
那么Java语言 本身对 原子性、可见性以及有序性提供了哪些保证呢?
在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行。
只有简单的读取、赋值(而且必须是将数字赋值给某个变量,变量之间的相互赋值不是原子操作)才是原子操作。
请分析以下哪些操作是原子性操作:
x = 10; //语句1
y = x; //语句2
x++; //语句3
x = x + 1; //语句4
不过这里有一点需要注意:在32位平台下,对64位数据的读取和赋值是需要通过两个操作来完成的,不能保证其原子性。但是好像在最新的JDK中,JVM已经保证对64位数据的读取和赋值也是原子性操作了。
从上面可以看出,Java内存模型只保证了基本读取和赋值是原子性操作,如果要实现更大范围操作的原子性,可以通过synchronized和Lock来实现。由于synchronized和Lock能够保证任一时刻只有一个线程执行该代码块,那么自然就不存在原子性问题了,从而保证了原子性。
在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
在Java里面,可以通过volatile关键字来保证一定的“有序性”。另外可以通过synchronized和Lock来保证有序性,很显然,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。
另外,Java内存模型具备一些先天的“有序性”,即不需要通过任何手段就能够得到保证的有序性,这个通常也称为 happens-before 原则。如果两个操作的执行次序无法从happens-before原则推导出来,那么它们就不能保证它们的有序性,虚拟机可以随意地对它们进行重排序。
下面就来具体介绍下happens-before原则(先行发生原则):
下面我们来解释一下前4条规则:
对于程序次序规则来说,我的理解就是一段程序代码的执行在单个线程中看起来是有序的。注意,虽然这条规则中提到“书写在前面的操作先行发生于书写在后面的操作”,这个应该是程序看起来执行的顺序是按照代码顺序执行的,因为虚拟机可能会对程序代码进行指令重排序。虽然进行重排序,但是最终执行的结果是与程序顺序执行的结果一致的,它只会对不存在数据依赖性的指令进行重排序。因此,在单个线程中,程序执行看起来是有序执行的,这一点要注意理解。事实上,这个规则是用来保证程序在单线程中执行结果的正确性,但无法保证程序在多线程中执行的正确性。
第二条规则也比较容易理解,也就是说无论在单线程中还是多线程中,同一个锁如果出于被锁定的状态,那么必须先对锁进行了释放操作,后面才能继续进行lock操作。
第三条规则是一条比较重要的规则,也是后文将要重点讲述的内容。直观地解释就是,如果一个线程先去写一个变量,然后一个线程去进行读取,那么写入操作肯定会先行发生于读操作。
第四条规则实际上就是体现happens-before原则具备传递性。
1.volatile关键字的两层语义
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2)禁止进行指令重排序。
2.volatile保证原子性吗
不能 可以通过synchronized:采用Lock: 或者对数字的操作使用AtomicInteger
public AtomicInteger inc = new AtomicInteger();
3**.volatile能保证有序性吗?**
可以 ;可以保证 在volatile修饰的变量相关语句之前的代码 一定在之前执行,之后的代码在之后执行
//x、y为非volatile变量
//flag为volatile变量
x = 2; ``//语句1
y = 0; ``//语句2
flag = true; ``//语句3
x = 4; ``//语句4
y = -1; ``//语句5
由于flag变量为volatile变量,那么在进行指令重排序的过程的时候,不会将语句3放到语句1、语句2前面,也不会讲语句3放到语句4、语句5后面。但是要注意语句1和语句2的顺序、语句4和语句5的顺序是不作任何保证的。
4.volatile的原理和实现机制
前面讲述了源于volatile关键字的一些使用,下面我们来探讨一下volatile到底如何保证可见性和禁止指令重排序的。
下面这段话摘自《深入理解Java虚拟机》:
“观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”
lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:
1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
2)它会强制将对缓存的修改操作立即写入主存;
3)如果是写操作,它会导致其他CPU中对应的缓存行无效。
synchronized关键字是防止多个线程同时执行一段代码,那么就会很影响程序执行效率,而volatile关键字在某些情况下性能要优于synchronized,但是要注意volatile关键字是无法替代synchronized关键字的,因为volatile关键字无法保证操作的原子性。通常来说,使用volatile必须具备以下2个条件:
1)对变量的写操作不依赖于当前值
2)该变量没有包含在具有其他变量的不变式中
实际上,这些条件表明,可以被写入 volatile 变量的这些有效值独立于任何程序的状态,包括变量的当前状态。
事实上,我的理解就是上面的2个条件需要保证操作是原子性操作,才能保证使用volatile关键字的程序在并发时能够正确执行。
1.状态标记量
volatile boolean flag = ``false``;
while(!flag){
doSomething();
}
public void setFlag() {
flag = true ;
}
double check
这种情况下由于 instance = new SingleInstance(); 不能保证原子性;也就是说,这行代码需要处理器分为多步才能完成,其中主要包含两个操作,分配内存空间,引用变量指向内存,由于编译器可能会产生指令重排序的优化操作,所以两个步骤不能确定实际的先后顺序,假如线程A已经指向了内存,但是并没有分配空间,线程A阻塞,那么当线程B执行时,会发现Instance已经非空了,那么这时返回的Instance变量实际上还没有分配内存,显然是错误的。
public class SingleInstance{
private static SingleInstance instance = null;
private SingleInstance(){}
public static SingleInstance getInstance(){
if(instance == null){
synchronized(SingleInstance.class){
if(instance == null){
instance = new SingleInstance(); //
}
}
}
return instance;
}
}
//给instance 变量加上volatile 关键字 保证前后代码的有序性 保证 一定是执行完之后再返回
public class SingleInstance{
private static volatile SingleInstance instance = null;
private SingleInstance(){}
public static SingleInstance getInstance(){
if(instance == null){
synchronized(SingleInstance.class){
if(instance == null){
instance = new SingleInstance();
}
}
}
return instance;
}
}
synchronized 关键字和 volatile 关键字是两个互补的存在,⽽不是对⽴的存在!
线程独享变量
hreadLoal 变量,线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本。这里有几点需要注意:
ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。
总的来说,ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景。
首先 ThreadLocal 是一个泛型类,保证可以接受任何类型的对象。
因为一个线程内可以存在多个 ThreadLocal 对象,所以其实是 ThreadLocal 内部维护了一个 Map ,这个 Map 不是直接使用的 HashMap ,而是 ThreadLocal 实现的一个叫做 ThreadLocalMap 的静态内部类。而我们使用的 get()、set() 方法其实都是调用了这个ThreadLocalMap类对应的 get()、set() 方法。
public void set(T value) {//value:用户id
//当前线程:每个用户访问tomcat都会创建一个线程 A B
Thread t = Thread.currentThread();
//Thread这个类里面有一个属性:threadLocals(类型是ThreadLocalMap,ThreadLocal的静态内部类) 传入A线程拿到的就是A线程threadLocals B。。。。
ThreadLocalMap map = getMap(t);//第一次去拿这个属性 为null
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
void createMap(Thread t, T firstValue) {
//t:A或者B this代表当前ThreadLocal对象 firstValue:当前用户的id
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//entry数组:长度16
table = new Entry[INITIAL_CAPACITY];
//数组下标:当前设置的值 应该放在数组哪个位子
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//Entry:key--value key:threadLocal对象 value放的用户id
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
public T get() {
//获取当前线程:A用户拿到就是A线程 B用户拿到的B线程
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);--->return t.threadLocals;
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
return (T)map.get(this);
// Maps are constructed lazily. if the map for this thread
// doesn't exist, create it, with this ThreadLocal and its
// initial value as its only entry.
T value = initialValue();
createMap(t, value);
return value;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap是个静态的内部类:
static class ThreadLocalMap {
........
}
最终的变量是放在了当前线程的 ThreadLocalMap 中,并不是存在 ThreadLocal 上,ThreadLocal 可以理解为只是ThreadLocalMap的封装,传递了变量值。
如上文所述,ThreadLocal 适用于如下两种场景
弱引⽤介绍: 如果⼀个对象只具有弱引⽤,那就类似于可有可⽆的⽣活⽤品。弱引⽤与软引⽤的区别在 于:只具有弱引⽤的对象拥有更短暂的⽣命周期。在垃圾回收器线程扫描它 所管辖的内存 区域的过程中,⼀旦发现了只具有弱引⽤的对象,不管当前内存空间⾜够与否,都会回收它 的内存。不过,由于垃圾回收器是⼀个优先级很低的线程, 因此不⼀定会很快发现那些只 具有弱引⽤的对象。 弱引⽤可以和⼀个引⽤队列(ReferenceQueue)联合使⽤,如果弱引⽤所引⽤的对象被垃 圾回收,Java 虚拟机就会把这个弱引⽤加⼊到与之关联的引⽤队列中。 2.3.21. 线程池 2.3.21.1. 为什么要⽤线程池? 池化技术相⽐⼤家已经屡⻅不鲜了,线程池、数据库连接池、Http 连接池等等都是对这个 思想的应⽤。池化技术的思想主要是为了减少每次获取资源的消耗,提⾼对资源的利⽤率。 static class Entry extends WeakReference>
DEFAULT_INITIAL_CAPACITY = 1 << 4; Hash表默认初始容量
MAXIMUM_CAPACITY = 1 << 30;
最大Hash表容量 DEFAULT_LOAD_FACTOR = 0.75f;
默认加载因子 TREEIFY_THRESHOLD = 8;
链表转红黑树阈值 UNTREEIFY_THRESHOLD = 6;
红黑树转链表阈值 MIN_TREEIFY_CAPACITY = 64;
链表转红黑树时hash表最小容量阈值,达不 到优先扩容。
1.7-hashtable = 数组(基础) + 链表
1.8 = 数组 + 链表 + 红黑树
hashmap在jdk1.7以前有回环
jdk1.8虽然加入红黑树但是能转树的概率 百万分之一
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
调用真正的put方法之前 会先算出key的hashcode
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//hashMap默认会创建一个长度为16的node数组
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//n=16 根据当前key的hash值算出在这个数组的下标
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);//没有值直接放入
else {//hash冲突
Node<K,V> e; K k;
if (p.hash == hash &&//当前放入的key跟原来存在的key相同
((k = p.key) == key || (key != null && key.equals(k))))
e = p;//替换
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
//判断是否树华,数组长度>=64 ,节点长度>=8
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();//判断是否扩容
afterNodeInsertion(evict);
return null;
}
concurrentHashmap---
//两个线程同时插入hashmap 算出hash值都等于3 3这个地方正好没有值 可能会出现覆盖
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break;
//两个线程同时插入hashmap 算出hash值等于 3这个位置有了,
synchronized
/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
//不允许存放空键值
if (key == null || value == null) throw new NullPointerException();
//2次扰动 获得当前key hash值
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
//算出当前key 应该存放的下标位置:如果下标位置为null
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
//多个线程同时算出下标都相等 有可能出现覆盖 于是采用cas达到线程安全
//1.并发2.并发的key的hash值相等 3.这个位置没有元素 写的概率小 读多写少
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
//链表操作 可能出现覆盖:多个线程同时判断下一个为null
//这个地方比前面cas的地方 写的概率大一些
//应该直接加锁,锁芯是当前下标的第一个元素,只锁住当前链表,效率远远高于hashtable
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
读读的问题,
公平锁是指多个线程按照申请锁的顺序来获取锁,线程直接进入队列中排队,队列中的第一个线程才能获得锁。公平锁的优点是等待锁的线程不会饿死。缺点是整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大。
非公平锁是多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾等待。但如果此时锁刚好可用,那么这个线程可以无需阻塞直接获取到锁,所以非公平锁有可能出现后申请锁的线程先获取锁的场景。非公平锁的优点是可以减少唤起线程的开销,整体的吞吐效率高,因为线程有几率不阻塞直接获得锁,CPU不必唤醒所有线程。缺点是处于等待队列中的线程可能会饿死,或者等很久才会获得锁。
可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class),不会因为之前已经获取过还没释放而阻塞。
Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。下面用示例代码来进行分析:
独享锁和共享锁同样是一种概念。我们先介绍一下具体的概念,然后通过ReentrantLock和ReentrantReadWriteLock的源码来介绍独享锁和共享锁。
独享锁也叫排他锁,是指该锁一次只能被一个线程所持有。如果线程T对数据A加上排它锁后,则其他线程不能再对A加任何类型的锁。获得排它锁的线程即能读数据又能修改数据。JDK中的synchronized和JUC中Lock的实现类就是互斥锁。
共享锁是指该锁可被多个线程所持有。如果线程T对数据A加上共享锁后,则其他线程只能对A再加共享锁,不能加排它锁。获得共享锁的线程只能读数据,不能修改数据。
独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。
读写锁是一个资源能够被多个读线程访问,或者被一个写线程访问但不能同时存在读线程。Java当中的读写锁通过ReentrantReadWriteLock实现。具体使用方法这里不展开。
所谓互斥锁就是指一次最多只能有一个线程持有的锁。在JDK中synchronized和JUC的Lock就是互斥锁
分段锁
ConcurrentHashMap中采用了分段锁
(1条消息) synchronized 实现原理_liuwg1226的专栏-CSDN博客
synchronized内置锁是一种对象锁(锁的是对象而非引用),作用粒度是对象,可以用来实现对临界资源的同步互斥访问,是可 重入的。 加锁的方式
释放锁:1.同步代码块运行完了,2.同步代码块抛异常
synchronized关键字被编译成字节码后会被翻译成monitorenter 和 monitorexit 两条指令分别在同步块逻辑代码的起始位置 与结束位置
package it.yg.juc.sync;
public class SynchronizedMethod {
public synchronized void method() {
System.out.println("Hello World!");
}
}
反编译结果:
ObjectMonitor() {
_header = NULL;
_count = 0; // 记录个数
_waiters = 0,
_recursions = 0;
_object = NULL;
_owner = NULL;
_WaitSet = NULL; // 处于wait状态的线程,会被加入到_WaitSet
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ; // 处于等待锁block状态的线程,会被加入到该列表
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
}
ObjectMonitor中有两个队列,_WaitSet 和 _EntryList,用来保存ObjectWaiter对象列表( 每个等待锁的线程都会被封装成ObjectWaiter对象 ),_owner指向持有ObjectMonitor对象的线程,当多个线程同时访问一段同步代码时:
同时,Monitor对象存在于每个Java对象的对象头Mark Word中(存储的指针的指向),Synchronized锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因,同时notify/notifyAll/wait等方法会使用到Monitor锁对象,所以必须在同步代码块中使用。
监视器Monitor有两种同步方式:互斥与协作。多线程环境下线程之间如果需要共享数据,需要解决互斥访问数据的问题,监视器可以确保监视器上的数据在同一时刻只会有一个线程在访问。
那么有个问题来了,我们知道synchronized加锁加在对象上,对象是如何记录锁状态的呢?答案是锁状态是被记录在每个对象的对象头(Mark Word)中,下面我们一起认识一下对象的内存布局
HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。
javap -verbose xxx.class
同步方法会acc_synchronized
同步代码块会出现 moniterenter moniterexit,完成一个异常----moniterexit
synchronized(对象)
对象在内存中得布局:
对象头
实例数据
对齐填充
java对象头:
new Student()----student.class
class metadata address:指向class对象的指针,虚拟机可以确定该对象时哪个class的实例
mark word:对象的信息,标志位等,实现锁的关键 id属性
对象的信息: hashcode,分代年龄:gc 垃圾回收
锁类型:
锁标志位: 01(无) 00(轻量级) 10 轻量级 重量级不同的
还会记录,当前持有该锁的线程是谁
只靠标记是不行:mointer监视器---来完成加锁放锁 管程
ObjectMointer父类 c++实现的,每一个对象都一定会有moniter,出现的时间:伴随着对象的出生而出生,第一次去拿对象锁的时候出生,moniterenter moniterexit
重要属性:
waitset:保存抢到该锁以后调用wait方法的线程
entrylist:记录没有抢到锁的线程。多个线程 5个线程来抢锁,某个线程抢到锁,对象mark word id=当前线程的id
owner:抢到锁的线程 如果释放锁,owner置空
count:+1 -1
A线程来了 把owner---改为A count+1 ----B线程来了,A线程还在执行 B进入entrylist等待----A线程执行wait方法,进入waitset里面
jdk6以前,lock比synchronized效率高很多,mutex lock效率低
jdk6以后做了优化,
B线程去拿锁 拿不到---转等待状态----B想运行又要转回来--浪费了效率
自旋:多数时候,拿锁就一哈哈儿,如果去切换得不偿失,就可以让其它线程while(true)不释放cpu还更快
但是锁的时间长,自旋就长,就更消耗了用户可以更改,参数可以不用管
就有了**自适应自旋锁**:由前一次,在同一个锁上自旋时间及锁的持有时间,可以自动调整自旋时间,甚至可以不自旋
**锁消除**
stringbuffer 自动消除
```java
//方法属于线程栈 天生线程安全 内部的东西都是安全的 没必要加锁
//jvm 会把锁自动消除:锁消除
public void test(){
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("abc");
}
```
**锁粗化:**
循环多次stringbuffer 每次调用方法都会加锁
StringBuffer stringBuffer = new StringBuffer();
public void test(){
synchronized(){//锁粗化
while(1000次)
stringBuffer.append("abc");
}
}
会在栈里面复制一份moniter的信息,对象头
对象头会有一个指针
无锁、偏向锁、轻量级锁、重量级锁
偏向锁:只有一个线程 抢锁,本来抢锁需要很多认证,改一些标记,先把这些标记标记为这个线程,减少加锁的流程
(就偏向于他了 mark word(堆中)的id=当前线程的thread id)
轻量级锁:AB交替执行,偏向锁----轻量级锁
轻量级锁:很多线程大家没有规律抢 ----重量级锁
锁的释放依赖moniter:jmm内存模型,1.把数据同步,高速缓冲(栈的内存) 比如读取student对象 修改student对象 写入student对象到堆,另外把其它栈里面有这个对象的全部要求重新读取(可见性) 2.修改对象头信息,锁标志,id,指针
https://blog.csdn.net/javazejian/article/details/72828483
锁 | 优点 | 缺点 | 使用场景 |
---|---|---|---|
偏向锁 | 加锁和解锁不需要额外的消耗,与执行非同步方法差距很小 | 如果线程存在竞争,会额外带来锁撤销的消耗 | 只有一个线程访问同步块的场景 |
轻量级锁 | 竞争的线程不会阻塞,提高了程序的相应速度 | 如果始终得不到锁竞争的线程,使用自旋锁会消耗cpu | 追求相应时间,同步块执行速度快 |
重量级锁 | 线程竞争不使用自选,不会消耗cpu | 线程阻塞 ,响应时间较慢 | 同步执行速度较慢 |
无锁 | 没有共享变量,即无竞争 |
无锁
检查对象头中 Mark Word 是否为可偏向状态,如果不是则直接升级为轻量级锁
如果是,判断 Mark Work 中的线程 ThreadId 是否指向当前线程,如果是则该线程就不会再重复获取锁了,执行同步代码块
如果不是,则进行 CAS 操作竞争锁,如果竞争到锁,则将 Mark Work 中的线程 ID 设为当前线程 ID,执行同步代码块
如果竞争失败,升级为轻量级锁
只有等到竞争,持有偏向锁的线程才会撤销偏向锁。偏向锁撤销后会恢复到无锁或者轻量级锁的状态。
引入轻量级锁的目的:在多线程交替执行同步代码块时(未发生竞争),避免使用互斥量(重量锁)带来的性能消耗。但多个线程同时进入临界区(发生竞争)则会使得轻量级锁膨胀为重量级锁。
将对象的 Mark Word 复制到栈帧中的 Lock Record 中,并将 Lock Record 中的 owner 指向当前对象,并使用 CAS 操作将对象的 Mark Word 更新为指向 Lock Record 的指针,
首先判断当前对象是否处于一个无锁的状态,如果是,Java 虚拟机将在当前线程的栈帧建立一个锁记录(Lock Record),用于存储对象目前的 Mark Word 的拷贝
如果第二步执行成功,表示该线程获得了这个对象的锁,将对象 Mark Word 中锁的标志位设置为 “00”,执行同步代码块。
如果第二步未执行成功,需要先判断当前对象的 Mark Word 是否指向当前线程的栈帧,如果是,表示当前线程已经持有了当前对象的锁,这是一次重入,直接执行同步代码块。如果不是表示多个线程存在竞争,该线程通过自旋尝试获得锁,即重复步骤2,自旋超过一定次数,轻量级锁升级为重量级锁
轻量级的解锁同样是通过 CAS 操作进行的,线程会通过 CAS 操作将 Lock Record 中的 Mark Word(官方称为 Displaced Mark Word)替换回来。如果成功表示没有竞争发生,成功释放锁,恢复到无锁的状态;如果失败,表示当前锁存在竞争,升级为重量级锁。
一句话总结轻量级锁的原理:将对象的 Mark Word 复制到当前线程的 Lock Record 中,并将对象的 Mark Word 更新为指向 Lock Record 的指针。
AQS是AbstractQueuedSynchronizer的简称。AQS 是⼀个⽤来构建锁和同步器的框架,使⽤ AQS 能简单且⾼效地构造出应⽤⼴泛的⼤量的同 步器,⽐如我们提到的 ReentrantLock , Semaphore ,其他的诸如 ReentrantReadWriteLock , SynchronousQueue , FutureTask 等等皆是基于 AQS 的。
AQS 核⼼思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的⼯作线 程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占⽤,那么就需要⼀套线程阻塞 等待以及被唤醒时锁分配的机制,这个机制 AQS 是⽤ CLH 队列锁实现的,即将暂时获取不到锁 的线程加⼊到队列中。
CLH(Craig,Landin,and Hagersten)队列是⼀个虚拟的双向队列(虚拟的双向队列即不存在 队列实例,仅存在结点之间的关联关系)。AQS 是将每条请求共享资源的线程封装成⼀个 CLH 锁队列的⼀个结点(Node)来实现锁的分配
AQS 定义两种资源共享⽅式
前面讲到synchronized,如果没有调用阻塞的wait()与join方法,那么只会在下面两种情况释放锁
1.当synchronized修饰的同步代码块执行完毕
2.当synchronized修饰的同步代码块出现异常,jvm会自动释放锁
也知道当多个线程试图获取synchronized锁的时候是没办法被中断的
那么问题就来了,如果现在需求是希望能等待一段时间过后,如果没有获得锁,就不继续等待锁了,很显然
synchronized做不到,于是就有了lock
ReentrantLock,意思是“可重入锁”,关于可重入锁的概念在下一节讲述。ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法。下面通过一些实例看具体看一下如何使用ReentrantLock。
void lock():获得锁,如果锁被占用则等待。
void lockInterruptibly():获得锁,但优先响应中断。(留着)
boolean tryLock():尝试获得锁,如果成功,则返回true;失败返回false。此方法不等待,立即返回。
boolean tryLock(long time,TimeUnit):在上面的方法上加上时间
void unlock():释放锁,一定要记得释放锁,出现死锁
Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。此一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。
lick不能玩为静态属性 因为静态属性 所有对象共享 ,每个线程执行该方法,都会保存一个副本,那么每个线程的lock。lock获取的是不同的锁
Lock lock = ...;
//lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待。
lock.lock();
try{
//处理任务
}catch(Exception ex){
}finally{
lock.unlock(); //释放锁
}
ReentrantLock lock = new ReentrantLock();
try { //等待时间,时间单位 可以说时分秒,天 一般 用业务执行过程的时间*2
if (lock.tryLock(5, TimeUnit.SECONDS)) {
try {
}catch (Exception e){
throw new RuntimeException(e);
}finally {
lock.unlock();
}
}else{
//没拿到锁的操作
}
} catch (InterruptedException e) {
//拿锁的过程报错了
e.printStackTrace();
}
//假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。
public void method() throws InterruptedException {
lock.lockInterruptibly();
try {
//.....
}
finally {
lock.unlock();
}
}
readLock()
writeLock()用来获取读锁和写锁
ReentrantReadWriteLock里面提供了很多丰富的方法,不过最主要的有两个方法
读读不互斥,读写互斥。
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
public void get(Thread thread) {
rwl.readLock().lock();
try {
long start = System.currentTimeMillis();
while(System.currentTimeMillis() - start <= 1) {
System.out.println(thread.getName()+"正在进行读操作");
}
System.out.println(thread.getName()+"读操作完毕");
} finally {
rwl.readLock().unlock();
}
}
synchronized 属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层操作系统的 Mutex Lock 来实现的,而操作系统实现线程之间的切换时需要从用户态转换到核心态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方从 JVM 层面对 synchronized 进行了较大优化,所以现在的 synchronized 锁效率也优化得很不错了。Java 6 之后,为了减少获得锁和释放锁所带来的性能消耗,引入了轻量级锁和偏向锁,
Lock底层实现基于AQS实现,采用线程独占的方式,在硬件层面依赖特殊的CPU指令(CAS)。
简单来说,ReenTrantLock的实现是一种自旋锁,通过循环调用CAS操作来实现加锁。它的性能比较好也是因为避免了使线程进入内核态的阻塞状态。想尽办法避免线程进入内核的阻塞状态是我们去分析和理解锁设计的关键钥匙。
Lock 适用于大量同步代码块的场景,synchronized 适用于少量同步代码块的场景
Condition newCondition():自定义条件 await()
它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition的await()、signal()这种方式实现线程间协作更加安全和高效
Conditon里面的方法,哪个Conditon的signal方法就只能叫醒自己的await
Conditon中的 await() 对应Object的wait();
Condition中的 signal() 对应Object的notify();
Condition中的 signalAll() 对应Object的notifyAll()。
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
condition.await()//当前线程阻塞
condition.await(5,TimeUnit.SECONDS); //一定时间后自动进入竞争状态
condition.signal(); //随机唤醒一个当前lock对象的等待线程
condition.signalAll(); //唤醒所有等待线程
syn..里面wait/notify/notifyall方法,由锁对象调用,等待与唤醒都有对应关系,写代码需要分析
Conditon里面的方法,哪个Conditon的signal方法就只能叫醒自己的await
Test3 test3 = new Test3();
synchronized (test3){
test3.wait(5);//让当前线程阻塞 ,等待notify或者notifyall 或者超过等待时间 毫秒
test3.notify();
test3.notifyAll();
}
信号量,Semaphore可以控同时访问的线程个数,可用做限流
//构造方法
public Semaphore(int permits) { //参数permits表示许可数目,即同时可以允许多少线程进行访问
sync = new NonfairSync(permits);
}
//构造方法
public Semaphore(int permits, boolean fair) { //这个多了一个参数fair表示是否是公平的,即等待时间越久的越先获取许可
sync = (fair)? new FairSync(permits) : new NonfairSync(permits);
}
public void acquire() throws InterruptedException { } //获取一个许可
public void acquire(int permits) throws InterruptedException { } //获取permits个许可
public void release() { } //释放一个许可
public void release(int permits) { } //释放permits个许可
这4个方法都会被阻塞,如果想立即得到执行结果,可以使用下面几个方法:
public boolean tryAcquire() { }; //尝试获取一个许可,若获取成功,则立即返回true,若获取失败,则立即返回false
public boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException { }; //尝试获取一个许可,若在指定的时间内获取成功,则立即返回true,否则则立即返回false
public boolean tryAcquire(int permits) { }; //尝试获取permits个许可,若获取成功,则立即返回true,若获取失败,则立即返回false
public boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException { }; //尝试获取permits个许可,若在指定的时间内获取成功,则立即返回true,否则则立即返回false
import java.util.concurrent.Semaphore;
public class SemaphoreTest implements Runnable{
//参数permits表示许可数目,即同时可以允许多少线程进行访问
//这个多了一个参数fair表示是否是公平的,即等待时间越久的越先获取许可
Semaphore semaphore = new Semaphore(2);
@Override
public void run() {
try {
//获取一个许可,可以有int参数 ,获取n个许可
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"获得令牌");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//释放信号量,计数器加1
semaphore.release();
}
}
public static void main(String[] args) {
SemaphoreTest semaphoreTest = new SemaphoreTest();
new Thread(semaphoreTest,"张三").start();
new Thread(semaphoreTest,"李四").start();
new Thread(semaphoreTest,"王五").start();
}
}
一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。等待多个任务做完再继续做其它事情(协作的)
CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当 一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的 线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。
1 CountDownLatch.countDown()
2 CountDownLatch.await();
1.注册用户
1.上传头像---->图片地址 A
2.上传生活昭----->图片地址 B
4.上面的操作都做完了 再插入数据库
import java.util.concurrent.CountDownLatch;
public class CountDownLatchTest {
public static void main(String[] args) {
//所有的线程中都要引入同一个CountDownLatch对象,当都执行latch.countDown();才执行latch.await()后面的方法
//等待两个线程任务,也就是要执行两次latch.countDown(); 才会开始执行下面的
//让后面的线程处于await状态 当countdoum为零时唤醒
final CountDownLatch latch = new CountDownLatch(2);
new Thread(){
public void run() {
try {
System.out.println("子线程"+Thread.currentThread().getName()+"正在执行");
Thread.sleep(3000);
System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕");
latch.countDown(); //----------执行结束
} catch (InterruptedException e) {
e.printStackTrace();
}
};
}.start();
new Thread(){
public void run() {
try {
System.out.println("子线程"+Thread.currentThread().getName()+"正在执行");
Thread.sleep(3000);
System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕");
latch.countDown(); //-------------------执行结束
} catch (InterruptedException e) {
e.printStackTrace();
}
};
}.start();
try {
System.out.println("等待2个子线程执行完毕...");
latch.await();//必须要等待两个线程来告诉我 执行完毕了 才会继续执行 会阻塞
System.out.println("2个子线程已经执行完毕");
System.out.println("继续执行主线程");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
栅栏类似于闭锁,它能阻塞一组线程直到某个事件的发生。栅栏与闭锁的关键区别在于,所有的线程必须同时到达栅栏位置,才能继续执行。闭锁用于等待事件,而栅栏用于等待其他线程。
栅栏屏障,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程 到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。 CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线 程数量,每个线程调用await方法告CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。
CyclicBarrier使用独占锁来执行await方法,并发性可能不是很高
如果在等待过程中,线程被中断了,就抛出异常。但如果中断的线程所对应的CyclicBarrier不是这代的,比如,在最后一次线程执行signalAll后,并且更新了这个“代”对象。在这个区间,这个线程被中断了,那么,JDK认为任务已经完成了,就不必在乎中断了,只需要打个标记。该部分源码已在dowait(boolean, long)方法中进行了注释。
如果线程被其他的CyclicBarrier唤醒了,那么肯定等于generation,这个事件就不能return了,而是继续循环阻塞。反之,如果是当前CyclicBarrier唤醒的,就返回线程在CyclicBarrier的下标。完成了一次冲过栅栏的过程。该部分源码已在dowait(boolean, long)方法中进行了注释。
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.atomic.AtomicInteger;
public class CyCleTest {
public static void main(String[] args) {
for(int i=0;i<4;i++)
new Writer(i+1).start();
}
static class Writer extends Thread{
//private AtomicInteger atomicInteger = new AtomicInteger(0);
//创建栅栏
public static CyclicBarrier cyclicBarrier=new CyclicBarrier(4);
private int i ;
public Writer(int i){
this.i = i;
}
@Override
public void run() {
System.out.println("线程"+Thread.currentThread().getName()+"正在写入数据...");
try {
Thread.sleep(i*2000); //以睡眠来模拟写入数据操作
System.out.println("线程"+Thread.currentThread().getName()+"写入数据完毕,等待其他线程写入完毕");
//会阻塞。。。直到 4 个线程都走到这儿
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
}catch(BrokenBarrierException e){
e.printStackTrace();
}
System.out.println("所有线程写入完毕,继续处理其他任务...");
}
}
}
Atomic 是指⼀个操作是不可中断的。即使是在多个线程⼀ 起执⾏的时候,⼀个操作⼀旦开始,就不会被其他线程⼲扰,通过引入低级别的原子化语义命令(比如compare-and-swap (CAS)),从而能在保证效率的同时保证原子性。 由CAS实现、
基本类:
AtomicInteger
AtomicLong
AtomicBoolean
引用类型:
数组类型:
属性原子修改器(Updater):
其中的主要方法:
get() 直接返回值
getAndAdd(int) 增加指定的数据,返回变化前的数据
getAndDecrement() 减少1,返回减少前的数据
getAndIncrement() 增加1,返回增加前的数据
getAndSet(int) 设置指定的数据,返回设置前的数据
addAndGet(int) 增加指定的数据后返回增加后的数据
decrementAndGet() 减少1,返回减少后的值
incrementAndGet() 增加1,返回增加后的值
boolean compareAndSet(int expect, int update) :如果输入的数值等于预期
值,则以原子方式将该值设置为输入的值。
lazySet(int) 仅仅当get时才会set
tomicInteger:以atomic开头的类都叫原子类
new AtomicInteger(0)
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerTest implements Runnable{
public int i = 0;
AtomicInteger atomicInteger = new AtomicInteger(0);
@Override
public void run() {
// synchronized (this) {
for (int i1 = 0; i1 < 1000; i1++) {
atomicInteger.incrementAndGet();
}
// }
}
public static void main(String[] args) {
AtomicIntegerTest atomicIntegerTest = new AtomicIntegerTest();
for (int i = 0; i < 10; i++) {
new Thread(atomicIntegerTest).start();
}
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println(atomicIntegerTest.atomicInteger.get());
}
}
CAS全称 Compare And Swap(比较与交换),是一种无锁算法。在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步。java.util.concurrent包中的原子类就是通过CAS来实现了乐观锁。
当且仅当 V 的值等于 A 时,CAS通过原子方式用新值B来更新V的值(“比较+更新”整体是一个原子操作),否则不会执行任何操作。一般情况下,“更新”是一个不断重试的操作。
利用cpu底层指令 比较并交换弄成一个指令
乐观锁,sync…悲观锁:读一样会被锁住,效率太低,sync:锁,cpu底层操作锁的切换,意味着线程切换,锁状态切换,
cas会通过类似于while(true)形式一直占着cpu,悲观锁会进行状态切换
CAS适用于写操作少,读操作多
cas:单点登录服务器
缺点:ABA解决不了
先把i改成2 再改为1 你会认为没有改
AtomicStampedReference ABA问题 1A 2B 3A
version:记录操作版本号 解决ABA 1.0—>2.0—>3.0 比较av成功 再比较版本
例如:AtomicInteger,git版本控制
原子类:Unsafe操作底层cpu,通过cpu指令,达到比较并交换
线程之间互相等待对方释放锁,就是死锁
package cn.cdqf.dead;
public class MyDeadLock implements Runnable{
private MyLock myLock1 = new MyLock();
private MyLock myLock2 = new MyLock();
@Override
public void run() {
synchronized (myLock1){
//Thread-1获得了myLock1
System.out.println(Thread.currentThread().getName()+"获得了myLock1");
synchronized (myLock2){
System.out.println(Thread.currentThread().getName()+"获得了myLock2");
}
}
synchronized (myLock2){
//Thread-0获得了myLock2
System.out.println(Thread.currentThread().getName()+"获得了myLock2");
synchronized (myLock1){
System.out.println(Thread.currentThread().getName()+"获得了myLock1");
}
}
}
public static void main(String[] args) {
MyDeadLock myDeadLock = new MyDeadLock();
new Thread(myDeadLock).start();
new Thread(myDeadLock).start();
}
}
class MyLock{}
编号解死锁:对多把锁 按从小到大排序,每个要用多锁的地方,都先锁小的,再锁大的。
package cn.cdqf.deadlock;
//一般syn里面锁芯不用String 因为String会放在字符串常量池,所以容易出现多个地方锁相同
//也少用Integer -128---127被缓存了的 是同一个对象
public class DeadLockTest {
private MyLock myLock1 = new MyLock(1);
private MyLock myLock2 = new MyLock(2);
public void test1(){
//一个地方 A在前B在后 另外一个地方B在前 A在后
//现在给锁编号 那么永远小的在前
if(myLock1.getI()<myLock2.getI()){
synchronized (myLock1){
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (myLock2){
}
}
}else {
synchronized (myLock2){
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (myLock1){
}
}
}
}
public void test2(){
if(myLock1.getI()<myLock2.getI()){
synchronized (myLock1){
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (myLock2){
}
}
}else {
synchronized (myLock2){
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (myLock1){
}
}
}
}
}
wait():等待,释放锁 来源于 object的 notify------waitset
wait/notify必须要同步代码块中使用
当前所对象的包含的代码中没有wait的线程,没法叫
当前锁对象中的notify/notifyAll只能叫醒,当前锁对象包含中的wait()
一般都用notifyAll:叫醒当前锁对象中所有的wait方法
notify:随机叫一个
BlockingQueue,是java.util.concurrent 包提供的用于解决并发生产者 - 消费者问题 的最有用的类,它的特性是在任意时刻只有一个线程可以进行take或者put操作,并且 BlockingQueue提供了超时return null的机制,在许多生产场景里都可以看到这个工具的 身影。
ArrayBlockingQueue 由数组支持的有界队列
队列基于数组实现,容量大小在创建ArrayBlockingQueue对象时已定义好
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>();
基于ReentrantLock保证线程安全,根据Condition实现队列满时的阻塞
LinkedBlockingQueue 由链接节点支持的可选有界队列
是一个基于链表的无界队列(理论上有界)
blockingQueue 的容量将设置为 Integer.MAX_VALUE 。 向无限队列添加元素的所有操作都将永远不会阻塞,[注意这里不是说不会加锁保证线程安 全],因此它可以增长到非常大的容量。 使用无限 BlockingQueue 设计生产者 - 消费者模型时最重要的是 消费者应该能够像生产 者向队列添加消息一样快地消费消息 。否则,内存可能会填满,然后就会得到一 个 OutOfMemory 异常。
BlockingQueue<String> blockingQueue = new LinkedBlockingQueue<>();
PriorityBlockingQueue 由优先级堆支持的无界优先级队列
BlockingQueue<String> blockingQueue = new DelayQueue();
DelayQueue 由优先级堆支持的、基于时间的调度队列
内部基于无界队列PriorityQueue实现,而无界 队列基于数组的扩容实现
入队的对象必须要实现Delayed接口,而Delayed集成自Comparable接口
BlockingQueue<String> blockingQueue = new DelayQueue();
offer(E e): 将给定的元素设置到队列中,如果设置成功返回true, 否则返回false. e的值不能为空,否则抛出空指针异常。
offer(E e, long timeout, TimeUnit unit): 将给定元素在给定的时间内设置到队列中,如果设置成功返回true, 否则返回false.
add(E e): 将给定元素设置到队列中,如果设置成功返回true, 否则抛出异常。如果是往限定了长度的队列中设置值,推荐使用offer()方法。
put(E e): 将元素设置到队列中,如果队列中没有多余的空间,该方法会一直阻塞,直到队列中有多余的空间。
take(): 从队列中获取值,如果队列中没有值,线程会一直阻塞,直到队列中有值,并且该方法取得了该值。
poll(long timeout, TimeUnit unit): 在给定的时间里,从队列中获取值,如果没有取到会抛出异常。
remainingCapacity():获取队列中剩余的空间。
remove(Object o): 从队列中移除指定的值。
contains(Object o): 判断队列中是否拥有该值。
drainTo(Collection c): 将队列中值,全部移除,并发设置到给定的集合中。
注意点:
1、新建状态 NEW
2、 就绪状态
3、运行状态 RUNNABLE
4、 阻塞状态
BLOCKED 被阻塞等待监视器锁定的线程处于这个状态
WAITING:正在等待另一个线程执行特定的动作的线程处于这个状态。
TIMED_WAITING:正在等待另一个线程执行动作达到指定等待时间的线程处于这个状态
5、终止状态 TERMINATED
当线程 A 处于 WAITING、TIMED_WAITING 状态时,如果其他线程调用线程 A 的 interrupt() 方法,会使线程 A 返回到 RUNNABLE 状态,同时线程 A 的代码会触发 InterruptedException 异常。上面我们提到转换到 WAITING、TIMED_WAITING 状态的触发条件,都是调用了类似 wait()、join()、sleep() 这样的方法,我们看这些方法的签名,发现都会 throws InterruptedException 这个异常。这个异常的触发条件就是:其他线程调用了该线程的 interrupt() 方法。
每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体
通过该Thread对象的start()方法来启动这个线程,而非直接调用run()
注意点:
public class Test01 {
public static void main(String[] args) {
//创建线程对象 创建线程时new了多个任务类,所以 this不唯一,用作锁对象是不能互斥
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
//启动线程
t1.start();
t2.start();
}
}
//任务类继承了Thread类
class MyThread extends Thread{
//重写run方法,当前线程抢到cpu资源后,就会执行run方法
@Override
public void run() {
System.out.println("当前线程抢到资源了");
}
}
//Thread匿名类直接执行,适用于只使用一次
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
ystem.out.println("子线程");
}
}.start();
}
实现方式的好处
public class Test01 {
public static void main(String[] args) {
//创建Runnable接口实现类对象 作为参数船给Thread类含参构造器
Task task = new Task();
//使用Thread类含参构造器创建线程对象 使用的是同一个任务类所以this唯一
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
}
}
//任务类实现了Runnable接口
class Task implements Runnable{
//当前线程抢到cpu资源后,就会执行run方法
@Override
public void run() {
System.out.println("抢到资源了");
}
}
//使用Runnable的匿名内部类简化创建过程
Thread t2 =new Thread(new Runnable() {
@Override
public void run() {
}
},"子线程2");//给子线程命名
String name = t2.getName();
System.out.println("name = " + name);//子线程2
//使用lanbda
new Thread(()-> {System.out.println("lambda实现了多线程"+Thread.currentThread().getName()
);} ,"线程1").start();
Callable接口可以返回结果,抛出异常
Future就是对于具体的Runnable或者Callable任务的执行结果进行查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。异步任务执行完回调方法;
FutureTask是Future接口的一个实现类(唯一的实现类)
也就是说Future提供了三种功能:
1)判断任务是否完成;
2)能够中断任务;
3)能够获取任务执行结果。
Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。
import java.util.concurrent.*;
//任务类 可以有 属性 传入参数用作计算
public class CallTest implements Callable<String> {
private int temp;
public Test2(int temp) {
this.temp = temp;
}
public Test2() {
}
//重写call方法,可以自定义返回值泛型,不能传入参数 所以只能对属性进行编辑
@Override
public String call() throws Exception {
temp=temp+5;
System.out.println(Thread.currentThread().getName()+",执行了");
return temp;
}
//使用FutureTask执行单个任务
public static void main(String[] args) {
//3.创建Callable接口实现类的对象
NumThread numThread = new NumThread();
//4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
FutureTask futureTask = new FutureTask(numThread);
//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
new Thread(futureTask).start();
try {
//6.获取Callable中call方法的返回值
//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
Object sum = futureTask.get();
System.out.println("总和为:" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
//使用线程池 执行多个任务
public static void main(String[] args) throws ExecutionException, InterruptedException {
//定义线程池
ExecutorService executorService = Executors.newFixedThreadPool(2);
//将任务类传入 并执行
Future<String> submit = executorService.submit(new CallTest());
//获取执行结果
String s = submit.get();
System.out.println("获得callable方法返回结果:"+s);
//使用lambda表达式
Future<String> submit1 = executorService.submit(() -> {
System.out.println(Thread.currentThread().getName() + ",执行了");
return "天青色等烟雨";
});
//线程池要关闭
executorService.shutdown();
}
}
Runnable 接⼝不会返回结果或抛出检查异常,但是 Callable 接⼝ 可以。所以,如果任务不需要返回结果或抛出异常推荐使⽤ Runnable 接⼝,这样代码看起来会 更加简洁。
⼯具类 Executors 可以实现 Runnable 对象和 Callable 对象之间的相互转换。
Executors.callable(Runnable task )
或 Executors.callable(Runnable task,Object result )
execute()⽅法和 submit()
a.start方法start()用来启动一个线程,当调用start方法后,系统才会开启一个新的线程来执行用户定义的子任务
a.getId用来得到线程ID
a.getName和setName
//设置线程优先级,只会影响优先级 但不能决定
同优先级线程组成先进先出队列(先到先服务),使用时间片策略
对高优先级,使用优先调度的抢占式策略
a.setPriority(Thread.MAX_PRIORITY);//10
b.setPriority(Thread.NORM_PRIORITY);//5
c.setPriority(Thread.MIN_PRIORITY);//1
//返回线程优先级
a.getPriority()
//获取当前线程的对象 (哪个线程调用了run方法,就获取哪个线程的对象)
Thread t = Thread.currentThread();
//获取当前线程名称
System.out.println(t.getName() + ":" + i);
//休眠 该方法是静态方法,写在哪个线程中,哪个线程就休眠
Thread.sleep(1000);//休眠1000毫秒
//获取当前活跃线程数
int i = Thread.activeCount();
//礼让的含义:让当前线程退出CPU资源,又立马到争抢资源的状态
Thread.yield();
// 线程的合并 - join() 会让t线程先执行完毕
t.join();//让t线程加入到当前线程中
//结束线程,会直接结束线程,可能会产生脏数据过时的方法
t.stop
//改变线程状态,,会把一个方法执行完毕,即优雅的结束线程,不会产生脏数据
t.interrupt();
//设置该线程名称
t.setName(String name):
1. 自己写getThreadName/setThreadName
2. 调用父类的一个参数的构造方法和getName
3. Thread.currentThread().getName()
// 使用构造器给线程命名,早Thread子类中建立构造器
public 类名(String name){
super(name);//Thread变成有参构造了 在new线程对象时可以添加姓名
}
//获取当前线程状态 false-没有销毁 true-销毁
Thread.currentThread().isInterrupted()
//用来设置线程是否成为守护线程和判断线程是否是守护线程。
setDaemon和isDaemon
Java中的线程分为两类:一种是守护线程,一种是用户线程。
它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。
守护线程是用来服务用户线程的,通过在start()方法前调用
当所有的前台线程都消亡后,守护线程会自动消亡
* 注意:垃圾回收器就是守护线程
public class Daemon extends Thread{
@Override
public void run() {
while(true){
System.out.println("守护线程正在默默守护着前台线程");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
}
public static void main(String[] args) throws InterruptedException {
Daemon daemon = new Daemon();
daemon.setDaemon(true); //将当前线程设置为守护线程
daemon.start();
CompletableFuture实现了CompletionStage接口和Future接口,前者是对后者的一个扩展,增加了异步回调、流式处理、多个Future组合处理的能力,使Java在处理多任务的协同工作时更加顺畅便利。
如果并发的请求数量非常多,但每个线程执行的时间很短,这样就会频繁的创建和销毁 线程,如此一来会大大降低系统的效率。可能出现服务器在为每个请求创建新线程和销毁线 程上花费的时间和消耗的系统资源要比处理实际的用户请求的时间和资源更多。 那么有没有一种办法使执行完一个任务,并不被销毁,而是可以继续执行其他的任务 呢? 这就是线程池的目的了。线程池为线程生命周期的开销和资源不足问题提供了解决方 案。通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上。
-- 分析ThreadPoolExecutor类的构造方法源码--------------------------------
public ThreadPoolExecutor(
int corePoolSize, ------------- 核心线程数量
int maximumPoolSize, ------------- 最大线程数量
long keepAliveTime, ------------- 闲置时间,作用于核心线程数与最大线程数之间的线程
TimeUnit unit, ------------- keepAliveTime的时间单位(可以是毫秒、秒....)
BlockingQueue<Runnable> workQueue, -- 任务队列
ThreadFactory threadFactory, -------- 线程工厂
RejectedExecutionHandler handler ---- 达到了线程界限和队列容量时的处理方案(拒绝策略)
) {}
执行步骤:
1.创建线程池后
2.任务提交后,查看是否有核心线程:
3.1 没有 -> 就创建核心线程 -> 执行任务 -> 执行完毕后又回到线程池中
3.2 有 -> 查看是否有闲置核心线程:
4.1 有 -> 执行任务 -> 执行完毕后又回到线程池
4.2 没有 -> 查看当前核心线程数是否达到核心线程数量:
5.1 否 -> 就创建核心线程 -> 执行任务 -> 执行完毕后又回到线程池中
5.2 是 -> 查看任务列表是否装载满:
6.1 没有 -> 就放入列表中,等待出现闲置线程
6.2 装满 -> 查看是否有普通线程(核心线程数到最大线程数量之间的线程)
7.1 没有 -> 就创建普通线程 -> 执行任务 -> 执行完毕后又回到线程池中
7.2 有 -> 查看是否有闲置普通线程
7.1.1 有 -> 执行任务 -> 执行完毕后又回到线程池中
7.1.2 没有 -> 查看现在所有线程数量是否为最大线程数:
8.1 是 -> 执行处理方案(默认处理抛出异常)
8.2 否 ->就创建普通线程-> 执行任务 -> 执行完毕后又回到线程池中
注:
1.为了更好的理解,在这里区分核心线程和普通线程,实际上区分的没这么清楚,都是线程
2.默认的处理方案就是抛出RejectedExecutionException
队列名称 | 详解 |
---|---|
LinkedBlockingQueue无界任务队列 | 使用无界任务队列,线程池的任务队列可以无限制的添加新的任务,而线程池创建的最大线程数量就是你corePoolSize设置的数量,也就是说在这种情况下maximumPoolSize这个参数是无效的,哪怕你的任务队列中缓存了很多未执行的任务,当线程池的线程数达到corePoolSize后,就不会再增加了;若后续有新的任务加入,则直接进入队列等待,当使用这种任务队列模式时,一定要注意你任务提交与处理之间的协调与控制,不然会出现队列中的任务由于无法及时处理导致一直增长,直到最后资源耗尽的问题 |
SynchronousQueue 同步任务队列 直接提交任务队列 | 使用直接提交任务队列,队列没有容量,每执行一个插入操作就会阻塞,需要再执行一个删除操作才会被唤醒,反之每一个删除操作也都要等待对应的插入操作。 任务队列为SynchronousQueue,创建的线程数大于maximumPoolSize时,直接执行了拒绝策略抛出异常。 使用SynchronousQueue队列,提交的任务不会被保存,总是会马上提交执行。如果用于执行任务的线程数量小于maximumPoolSize,则尝试创建新的线程,如果达到maximumPoolSize设置的最大值,则根据你设置的handler执行拒绝策略。因此这种方式你提交的任务不会被缓存起来,而是会被马上执行,在这种情况下,你需要对你程序的并发量有个准确的评估,才能设置合适的maximumPoolSize数量,否则很容易就会执行拒绝策略; |
ArrayBlockingQueue有界任务队列 | 使用有界任务队列,若有新的任务需要执行时,线程池会创建新的线程,直到创建的线程数量达到corePoolSize时,则会将新的任务加入到等待队列中。若等待队列已满,即超过ArrayBlockingQueue初始化的容量,则继续创建线程,直到线程数量达到maximumPoolSize设置的最大线程数量,若大于maximumPoolSize,则执行拒绝策略。在这种情况下,线程数量的上限与有界任务队列的状态有直接关系,如果有界队列初始容量较大或者没有达到超负荷的状态,线程数将一直维持在corePoolSize以下,反之当任务队列已满时,则会以maximumPoolSize为最大线程数上限。 |
PriorityBlockingQueue优先任务队列 | 使用优先任务队列,它其实是一个特殊的无界队列,它其中无论添加了多少个任务,线程池创建的线程数也不会超过corePoolSize的数量,只不过其他队列一般是按照先进先出的规则处理任务,而PriorityBlockingQueue队列可以自定义规则根据任务的优先级顺序先后执行。 |
ThreadPoolExecutor自带的拒绝策略有四种,都实现了RejectedExecutionHandler接口
比如:new ThreadPoolExecutor.AbortPolicy()
拒绝策略 | 解释 |
---|---|
AbortPolicy | 当有任务添加到线程池被拒绝时,会抛出RejectedExecutionException异常 线程池默认的拒绝策略。必须处理好抛出的异常,否则会打断当前的执行流程,影响后续的任务执行。 |
DiscardPolicy | 当有任务添加到线程池被拒绝时,直接丢弃,其他啥都没有 |
CallerRunsPolicy | 当有任务添加到线程池被拒绝时,线程池会将被拒绝的任务添加到线程池正在运行的线程中去运行。 一般并发比较小,性能要求不高,不允许失败。但是,由于调用者自己运行任务,如果任务提交速度过快,可能导致程序阻塞,性能效率上必然的损失较大 |
DiscardOledestPolicy | 当有任务添加到线程池被拒绝时,线程池会丢弃阻塞队列中末尾的任务(最老的任务–第一个添加的任务),然后将被拒绝的任务添加到末尾。 如果项目中有允许丢失任务的需求,可以使用 |
public class Test {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 1, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(1), Executors.defaultThreadFactory(), new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println(r.toString()+"执行了拒绝策略");
}
});
for (int i = 1; i <= 10; i++) {
pool.execute(new Task());
}
pool.shutdown();
}
}
class Task implements Runnable{
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
ThreadPoolExecutor扩展主要是围绕beforeExecute()、afterExecute()和terminated()三个方法的重写, 通过这三个方法我们可以监控每个任务的开始和结束时间,或者其他一些功能。
方法 解释 beforeExecute 线程池中任务运行前执行 afterExecute 线程池中任务运行完毕后执行 terminated 线程池退出后执行 下面我们可以通过代码实现一下
ThreadPoolExecutor pool = new ThreadPoolExecutor(
10, 10, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(10),
new ThreadFactory() {
int threadNum = 1;
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r,"线程"+threadNum++);
return t;
}
}, new ThreadPoolExecutor.AbortPolicy()
){
@Override
protected void beforeExecute(Thread t,Runnable r) {
System.out.println("准备执行:"+ Thread.currentThread().getName());
}
@Override
protected void afterExecute(Runnable r,Throwable t) {
System.out.println("执行完毕:"+ Thread.currentThread().getName());
}
@Override
protected void terminated() {
System.out.println("线程池退出");
}
};
for (int i = 1; i <= 10; i++) {
pool.execute(new Task());
}
pool.shutdown();
}
}
class Task implements Runnable{
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println("执行中:" + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
runAsync(一个Runnable接口,线程池)
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class CompletableFutureTest {
private static final AtomicInteger ATOMIC_INTEGER = new AtomicInteger(0);
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(10, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
//创建线程
Thread thread = new Thread(r);
thread.setName("cdqf_" + ATOMIC_INTEGER.incrementAndGet());
return thread;
}
});
CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(() -> System.out.println(Thread.currentThread().getName() + ",go go go"), executorService);
System.out.println(voidCompletableFuture.get());
executorService.shutdown();
}
}
一个参数
runAsync(一个Runnable接口) 默认使用这个线程池ForkJoinPool.commonPool()
CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行了");
});
System.out.println( voidCompletableFuture.get());
System.out.println("睡完了.....");
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class CompletableFutureTest {
private static AtomicInteger atomicInteger = new AtomicInteger(0);
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(10, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("cdqf-"+atomicInteger.incrementAndGet());
return thread;
}
});
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"run方法被调用了");
},executorService);
System.out.println(completableFuture.get());
System.out.println("completableFuture runasync执行结束");
executorService.shutdown();
}
}
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class CompletableFutureTest {
private static AtomicInteger atomicInteger = new AtomicInteger(0);
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(10, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("cdqf-"+atomicInteger.incrementAndGet());
return thread;
}
});
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在执行");
return "hello CompletableFuture";
}, executorService);
System.out.println(completableFuture.get());
System.out.println("带返回值的CompletableFuture执行结束");
executorService.shutdown();
}
}
跟supplyAsync使用同一个线程运行
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class CompletableFutureTest {
private static AtomicInteger atomicInteger = new AtomicInteger(0);
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(10, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("cdqf-"+atomicInteger.incrementAndGet());
return thread;
}
});
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在执行");
return "hello CompletableFuture";
}, executorService).thenApply(s->{
System.out.println("thenApply开始执行,执行的线程为:"+Thread.currentThread().getName());
System.out.println("supplyAsync方法执行完毕,并获得supplyAsync的返回值:"+s);
return s.toUpperCase();
});
System.out.println(completableFuture.get());
System.out.println("带返回值的CompletableFuture执行结束");
executorService.shutdown();
}
}
再上面的基础上,thenApplyAsync可以使用另外的线程执行,当然也可以指定线程池
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class CompletableFutureTest {
private static AtomicInteger atomicInteger = new AtomicInteger(0);
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(10, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("cdqf-"+atomicInteger.incrementAndGet());
return thread;
}
});
ExecutorService executorService2 = Executors.newFixedThreadPool(10, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("线程池二-"+atomicInteger.incrementAndGet());
return thread;
}
});
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在执行");
return "hello CompletableFuture";
}, executorService).thenApplyAsync(s->{
System.out.println("thenApply开始执行,执行的线程为:"+Thread.currentThread().getName());
System.out.println("supplyAsync方法执行完毕,并获得supplyAsync的返回值:"+s);
return s.toUpperCase();
},executorService2);
System.out.println(completableFuture.get());
System.out.println("带返回值的CompletableFuture执行结束");
executorService.shutdown();
executorService2.shutdown();
}
}
跟上面的方法类似,只不过没有返回值,参数是消费者接口
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class CompletableFutureTest {
private static AtomicInteger atomicInteger = new AtomicInteger(0);
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(10, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("cdqf-"+atomicInteger.incrementAndGet());
return thread;
}
});
ExecutorService executorService2 = Executors.newFixedThreadPool(10, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("线程池二-"+atomicInteger.incrementAndGet());
return thread;
}
});
CompletableFuture<Void> completableFuture = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在执行");
return "hello CompletableFuture";
}, executorService).thenAccept(s-> System.out.println("获得上面的结果:"+s+",但是自己没有返回值了"));
System.out.println(completableFuture.get());
System.out.println("带返回值的CompletableFuture执行结束");
executorService.shutdown();
executorService2.shutdown();
}
}
thenAcceptAsync跟上面一样只不过可以换线程,也可以换线程池
跟上面方法类似,只不过拿不到参数,参数为一个runnable对象
组装多个有关联关系的异步任务,比如第二个future依赖第一个的id信息,得到最终的结果
当然也可以通过重载方法指定线程池
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class CompletableFutureTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<String> completableFuture = getProjectId().thenCompose(id -> getProjectById(id));
System.out.println("获得最终的结果为:"+completableFuture.get());
}
//根据某某条件获得商品id
private static CompletableFuture<Integer> getProjectId(){
return CompletableFuture.supplyAsync(()->{
System.out.println("当前执行的线程为:"+Thread.currentThread().getName());
System.out.println("这里面执行了非常多的操作然后得到了一个项目ID");
return 10;
});
}
//根据上面获得商品id去查询商品的详细信息
private static CompletableFuture<String> getProjectById(int id){
return CompletableFuture.supplyAsync(()->{
System.out.println("当前执行的线程为:"+Thread.currentThread().getName());
System.out.println("这里面执行了非常多的操作然后根据id获得了商品的详细信息,id:"+id);
return "华为手机";
});
}
}
组装两个没有参数关联的异步CompletableFuture
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class CompletableFutureTest {
private static final AtomicInteger ATOMIC_INTEGER = new AtomicInteger(0);
public static void main(String[] args) throws ExecutionException, InterruptedException {
//插入一条员工信息,一组任务插入头像 一组任务生活照
CompletableFuture<String> completableFuture1 = CompletableFuture.supplyAsync(() -> {
System.out.println("插入头像,并返回头像文件id");
return "abc";
});
CompletableFuture<String> completableFuture2 = CompletableFuture.supplyAsync(() -> {
System.out.println("插入生活照,并返回生活照文件id");
return "bbc";
});
CompletableFuture<String> completableFuture = completableFuture1.thenCombine(completableFuture2, (headId, shzId) -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("获得上面的结果:headId:" + headId + ",shzId:" + shzId);
System.out.println("执行数据库插入");
return "插入success";
});
String join = completableFuture.join();
System.out.println(join);
}
}
多个任务执行成功
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class CompletableFutureTest {
private static final AtomicInteger ATOMIC_INTEGER = new AtomicInteger(0);
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Void> completableFuture1 = CompletableFuture.runAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第一个task");});
CompletableFuture<Void> completableFuture2 = CompletableFuture.runAsync(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第二个task");});
CompletableFuture<Void> completableFuture3 = CompletableFuture.runAsync(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第三个task");});
//希望上面三个任务都完成才继续后面
CompletableFuture<Void> allOf = CompletableFuture.allOf(completableFuture1, completableFuture2, completableFuture3);
allOf.join();
System.out.println("三个任务全部结束,现在可以继续后面的工作");
}
}
返回最先执行结束的CompletableFuture
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class CompletableFutureTest {
private static final AtomicInteger ATOMIC_INTEGER = new AtomicInteger(0);
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<String> completableFuture1 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000);
System.out.println("第一个方法ok");
} catch (InterruptedException e) {
e.printStackTrace();
}
return "first blood";
});
CompletableFuture<String> completableFuture2 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(3000);
System.out.println("第二个方法ok");
} catch (InterruptedException e) {
e.printStackTrace();
}
return "second blood";
});
CompletableFuture<String> completableFuture3 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(5000);
System.out.println("第三个方法ok");
} catch (InterruptedException e) {
e.printStackTrace();
}
return "third blood";
});
CompletableFuture<Object> objectCompletableFuture = CompletableFuture.anyOf(completableFuture1, completableFuture2, completableFuture3);
Object o = objectCompletableFuture.get();
System.out.println("某一个方法执行结束:"+o);
}
}
前面有一个出现异常,后面的都不会执行
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class CompletableFutureTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Void> voidCompletableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("supplyAsync方法执行");
throw new RuntimeException("在supplyAsync方法中出现异常");
}).thenApply(s ->
{
System.out.println("因为上面出现了异常我就不会执行了");
return "不会执行";
}).thenAccept(t -> {
System.out.println("前面都没执行更别说我了");
});
voidCompletableFuture.join();
System.out.println("出现异常不执行了");
}
}
处理方式一exceptionally:出现异常才会执行,返回一个降级结果
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class CompletableFutureTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<String> voidCompletableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("supplyAsync方法执行");
throw new RuntimeException("在supplyAsync方法中出现异常");
}).thenApply(s ->
{
System.out.println("因为上面出现了异常我就不会执行了");
return "不会执行";
})
.exceptionally(e->
{
System.out.println(Thread.currentThread().getName()+"执行过程中出现了异常了"+e);
return "凉凉";
}
);
System.out.println(voidCompletableFuture.get());;
System.out.println("出现异常不执行了");
}
}
handle():有没有异常都会执行,可以根据判断来返回不同的东西
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class CompletableFutureTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<String> voidCompletableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("supplyAsync方法执行");
//throw new RuntimeException("在supplyAsync方法中出现异常");
return "没有异常继续下面执行";
}).thenApply(s ->
{
System.out.println("因为上面出现了异常我就不会执行了");
return "不会执行";
})
.handleAsync((result,e)->
{
if(e!=null) {
System.out.println(Thread.currentThread().getName() + "执行过程中出现了异常了" + e);
return "凉凉";
}else {
System.out.println("程序没有出现异常,返回正常结果");
return result;
}
}
,Executors.newSingleThreadExecutor());
System.out.println(voidCompletableFuture.get());;
System.out.println("出现异常不执行了");
}
}