什么是原子类
一度认为原子是不可分割的最小单位,故原子类可以认为其操作都是不可分割
为什么要有原子类?
对多线程访问同一个变量,我们需要加锁,而锁是比较消耗性能的,JDk1.5之后,
新增的原子操作类提供了一种用法简单、性能高效、线程安全地更新一个变量的方式,
这些类同样位于JUC包下的atomic包下,发展到JDk1.8,该包下共有17个类,
囊括了原子更新基本类型、原子更新数组、原子更新属性、原子更新引用
原子更新基本类型
发展至JDk1.8,基本类型原子类有以下几个:
- AtomicBoolean、AtomicInteger、AtomicLong、DoubleAccumulator、DoubleAdder、
LongAccumulator、LongAdder
大致可以归为3类
- AtomicBoolean、AtomicInteger、AtomicLong 元老级的原子更新,方法几乎一模一样
- DoubleAdder、LongAdder 对Double、Long的原子更新性能进行优化提升
- DoubleAccumulator、LongAccumulator 支持自定义运算
实例1:
AtomicInteger atomicInteger = new AtomicInteger();
实例2:
//输入一个数字,如果比上一个输入的大,则直接返回,如果小,则返回上一个
public static void main(String[] args) {
LongAccumulator longAccumulator = new LongAccumulator((left, right) ->
left * right, 0L
);
longAccumulator.accumulate(3L);
System.out.println(longAccumulator.get());
longAccumulator.accumulate(5L);
System.out.println(longAccumulator.get());
}
原子更新数组类型
- 原子更新数组类型
AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
实例:
public static void main(String[] args) {
int[] arr = new int[]{3, 2};
AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(arr);
System.out.println(atomicIntegerArray.addAndGet(1, 8));
int i = atomicIntegerArray.accumulateAndGet(0, 2, (left, right) ->
left * right / 3
);
System.out.println(i);
}
原子的更新属性
原子地更新某个类里的某个字段时,就需要使用原子更新字段类
Atomic包提供了以下4个类进行原子字段更新:
AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicStampedReference、AtomicReferenceFieldUpdater
使用上述类的时候,必须遵循以下原则:
- 字段必须是volatile类型的,在线程之间共享变量时保证立即可见
- 字段的描述类型是与调用者与操作对象字段的关系一致。
- 也就是说调用者能够直接操作对象字段,那么就可以反射进行原子操作。
- 对于父类的字段,子类是不能直接操作的,尽管子类可以访问父类的字段。
- 只能是实例变量,不能是类变量,也就是说不能加static关键字。
- 只能是可修改变量,不能使final变量,因为final的语义就是不可修改。
- 对于AtomicIntegerFieldUpdater和AtomicLongFieldUpdater只能修改int/long类型的字段,不能修改其包装类型(Integer/Long)。
- 如果要修改包装类型就需要使用AtomicReferenceFieldUpdater。
实例:
public static void main(String[] args) {
AtomicLongFieldUpdater<Student> longFieldUpdater = AtomicLongFieldUpdater.newUpdater(Student.class, "id");
Student xdclass = new Student(1L, "xdclass");
longFieldUpdater.compareAndSet(xdclass, 1L, 100L);
System.out.println("id="+xdclass.getId());
AtomicReferenceFieldUpdater<Student, String> referenceFieldUpdater = AtomicReferenceFieldUpdater.newUpdater(Student.class, String.class, "name");
referenceFieldUpdater.compareAndSet(xdclass, "xdclass", "wiggin");
System.out.println("name="+xdclass.getName());
}
}
class Student{
volatile long id;
volatile String name;
public Student(Long id, String name) {
this.id = id;
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
原子更新引用
- AtomicReference:用于对引用的原子更新
- AtomicMarkableReference:带版本戳的原子引用类型,版本戳为boolean类型。
- AtomicStampedReference:带版本戳的原子引用类型,版本戳为int类型。
public static void main(String[] args) {
AtomicReference<Student> studentAtomicReference = new AtomicReference<>();
Student student = new Student(1L, "xdclass");
Student student1 = new Student(2L, "wiggin");
//需要先对studentAtomicReference进行初始化操作
studentAtomicReference.set(student);
studentAtomicReference.compareAndSet(student, student1);
Student student2 = studentAtomicReference.get();
System.out.println(student2.getName());
}
}
class Student{
private long id;
private String name;
public Student(long id, String name) {
this.id = id;
this.name = name;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
目录:
ThreadPoolExecutor:
其所有的构造函数如下:
corePoolSize 线程池中核心线程的数量
maximumPoolSize 线程池中最大线程数量
keepAliveTime 非核心线程的超时时长,当系统中非核心线程闲置时间超过keepAliveTime之后,则会被回收。如果ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,则该参数也表示核心线程的超时时长
unit 第三个参数的单位,有纳秒、微秒、毫秒、秒、分、时、天等
workQueue 线程池中的任务队列,该队列主要用来存储已经被提交但是尚未执行的任务。存储在这里的任务是由ThreadPoolExecutor的execute方法提交来的。
threadFactory 为线程池提供创建新线程的功能,这个我们一般使用默认即可
handler 拒绝策略,当线程无法执行新任务时(一般是由于线程池中的线程数量已经达到最大数或者线程池关闭导致的),默认情况下,当线程池无法处理新线程时,会抛出一个RejectedExecutionException。
FixedThreadPool:
其内部的实现还是ThreadPoolExecutor,而它的特点在没有非核心线程,只存在核心线程并且无过期时间
singleThreadExecutor:
与FixedThreadPool类似,但与之不同的地方在于它只有一条核心线程
CachedThreadPool:
内部实现还是TreadPoolExecutor,它的特点是没有核心线程,只有非核心线程,并且设有非核心线程的过期时间,过期时间是60秒
ScheduledThreadPool:
ScheduledThreadPool是单独封装的一个类,其内部不是直接创建了一个ThreadPoolExecutor,而是集成了ThreadPoolExecutor,它的特点是可以延迟执行任务
线程池拒绝策略
- AbortPolicy:该策略直接抛出异常,阻止系统正常工作 - CallerRunsPolicy:只要线程池没有关闭,该策略直接在调用者线程中,执行当前被丢弃的任务(叫老板帮你干活) - DiscardPolicy:直接啥事都不干,直接把任务丢弃 - DiscardOldestPolicy:丢弃最老的一个请求(任务队列里面的第一个),再尝试提交任务 - 或者自定义实现RejectedExecutionHandler接口shutDown()和shutDownNow()
shutDown():
当调用该方法的的时候,会将线程的状态设置为shutdown状态,并且禁止再向线程池中添加任务,
否则会抛出异常,而正在的执行的线程会继续执行,并且将队列弄未执行的任务执行完毕后才会
退出
shutDownNow():
当调用该方法的的时候,会将线程的状态设置为STOP状态,并且不会再处理队列中等待执行的任务,
不过会将这一部分任务进行返回,而正在执行的线程是试图去终止它,而终止的方式调用了interrupt
函数,而interrupt也只是会改变当前这条线程的状态标记,代码中如有:
Thread.sleep、Thread.join、Object.wait、LockSupport.park等在检查到线程的中断状态时,
会抛出InterruptedException,同时会清除线程的中断状态,如果没有,并不会中断线程任务执行,
需要等正在执行的所以任务执行完毕以后,才会退出
常用的几种workQueue
ArrayBlockingQueue:
其内部的存储实现是一个数组,值得一说的是其内部的存和取都是公用一把锁,其构造函数形参中必须指定容量的大小,另外它的队列顺序是FIFO
linkedBlockingQueue:
其内部的存储实现是一个链表,内部维护了一个Node类,其可以不指定容量大小,默认值大小是Intger.MAX_VALUE,就是2的32次方-1.而其存和取是独立的两把安全锁
PriorityBlockingQueue:
和linkedBlockingQueue类似,与之不同的是,其队列的顺序并不依照于FIFO,而是取决于Comparator,而我们可以自定义实现Comparator,实现自定义排序,从而实现自定义优先级
SynchronousQueue:
其内部并没有维护一个存储数据的列表,所以是无法进行遍历,插入等操作的,其写入和读取都是交替完成,当生产者线程想存入数值是需等消费者线程将值取出之后才能写入,这是SynchronousQueue的特点.
总结:
在实际的使用中,使用的比较多的是linkedBlockingQueue和PriorityBlockingQueue;
使用PriorityBlockingQueue是为了满足自定义队列顺序的要求;
而使用linkedBlockingQueue和未选择相似的ArrayBlockingQueue是因为linkedBlockingQueue数据的吞吐量会比它大,而比他大的原因在于两方面:
一方面是数据结构的原因,数组的地址是连续性的在存入和取出的时候,一整个数组数据的存储位置都需要位移,而链表式前后数据都不需要位移只需要修改前后值索引所需的指针指向.
二方面是LinkedBlockingQueue的存入和读取分别是独立的两把锁,而ArrayBlockingQueue存入和读取是公用一把锁,显然在高并发的时候linkedBlockingQueue的数据流通的通透性更强.
BlockingQueue常用的API:
存入:
add,offer,put
取出:
remove,poll,take
他们都存在着对应关系:
add — remove
offer — poll
put — take
add:向BlockingQueue存入一个值,当队列未满可以存入的时候,返回true,当满了无法存入则抛出异常,其内部底层调用的是offer函数
offer:向BlockingQueue存入一个值,当队列未满可以存入的时候,返回true,当满了无法存入则返回false
put:向BlockingQueue存入一个值,当队列未满可以存入的时候,直接存入,而当队列满了无法存入的时候则调用此方法的线程被阻断,直到队列有空间再继续
remove:向BlockingQueue取出一个值,当能取出值则取出,当为空没有的时候就抛出异常,其内部底层调用的是poll函数
poll:向BlockingQueue取出一个值,当能取出值则取出,返回true当为空没有的时候就返回false
take:向BlockingQueue取出一个值,当能取出值则取出,当为空没有的时候就调用此方法的线程被阻断,直到有值可以被取出.