JUC学习小结
并发容器
BlockingQueue
阻塞队列接口,先进先出(FIFO)
主要的实现类有ArrayBlockingQueue、LinkedBlockingQueue、DelayQueue、PriorityBlockingQueue、SynchronousQueue
1.ArrayBlockingQueue
基于数组实现,内部维护了一个定长数组items,用于缓存队列中的数据对象,内部还有两个整型变量putIndex和takeIndex,用来标识队列头部和尾部在数组中的位置共用一个锁资源ReentrantLock lock,但其读写和获取足够轻巧,故没必要再引入单独的锁机制
生产者消费者
以下以生产者消费者为例
public class ArrayBlockingQueueDemo {
// 队列的最大长度
private final int MAX_LEN = 10;
// 实现多线程可选择继承Thread,实现Runnable接口和实现Callable接口
class Provider extends Thread {
ArrayBlockingQueue<Integer> queue;
public Provider (ArrayBlockingQueue<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
provider();
}
private synchronized void provider() {
while (true) {
while (queue.size() == MAX_LEN) {
System.out.println("队列资源已满");
// flag = false;
}
try {
queue.put(1);
System.out.println("生产-当前队列资源数:"+queue.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Consumer extends Thread {
ArrayBlockingQueue<Integer> queue;
public Consumer (ArrayBlockingQueue<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
consumer();
}
private void consumer() {
while (true) {
while (queue.size() == 0) {
System.out.println("队列内无资源");
}
queue.poll();
System.out.println("消费-当前队列资源数:"+queue.size());
// 生产者等待消费者释放资源
try {
Thread.sleep(51);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
ArrayBlockingQueueDemo pac = new ArrayBlockingQueueDemo();
ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(pac.MAX_LEN);
Consumer consumer1 = pac.new Consumer(queue);
Provider provider1 = pac.new Provider(queue);
provider1.start();
consumer1.start();
}
}
2.LinkedBlockingQueue
基于链表实现,内部维护了一个数据缓冲队列(由一个链表构成)
生产者消费者分别采用独立的锁ReentrantLock putLock和ReentrantLock takeLock,高并发情况下可以并行操作数据。
3.DelayQueue
其中的元素只有给定时间到了,才能从队列中获取数据
该队列没有大小限制,故生产者可以一直放资源。
4.PriorityBlockingQueue
基于优先级的阻塞队列,优先级由传入的Compare对象决定
不会阻塞生产者,但在无可用数据时才会阻塞消费者,因此生产速度必须大于消费速度,避免安全隐患。
5.SynchronousQueue
无缓冲区的等待队列,生产者直接将数据交给消费者,消费者直接找生产者要数据,若一方没找到则等待。
BlockingDeque
双端阻塞队列,相较于BlockingQueue头部进尾部出,该队列头部尾部都可进出,可以自由选择
实现类以LinkedBlockingDeque为例(基于链表的双端阻塞队列)
public class LinkedBlockingDequeDemo {
private static int MAX_NUM = 10000;
private static LinkedBlockingDeque<Integer> deque = new LinkedBlockingDeque<>();
static class Privider implements Runnable {
@Override
public void run() {
for (int i = 0; i < MAX_NUM; i++) {
deque.addFirst(i);
System.out.println("添加" + i);
try {
Thread.sleep(40);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class Consumer implements Runnable {
@Override
public void run() {
for (int i = 0; i < MAX_NUM; i++) {
deque.getLast();
try {
System.out.println("获取" + deque.takeLast());
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
Privider privider = new Privider();
Consumer consumer = new Consumer();
Thread prividerThread = new Thread(privider);
Thread consumerThread = new Thread(consumer);
prividerThread.start();
consumerThread.start();
}
}
ConcurrentHashMap
与HashMap类似,但线程安全,1.7采用分段锁机制,用Segment数组存数据,对每个Segment数组加锁,1.8采用CAS
public class ConcurentHashMaoDemo {
private static int MAX_NUM = 1000;
private static ConcurrentHashMap<Integer, String>
map = new ConcurrentHashMap<>();
static class Provider implements Runnable {
@Override
public void run() {
for (int i = 0; i < MAX_NUM; i++) {
map.put(i, "value" + i);
System.out.println("添加了:value" + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class Consumer implements Runnable {
@Override
public void run() {
for (int i = 0; i < MAX_NUM; i++) {
System.out.println("获取了:" + map.get(i));
try {
Thread.sleep(150);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
Provider provider = new Provider();
Consumer consumer = new Consumer();
Thread providerThread = new Thread(provider);
Thread consumerThread = new Thread(consumer);
providerThread.start();
consumerThread.start();
}
}
Executor框架
把整个线程分为任务、任务的执行、对执行结果的操作
任务:包括被执行的任务需要实现的接口:Runnable或Callable接口
任务的执行:考扩任务执行的核心接口Executor,继承自Executor的ExecutorService接口。(Executor中有两个ExecutorService的实现类ThreadPoolExecutor和ScheduledThreadPoolExecutor)
对执行结果的操作:包括Future接口和实现它的FutureTask
Executor
Executor框架的基础接口,规定了execute()方法
ExecutorService
继承自Executor的接口
ThreadPoolExecutor
线程池的核心实现类,用来执行被提交的任务。参数:
int corePoolSize:核心线程池的大小
int maximumPoolSize:最大线程池的大小
long keepAliveTime:多余的空闲线程等待新任务的最长时间
TimeUnit unit:上面keepAliveTime时间的单位(毫秒/秒/分/时)
BlockingQueue workQueue:工作队列,存放任务
ScheduledThreadPoolExecutor
可以在给定的延迟后执行任务,或者定期执行任务
Future接口和实现它的FutureTask
代表任务执行的结果,可以用来查询、取消执行任务的结果
Runnable或Callable接口的实现类
执行的任务,都可以被ThreadPoolExecutor和ScheduledThreadPoolExecutor执行,可以交给ExecutorService.submit()执行,Runnable可以直接交给ExecutorService.execute()执行
Executors
提供线程池相关操作,提供一系列工厂方法创建线程池,返回的线程池都实现了ExecutorService接口
线程池
Executors提供了创建四种线程池的工厂方法
FixedThreadPool
固定线程数的线程池,使用LinkedBlockingQueue作为工作队列,以下为用Executors调用工厂方法
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
ExecutorService executorService = Executors.newFixedThreadPool(5);
CachedThreadPool
可缓存的线程池,使用SynchronousQueue作为工作队列,采用直接提交的提交策略。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
ExecutorService executorService = Executors.newCachedThreadPool();
SingleThreadPool
只有一个核心线程的线程池,串行执行任务
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
ExecutorService executorService = Executors.newSingleThreadExecutor();
ScheduledThreadPool
支持定时及周期性执行任务的线程池
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
ExecutorService executorService = Executors.newScheduledThreadPool(5);
Atomic原子类
以i++为例,它实际上分为"读-改-写"三个步骤,保证原子性就是保证这三个步骤不可分割,atomic中实现的基础是CAS(Compare-And-Swap),使用一个Unsafe对象调用底层的native函数实现,。
CAS
核心在于用期望值与实际值比较,如果相同则执行操作提交,否则不做修改。例如一整型变量i=10,线程A想把i的值+1,则此时期望值=10,更新后的新值为11;但此时线程B已经把i的值修改成了11,则此时i的实际值为11,与线程A中的期望值比较不一样,则不做修改。
原子更新基本类型
包括AtomicInteger,AtomicLong,AtomicBoolean,对应的值value用volatile修饰,保证内存可见;用CAS算法保证数据的原子性。以下以AtomicInteger为例,常用方法包括:
int get():获取当前值
void set(int newValue):设置为给定值
void lazySet(int newValue):最终设置为给定值
int getAndSet(int newValue):获取当前值,再设置为给定值
boolean compareAndSet(int expect, int update):内存值与预期值(expect)相同,则设置为新值(update)
int getAndIncrement():先获取当前值,再以原子方式将当前值+1
public class AtomicIntegerDemo {
public static AtomicInteger atomicFlag = new AtomicInteger();
public static int basicFlag = 0;
public static int MAX_NUM = 35000;
public static class MyAtomicAddRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < MAX_NUM; i++) {
// AtomicInteger的自增方法
atomicFlag.incrementAndGet();
// System.out.println(atomicFlag.get());
}
System.out.println("flag = "+atomicFlag.get());
}
}
public static class MyBasicAddRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < MAX_NUM; i++) {
basicFlag++;
// System.out.println(basicFlag);
}
System.out.println("flag = "+basicFlag);
}
}
public static void main(String[] args) {
MyAtomicAddRunnable r1 = new MyAtomicAddRunnable();
MyAtomicAddRunnable r2 = new MyAtomicAddRunnable();
MyBasicAddRunnable r3 = new MyBasicAddRunnable();
MyBasicAddRunnable r4 = new MyBasicAddRunnable();
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
}
}
没使用原子类型时输出的值为:flag = 69999
使用了原子类型时输出值为:flag = 70000
原子更新引用类型
包括AtomicReference,AtomicStampedReference,AtomicMarkableReference,前面原子基本类型每次只能更新一个变量,当存在多个变量,如一个对象的多个属性时,就需要引用类型
public class AtomicReferenceDemo {
private static AtomicReference<Person> reference = new AtomicReference();
public static void main(String[] args) {
Person person1 = new Person("zs", 20);
Person person2 = reference.getAndSet(person1);
System.out.println("reference: "+reference.get());
System.out.println("person2: "+person2);
System.out.println("=====================");
// 先将旧值赋给person3,自己的值再set为传入的Person
Person person3 = reference.getAndSet(new Person("ls", 22));
person2 = reference.get();
System.out.println("reference: "+reference.get());
System.out.println("person1: "+person1);
System.out.println("person2: "+person2);
System.out.println("person3: "+person3);
// 同样采用CAS,赋的是内存地址,equals结果为true
System.out.println("person1.equals(person3) = " + person1.equals(person3));
System.out.println("=====================");
// 如果reference的值为person2的话,就将其set为person1
System.out.println(reference.compareAndSet(person2, person1));
System.out.println("reference: "+reference.get());
System.out.println("person1: "+person1);
System.out.println("person2: "+person2);
System.out.println("person1.equals(person2) = " + person1.equals(person2));
System.out.println("person1.equals(reference.get()) = " + person1.equals(reference.get()));
}
}
但根据CAS的特点,如果我们在线程1用AtomicReference对某一对象进行操作,提交之前有一个线程2先修改了这一对象的值后又修改了回来,导致线程1在获取对象内存值(valueOffset)时,以为该对象没有被修改过,可能会出一些安全隐患,所以引入AtomicStampedReference,AtomicMarkableReference加入版本号和修改标记表示对象是否被修改过。
public class AtomicStampedReferenceDemo {
private static final Integer DEFAULT_NUM = 50;
private static final Integer UPDATE_NUM = 55;
private static final Integer TEM_NUM = 54;
private static AtomicStampedReference<Integer> reference = new AtomicStampedReference(DEFAULT_NUM, 1);
/**
* 线程A:将reference内的值由DEFAULT_NUM修改为UPDATE_NUM
* 线程B:将reference内的值由DEFAULT_NUM修改为TEM_NUM,再由TEM_NUM修改回DEFAULT_NUM
*/
public static void main(String[] args) {
new Thread(() -> {
// 当前值
Integer value = reference.getReference();
//当前版本号
int stamp = reference.getStamp();
System.out.println(Thread.currentThread().getName() + "===当前值为:" + value +
",当前版本号为:" + stamp);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (reference.compareAndSet(value, UPDATE_NUM, stamp, stamp + 1)) {
System.out.println(Thread.currentThread().getName() + "===当前值为:" + reference.getReference() +
",当前版本号为:" + reference.getStamp());
} else {
System.out.println("版本号不同,更新失败");
}
},"线程A").start();
new Thread(() -> {
// 确保线程A先执行
Thread.yield();
// 当前值
Integer value = reference.getReference();
//当前版本号
int stamp = reference.getStamp();
System.out.println(Thread.currentThread().getName() + "===当前值为:" + reference.getReference() +
",当前版本号为:" + reference.getStamp());
reference.compareAndSet(reference.getReference(), TEM_NUM, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName() + "===当前值为:" + reference.getReference() +
",当前版本号为:" + reference.getStamp());
reference.compareAndSet(reference.getReference(), DEFAULT_NUM, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName() + "===当前值为:" + reference.getReference() +
",当前版本号为:" + reference.getStamp());
}, "线程B").start();
}
}
输出结果:
线程A===当前值为:50,当前版本号为:1
线程B===当前值为:50,当前版本号为:1
线程B===当前值为:54,当前版本号为:2
线程B===当前值为:50,当前版本号为:3
版本号不同,更新失败
原子更新数组类型
包括AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray,是存储前面基本类型变量的数组,以下以AtomicIntegerArray为例
常用方法:
int length():得到当前数组长度
int get(int i):得到下标为i处的元素
void set(int i, int newValue):将下标i处的元素设置为给定值
void lazySet(int i, int newValue):将下标i处的元素最终设置为给定值
boolean compareAndSet(int i, int expect, int update):比较i处元素是否为期待值,true则设置为给定值
int getAndIncrement(int i):i处元素值+1
public class AtomicIntegerArrayDemo {
private static int[] intArray = new int[]{115, 26, 34, 49, 58};
private static AtomicIntegerArray array = new AtomicIntegerArray(intArray);
public static void main(String[] args) {
System.out.println(array.length());
System.out.println("下标为2的值为:" + array.get(2));
array.set(2, 33);
System.out.println("set后下标为2的值为:" + array.get(2));
array.lazySet(2, 36);
System.out.println("lazySet后下标为2的值为:" + array.get(2));
System.out.println(array.compareAndSet(2, 38, 39));
System.out.println("自增前值为:" + array.getAndIncrement(0));
System.out.println("自增后为:" + array.get(0));
}
}
输出结果为:
数组长度为:5
下标为2的值为:34
set后下标为2的值为:33
lazySet后下标为2的值为:36
是否设置为新值:false
自增前值为:115
自增后为:116
原子更新字段
包括AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,AtomicReferenceFieldUpdater,是基于反射的原子更新字段的值,相较于原子基本类型有一些限制:
1.字段必须是volatile修饰的,保证线程间共享变量时立即可见
2.字段的可访问描述符相同(private/public/protected)
3.只能是实例对象(不能加static)
4.只能是可修改变量(不能加final)
5.对于AtomicIntegerFieldUpdater/AtomicLongFieldUpdater,只能修改基本类型int/long,对于其包装类Integer/Long不能修改,只能由AtomicReferenceFieldUpdater修改
AtomicIntegerFieldUpdater例子:
public class AtomicIntgerFieldUpdaterDemo {
private static AtomicIntegerFieldUpdater<DataDemo> updater =
AtomicIntegerFieldUpdater.newUpdater(DataDemo.class, "count");
static class DataDemo {
public volatile int count = 100;
}
public static void main(String[] args) {
DataDemo dataDemo = new DataDemo();
// 把count的值由100更新为120
Thread t1 = new Thread(() -> {
if (updater.compareAndSet(dataDemo, 100, 120)) {
System.out.println("update success,count = " + dataDemo.count);
} else {
System.out.println("update false,count = " + dataDemo.count);
}
});
Thread t2 = new Thread(() -> {
if (updater.compareAndSet(dataDemo, 100, 120)) {
System.out.println("update success,count = " + dataDemo.count);
} else {
System.out.println("update false,count = " + dataDemo.count);
}
});
t1.start();
t2.start();
}
}
输出结果:
update success,count = 120
update false,count = 120
AtomicReferenceFieldUpdater例子:
public class AtomicReferenceFieldUpdaterDemo {
public static void main(String[] args) {
// 把name由catA换成catB
AtomicReferenceFieldUpdater updater = AtomicReferenceFieldUpdater
.newUpdater(Cat.class, String.class, "name");
Cat cat = new Cat();
System.out.println(cat.name);
updater.compareAndSet(cat, "catA", "carB");
System.out.println(cat.name);
}
}
class Cat {
public volatile String name = "catA";
}
输出结果
catA
carB