无锁并发
CAS原理
全称是 Compare And Swap 比较并交换
方法执行是原子性的。
unsafe.compareAndSwapInt(this, valueOffset, expect, update)
- valueOffset,value 共享变量最新的值(借助
volatile
才能读取到共享变量的新值)。 - expect,更新之前的值。
- update,要更新的最新值
如果原子变量中的 value 值等于 expect,则使用 update 值更新该值并返回 true,否则返回 false。
CPU保证了CAS的原子性操作
- CPU 处理器速度远远大于在主内存中的,为了解决速度差异,在他们之间架设了多级缓存,如 L1、L2、L3级别的缓存,这些缓存离CPU越近就越快,将频繁操作的数据缓存到这里,加快访问速度 ,如下图所示:
多核 CPU 处理器都维护了一块字节的内存,当多线程并发读写时,就会出现缓存数据不一致的情况。
总线锁定
- 当一个处理器要操作共享变量时,在
BUS 总线
上发出一个 Lock信号,其他处理就无法操作这个共享变量了。可能会导致大量阻塞,从而增加系统的性能开销。
缓存锁定
- 后来的处理器都提供了缓存锁定机制,也就说当某个处理器对缓存中的共享变量进行了操作,其他处理器会有个嗅探机制,将其他处理器的该共享变量的缓存失效,待其他线程读取时会重新从主内存中读取最新的数据,基于MESI 缓存一致性协议来实现的。
无锁解决线程安全问题
使用原子整数
interface Account {
Integer getBalance();
void withdraw(Integer amount);
/**
* 方法内会启动 1000 个线程,每个线程做 -10 元 的操作 * 如果初始余额为 10000 那么正确的结果应当是 0
*/
static void demo(Account account) {
List<Thread> ts = new ArrayList<>();
long start = System.nanoTime();
for (int i = 0; i < 1000; i++) {
ts.add(new Thread(() -> {
account.withdraw(10);
}));
}
ts.forEach(Thread::start);
ts.forEach(t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long end = System.nanoTime();
System.out.println(account.getBalance() + " cost: " + (end - start) / 1000_000 + " ms");
}
}
//线程不安全的做法
class AccountUnsafe implements Account {
private Integer balance;
public AccountUnsafe(Integer balance) {
this.balance = balance;
}
@Override
public Integer getBalance() {
return this.balance;
}
@Override
public synchronized void withdraw(Integer amount) {
balance -= amount;
}
public static void main(String[] args) {
Account.demo(new AccountUnsafe(10000));
Account.demo(new AccountCas(10000));
}
}
//线程安全的做法
class AccountCas implements Account {
//使用原子整数
private AtomicInteger balance;
public AccountCas(int balance) {
this.balance = new AtomicInteger(balance);
}
@Override
public Integer getBalance() {
//得到原子整数的值
return balance.get();
}
@Override
public void withdraw(Integer amount) {
while(true) {
//获得修改前的值
int prev = balance.get();
//获得修改后的值
int next = prev-amount;
//比较并设值
if(balance.compareAndSet(prev, next)) {
break;
}
}
也可以简化成这一行代码
balance.getAndAdd(-1 * amount);
}
}
效率问题
为什么无锁比加锁快?
- CAS 是基于乐观锁的思想,不怕别的线程来修改共享变量,就算改了也没关系,我吃亏点再重试。
- synchronized是基于悲观锁的思想:得防着其它线程来修改共享变量,我上了锁你们都别想改,我改完了解开锁,你们才有机会。
CAS 体现的是**无锁并发、无阻塞并发**。线程不会陷入阻塞,这是效率提升的因素之一。
- 但是重复过多也会导致效率下降
- 原子变量可以保证线程安全
ABA问题
- 感知不到共享变量被动过了。
public class Demo3 {
static AtomicReference<String> str = new AtomicReference<>("A");
public static void main(String[] args) {
new Thread(() -> {
String pre = str.get();
System.out.println("change");
try {
other();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//把str中的A改为C
System.out.println("change A->C " + str.compareAndSet(pre, "C"));
}).start();
}
static void other() throws InterruptedException {
new Thread(()-> {
System.out.println("change A->B " + str.compareAndSet("A", "B"));
}).start();
Thread.sleep(500);
new Thread(()-> {
System.out.println("change B->A " + str.compareAndSet("B", "A"));
}).start();
}
}
ABA处理
AtomicStampedReference
public class Demo3 {
//指定版本号
static AtomicStampedReference<String> str = new AtomicStampedReference<>("A", 0);
public static void main(String[] args) {
new Thread(() -> {
String pre = str.getReference();
//获得版本号
int stamp = str.getStamp();
System.out.println("change");
try {
other();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//把str中的A改为C,并比对版本号,如果版本号相同,就执行替换,并让版本号+1
System.out.println("change A->C stamp " + stamp + str.compareAndSet(pre, "C", stamp, stamp+1));
}).start();
}
static void other() throws InterruptedException {
new Thread(()-> {
int stamp = str.getStamp();
System.out.println("change A->B stamp " + stamp + str.compareAndSet("A", "B", stamp, stamp+1));
}).start();
Thread.sleep(500);
new Thread(()-> {
int stamp = str.getStamp();
System.out.println("change B->A stamp " + stamp + str.compareAndSet("B", "A", stamp, stamp+1));
}).start();
}
}
AtomicMarkableReference
查看是否更改过
public class Demo4 {
//指定版本号
static AtomicMarkableReference<String> str = new AtomicMarkableReference<>("A", true);
public static void main(String[] args) {
new Thread(() -> {
String pre = str.getReference();
System.out.println("change");
try {
other();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//把str中的A改为C,并比对版本号,如果版本号相同,就执行替换,并让版本号+1
System.out.println("change A->C mark " + str.compareAndSet(pre, "C", true, false));
}).start();
}
static void other() throws InterruptedException {
new Thread(() -> {
System.out.println("change A->A mark " + str.compareAndSet("A", "A", true, false));
}).start();
}
}
两者的区别
- AtomicStampedReference 需要我们传入
整型变量
作为版本号,来判定是否被更改过 - AtomicMarkableReference需要我们传入
布尔变量
作为标记,来判断是否被更改过
原子操作类
原子整数
J.U.C 并发包提供了
AtomicBoolean
AtomicInteger
AtomicLong
AtomicInteger a = new AtomicInteger(10);
//先获取再++,类似++i
a.incrementAndGet();
//先++再获取,类似i++
a.getAndIncrement();
//先增加5再获取
a.addAndGet(5);
//先获取再增加5
a.getAndAdd(5);
//进行进行运算,再获得值
a.updateAndGet(x -> x * 5);
//进行获得值,再进行运算
a.getAndUpdate(x -> x * 5);
a.get();
原子引用
public interface DecimalAccount {
BigDecimal getBalance();
void withdraw(BigDecimal amount);
/**
* 方法内会启动 1000 个线程,每个线程做 -10 元 的操作
* 如果初始余额为 10000 那么正确的结果应当是 0
*/
static void demo(DecimalAccountImpl account) {
List<Thread> ts = new ArrayList<>();
long start = System.nanoTime();
for (int i = 0; i < 1000; i++) {
ts.add(new Thread(() -> {
account.withdraw(BigDecimal.TEN);
}));
}
ts.forEach(Thread::start);
ts.forEach(t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long end = System.nanoTime();
System.out.println(account.getBalance() + " cost: " + (end - start) / 1000_000 + " ms");
}
}
class DecimalAccountImpl implements DecimalAccount {
//原子引用,泛型类型为小数类型
AtomicReference<BigDecimal> balance;
public DecimalAccountImpl(BigDecimal balance) {
this.balance = new AtomicReference<BigDecimal>(balance);
}
@Override
public BigDecimal getBalance() {
return balance.get();
}
@Override
public void withdraw(BigDecimal amount) {
while(true) {
BigDecimal pre = balance.get();
BigDecimal next = pre.subtract(amount);
if(balance.compareAndSet(pre, next)) {
break;
}
}
}
public static void main(String[] args) {
DecimalAccount.demo(new DecimalAccountImpl(new BigDecimal("10000")));
}
}
原子数组
- AtomicIntegerArray
- AtomicLongArray
- AtomicReferenceArray
public static void main(String[] args) {
demo(
()-> new AtomicIntegerArray(10),
(array) -> array.length(),
(array, index) -> array.getAndIncrement(index),
array -> System.out.println(array)
);
}
private static <T> void demo(
Supplier<T> arraySupplier,
Function<T, Integer> lengthFun,
BiConsumer<T, Integer> putConsumer,
Consumer<T> printConsumer ) {
List<Thread> ts = new ArrayList<>();
T array = arraySupplier.get();
int length = lengthFun.apply(array);
for (int i = 0; i < length; i++) {
// 每个线程对数组作 10000 次操作
ts.add(new Thread(() -> {
for (int j = 0; j < 10000; j++) {
putConsumer.accept(array, j%length);
}
}));
}
ts.forEach(t -> t.start()); // 启动所有线程
ts.forEach(t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}); // 等所有线程结束
printConsumer.accept(array);
}
字段更新器
- AtomicReferenceFieldUpdater // 域 字段
- AtomicIntegerFieldUpdater
- AtomicLongFieldUpdate
原子更新器用于帮助我们改变某个对象中的某个属性
public class Demo1 {
public static void main(String[] args) {
Student student = new Student();
// 获得原子更新器
// 泛型
// 参数1 持有属性的类 参数2 被更新的属性的类
// newUpdater中的参数:第三个为属性的名称
AtomicReferenceFieldUpdater<Student, String> updater = AtomicReferenceFieldUpdater.newUpdater(Student.class, String.class, "name");
// 修改
updater.compareAndSet(student, null, "Nyima");
System.out.println(student);
}
}
class Student {
volatile String name;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
}
原子累加器
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
demo(() -> new LongAdder(), adder -> adder.increment());
}
}
private static <T> void demo(Supplier<T> adderSupplier, Consumer<T> action) {
T adder = adderSupplier.get();
long start = System.nanoTime();
List<Thread> ts = new ArrayList<>();
// 4 个线程,每人累加 50 万
for (int i = 0; i < 40; i++) {
ts.add(new Thread(() -> {
for (int j = 0; j < 500000; j++) {
action.accept(adder);
}
}));
}
ts.forEach(t -> t.start());
ts.forEach(t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long end = System.nanoTime();
System.out.println(adder + " cost:" + (end - start)/1000_000);
}
Unsafe
- Java和C++语言的一个重要区别就是Java中我们无法直接操作一块内存区域,不能像C++中那样可以自己申请内存和释放内存。Java中的Unsafe类为我们提供了类似C++手动管理内存的能力。
- 是个非常底层的方法,只能反射调用。
无状态
在 web 阶段学习时,设计 Servlet 时为了保证其线程安全,都会有这样的建议,不要为 Servlet 设置成员变量,这种没有任何成员变量的类是线程安全的
- 因为成员变量保存的数据也可以称为状态信息,因此没有成员变量就称之为【无状态】
数据库连接池
- 数据库连接池的解决方案是在应用程序启动时建立足够的数据库连接,并将这些连接组成一个连接池(简单说:
在一个“池”里放了好多半成品的数据库联接对象
),由应用程序动态地对池中的连接进行申请、使用和释放。
对于多于连接池中连接数的并发请求,应该在请求队列中排队等待。并且应用程序可以根据池中连接的使用率,动态增加或减少池中的连接数
public class Pool {
//连接池大小
private final int poolSize;
//连接对象数组
private Connection[] connection;
//连接状态
private AtomicIntegerArray states;
public Pool(int poolSize) {
this.poolSize = poolSize;
this.connection = new Connection[poolSize];
this.states = new AtomicIntegerArray(new int[poolSize]);
for (int i = 0; i < poolSize; i++) {
connection[i] = new MockConnection("连接" + (i+1));
}
}
//借连接
public Connection borrow(){
while (true){
for (int i = 0; i < poolSize; i++) {
// 获取空闲连接
if (states.get(i) == 0) {
if (states.compareAndSet(i, 0, 1)) {
log.debug("borrow{}", connection[i]);
return connection[i];
}
}
}
//如果没有空闲连接,当前线程进入等待
synchronized (this){
try {
log.debug("wait...");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//归还连接
public void free(Connection conn){
for (int i = 0; i < poolSize; i++) {
if (connection[i] == conn) {
states.set(i, 0);
synchronized(this){
log.debug("free{}", conn);
this.notifyAll();
}
break;
}
}
}
}
class Test3 {
public static void main(String[] args) {
Pool pool = new Pool(2);
for (int i = 0; i < 5 ; i++) {
new Thread(()->{
Connection borrow = pool.borrow();
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
pool.free(borrow);
}).start();
}
}
}
线程池
线程池的好处:
- 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度:当任务到达时,可以不需要等待线程创建就能立即执行。
- 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,监控和调优。
自定义线程池
mV0L3dlaXhpbl80NTMyMTY4MQ==,size_16,color_FFFFFF,t_70)
代码:
package com.zzs.demo.n5;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashSet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j
public class 线程池的实现 {
public static void main(String[] args) {
ThreadPool pool = new ThreadPool(1, 1000, TimeUnit.MILLISECONDS, 1
, ((queue, task) -> {
/**
* 自定义拒绝策略
*/
// 1. 死等
// queue.put(task);
//
// 2) 带超时等待
// queue.put(task, 1500, TimeUnit.MILLISECONDS);
// 3) 让调用者放弃任务执行
// log.debug("放弃{}", task);
// 4) 让调用者抛出异常
// throw new RuntimeException("任务执行失败 " + task);
// 5) 让调用者自己执行任务
// task.run();
}));
for (int i = 0; i < 3; i++) {
int j = i;
pool.execute(()->{
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("{}", j);
});
}
}
}
@FunctionalInterface //拒绝策略
interface RejectPolicy<T> {
void reject(BlockingQueue<T> queue, T task);
}
@Slf4j
class ThreadPool {
//线程队列
private BlockingQueue<Runnable> taskQueue;
//线程集合
private HashSet<Worker> workers = new HashSet<>();
//线程数
private int coreSize;
//获取任务的超时时间
private long timeout;
private RejectPolicy<Runnable> reject;
private TimeUnit timeUnit;
public ThreadPool(int coreSize, long timeout,
TimeUnit timeUnit, int queueCapcity,
RejectPolicy<Runnable> rejectPolicy) {
this.coreSize = coreSize;
this.timeout = timeout;
this.timeUnit = timeUnit;
this.taskQueue = new BlockingQueue<>(queueCapcity);
this.reject = rejectPolicy;
}
//执行任务
public void execute(Runnable task){
// 当任务未超过coreSize时,直接交给woker对象执行。
// 如果任务数超过coresize时,加入任务队列暂存
synchronized (workers){
if (workers.size() < coreSize) {
Worker worker = new Worker(task);
log.debug("新增worker{}, {}", worker, task);
workers.add(worker); //只是做了个记录
worker.start();
} else {
// taskQueue.put(task);
// 死等
// 带超时等待
// 放弃任务执行
// 抛出移除
// 让调用者自己执行任务
taskQueue.tryPut(reject, task);
}
}
}
class Worker extends Thread{
private Runnable task;
public Worker(Runnable task) {
this.task = task;
}
@Override
public void run() { //启动了一个线程!!!
// 当task不为空执行任务
while (task != null || ((task = taskQueue.take(timeout, timeUnit)) != null)){ //再判断时可能条件已经变了
try {
log.debug("正在执行...{}", task);
task.run();
System.out.println("线程id:" + Thread.currentThread().getId());
} catch (Exception e) {
e.printStackTrace();
} finally {
task = null;
}
}
synchronized (workers){
log.debug("worker被移除{}", this);
workers.remove(this);
}
}
}
}
@Slf4j
class BlockingQueue<T> {
// 1. 任务队列
private Deque<T> queue = new ArrayDeque<>();
// 2. 锁
private ReentrantLock lock = new ReentrantLock();
// 3. 生产者条件变量
private Condition fullWaitSet = lock.newCondition();
// 4. 消费者条件变量
private Condition emptyWaitSet = lock.newCondition();
// 5.容量
private int capcity;
public BlockingQueue(int capcity) {
this.capcity = capcity;
}
// 带超时的阻塞获取
public T take(long timeout, TimeUnit unit) {
lock.lock();
try {
long nanos = unit.toNanos(timeout);
while (queue.isEmpty()) {
try {
if (nanos <= 0) {
return null;
}
// 返回的是剩余的时间
nanos = emptyWaitSet.awaitNanos(nanos);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T t = queue.removeFirst();
// 已经不是满的了唤醒生产者
fullWaitSet.signal();
return t;
} finally {
lock.unlock();
}
}
public T take(){
lock.lock();
try {
while (queue.isEmpty()) {
try {
emptyWaitSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T t = queue.removeFirst();
// 已经不是满的了唤醒生产者
fullWaitSet.signal();
return t;
} finally {
lock.unlock();
}
}
//阻塞添加
public void put(T element) {
lock.lock();
try {
while (queue.size() == capcity) {
try {
log.debug("等待加入任务队列{}...", element);
fullWaitSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("加入任务队列{}", element);
queue.addLast(element);
// 队列不空了唤醒消费者
emptyWaitSet.signal();
} finally {
lock.unlock();
}
}
//带超时时间阻塞添加
public boolean put(T element, long timeout, TimeUnit unit) {
lock.lock();
try {
long nanos = unit.toNanos(timeout);
while (queue.size() == capcity) {
try {
log.debug("等待加入任务队列{}..", element);
if (nanos <= 0){
return false;
}
nanos = fullWaitSet.awaitNanos(nanos);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("加入任务队列{}", element);
queue.addLast(element);
// 队列不空了唤醒消费者
emptyWaitSet.signal();
return true;
} finally {
lock.unlock();
}
}
//获取大小
public int size() {
lock.lock();
try {
return queue.size();
} finally {
lock.unlock();
}
}
public void tryPut(RejectPolicy<T> rejectPolicy, T task) {
lock.lock();
try {
// 判断队列是否满
if (queue.size() == capcity){
//由用户决定
rejectPolicy.reject(this, task);
} else { // 队列不满
log.debug("加入任务队列{}", task);
queue.addLast(task);
emptyWaitSet.signal();
}
} finally {
lock.unlock();
}
}
}
简单概述:通过核心线程数控制能并发的线程,一个线程里运行多个线程的方法
。
线程池状态
线程池里的属性
// 工作线程,内部封装了Thread
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable {
...
}
// 阻塞队列,用于存放来不及被核心线程执行的任务
private final BlockingQueue<Runnable> workQueue;
// 锁
private final ReentrantLock mainLock = new ReentrantLock();
// 用于存放核心线程的容器,只有当持有锁时才能够获取其中的元素(核心线程)
private final HashSet<Worker> workers = new HashSet<Worker>();
构造方法与其参数
ThreadPoolExecutor最全面的构造方法
- 也是构造自定义线程池的方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
参数解释
- corePoolSize:核心线程数
- maximumPoolSize:最大线程数
- maximumPoolSize - corePoolSize = 救急线程数
- keepAliveTime:救急线程空闲时的最大生存时间
- unit:时间单位
- workQueue:阻塞队列(存放任务)
有界阻塞队列 ArrayBlockingQueue(有限容纳)
无界阻塞队列 LinkedBlockingQueue(无限容纳 不设置大小时就是Integer.MAX_VALUE)
最多只有一个同步元素的 SynchronousQueue
优先队列 PriorityBlockingQueue - threadFactory:线程工厂(给线程取名字)
- handler:拒绝策略
线程池队列
- ArrayBlockingQueue
是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。 - LinkedBlockingQueue
一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于 - ArrayBlockingQueue。
静态工厂方法Executors.newFixedThreadPool()使用了这个队列 - SynchronousQueue
一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。 - PriorityBlockingQueue
一个具有优先级的无限阻塞队列。
工作方式
当一个任务传给线程池以后,可能有以下几种可能
- 将任务分配给一个核心线程来执行
- 核心线程满了,其他线程放到阻塞队列workQueue中等待被执行
- 阻塞队列满了,使用救急线程来执行任务
- 救急线程用完以后,超过
生存时间(keepAliveTime)
后会被释放 - 任务总数大于了 最大线程数(
maximumPoolSize
)与阻塞队列容量的最大值(workQueue.capacity
),使用拒接策略
拒绝策略
如果线程到达 maximumPoolSize
仍然有新任务这时会执行拒绝策略。拒绝策略 jdk 提供了 4 种实现
- AbortPolicy:让调用者抛出
RejectedExecutionException
异常,这是默认策略 - CallerRunsPolicy:让调用者运行任务
- DiscardPolicy:放弃本次任务
- DiscardOldestPolicy:放弃队列中最早的任务,本任务取而代之
newFixedThreadPool
固定大小的线程池
public class TestFixedThreadPool {
public static void main(String[] args) {
// 自定义线程工厂
ThreadFactory factory = new ThreadFactory() {
AtomicInteger atomicInteger = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "myThread_" + atomicInteger.getAndIncrement());
}
};
// 创建核心线程数量为2的线程池
// 通过 ThreadFactory可以给线程添加名字
ExecutorService executorService = Executors.newFixedThreadPool(2, factory);
// 任务
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
System.out.println("this is fixedThreadPool");
}
};
executorService.execute(runnable);
}
}
可以传入两个参数
- 核心线程数:nThreads
- 线程工厂:threadFactory
内部调用的构造方法
ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
特点:
- 核心线程 == 最大线程数,没有救急线程不用等待
- 阻塞队列是无界的可以放任意数量的任务
适合任务量已知,相对耗时的任务
newCachedThreadPool
缓存线程池
ExecutorService executorService = Executors.newCachedThreadPool();
内部构造方法
ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
特点:
- 没有核心线程,最大线程数为Integer.MAX_VALUE,所有创建的线程都是救急线程,空闲时生存时间为60秒
- 阻塞队列使用的是
SynchronousQueue
SynchronousQueue是一种特殊的队列
没有容量
,没有线程来取是放不进去的
只有当线程取任务时,才会将任务放入该阻塞队列中
适合任务时间较短,比较密集的。
newSingleThread
单线程池
ExecutorService service = Executors.newSingleThreadExecutor();
内部构造方法
new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
- 内部采用装饰器模式让外部无法修改
自己创建的线在任务出错会终止没有任何补救措施,而单线程线程池会新建个线程来维持线程池的运行。
- 适合运用在即使出错也要继续运行的任务
线程池常见方法
停止
shutdown()
/**
* 将线程池的状态改为 SHUTDOWN
* 不再接受新任务,但是会将阻塞队列中的任务执行完
*/
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
// 修改线程池状态为 SHUTDOWN
advanceRunState(SHUTDOWN);
// 中断空闲线程(没有执行任务的线程)
// Idle:空闲的
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
// 尝试终结,不一定成功
//
tryTerminate();
}
shutdownNow()
/**
* 将线程池的状态改为 STOP
* 不再接受新任务,也不会在执行阻塞队列中的任务
* 会将阻塞队列中未执行的任务返回给调用者供条件判断
*/
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
// 修改状态为STOP,不执行任何任务
advanceRunState(STOP);
// 中断所有线程
interruptWorkers();
// 将未执行的任务从队列中移除,然后返回给调用者
tasks = drainQueue();
} finally {
mainLock.unlock();
}
// 尝试终结,一定会成功,因为阻塞队列为空了
tryTerminate();
return tasks;
}
AQS 原理
概述
全称是 AbstractQueuedSynchronizer(抽象队列同步器)
,是阻塞式锁和相关的同步器工具的框架。
它维护了一个volatile int state(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)
- isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
- tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
- tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
- tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
- tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。
执行流程:
- 调用自定义同步器的
tryAcquire()
尝试直接去获取资源,如果成功则直接返回; - 没成功,则
addWaiter()
将该线程加入等待队列的尾部,并标记为独占模式,先尝试CAS
快速入队,失败(其他线程修改尾结点失败)就完整的入队通过不断自旋重试CAS入队
,追求性能; acquireQueued()
自旋的方式判断当前线程是否是头节点(头节点才有资格获取锁),获取失败线程会被LockSupport.park(this);
挂起,避免自旋浪费cpu性能,到一定时间会被唤醒。- 当线程释放锁(
release()
方法先判断tryRelease()方法
是否成功,成功底层再调用LockSupport.unPark(this);
),就会唤醒等待的线程继续自旋获取锁。
AQS核心思想
- 如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
AQS使用一个voliate
int成员变量来表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。AQS使用CAS对该同步状态进行原子操作实现对其值的修改。 - AQS定义了两种资源获取方式:
独占
(只有一个线程能访问执行,又根据是否按队列的顺序分为公平锁和非公平锁,如ReentrantLock)和共享
(多个线程可同时访问执行,如Semaphore/CountDownLatch,Semaphore、CountDownLatCh、CyclicBarrier )。ReentrantReadWriteLock 可以看成是组合式,允许多个线程同时对某一资源进行读。 - AQS底层使用了
模板方法模式
, 自定义同步器在实现时只需要实现共享资源 state
的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在上层已经帮我们实现好了
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
实现不可同步锁的实例
package com.zzs.demo.n6;
import lombok.extern.slf4j.Slf4j;
import sun.dc.pr.PRError;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
@Slf4j
public class AQSTest01 {
public static void main(String[] args) {
MyLock lock = new MyLock();
new Thread(() -> {
lock.lock();
log.debug("running");
//不可重入锁
lock.lock();
try {
log.debug("加锁成功...");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
log.debug("unlocking...");
lock.unlock();
}
}, "t1").start();
// new Thread(() -> {
// lock.lock();
// try {
// log.debug("加锁成功...");
// } finally {
// log.debug("unlocking...");
// lock.unlock();
// }
// }, "t2").start();
}
}
// 自定义锁
class MyLock implements Lock {
// 自定义同步器
class MySync extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire(int arg) {
// 修改状态
if (compareAndSetState(0, 1)) {
// 加上了锁, 并设置owner为当前线程
setExclusiveOwnerThread(Thread.currentThread());
return true;
} else {
//锁重入实现
判断当前线程与owner的一样就返回true
}
return false;
}
@Override
protected boolean tryRelease(int arg) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
}
public Condition newCondition() {
return new ConditionObject();
}
}
private MySync sync = new MySync();
@Override
// 尝试,不成功,进入等待队列
public void lock() {
sync.acquire(1);
}
@Override
// 尝试,不成功,进入等待队列,可打断
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
// 尝试一次,不成功返回,不进入队列
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override
// 尝试,不成功,进入等待队列,有时限
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(time));
}
@Override
// 释放锁
public void unlock() {
sync.release(1);
}
@Override
// 生成条件变量
public Condition newCondition() {
return sync.newCondition();
}
}
同步工具类
倒计时锁CountDownLatch
底层实现aqs
实例:
public class 王者荣耀加载 {
public static void main(String[] args) throws InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(10);
CountDownLatch latch = new CountDownLatch(10);
Random random = new Random();
String[] str = new String[10];
for (int i = 0; i < 10; i++) {
int k = i;
pool.submit(()->{
for (int j = 0; j <= 100; j++) {
try {
Thread.sleep(random.nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
str[k] = j + "%";
System.out.print("\r" + Arrays.toString(str));
}
//执行完-1
latch.countDown();
});
}
latch.await(); //为0唤醒
System.out.println("\n" + "游戏开始");
pool.shutdown();
}
}
信号量
主要可以控制线程,控制并发同步问题
// 1. 创建 semaphore 对象
Semaphore semaphore = new Semaphore(3);
// 2. 10个线程同时运行
for (int i = 0; i < 10; i++) {
new Thread(() -> {
// 3. 获取许可
try {
semaphore.acquire(); -1
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
log.debug("running...");
sleep(1);
log.debug("end...");
} finally {
// 4. 释放许可
semaphore.release(); +1
}
}).start();
}
}
CyclicBarrier(循环栅栏)
设置线程个数,到达某个次数才继续执行
CyclicBarrier cb = new CyclicBarrier(2); // 个数为2时才会继续执行
new Thread(() -> {
System.out.println("线程1开始.." + new Date());
try {
cb.await(); // 当个数不足时,等待
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("线程1继续向下运行..." + new Date());
}).start();
new Thread(() -> {
System.out.println("线程2开始.." + new Date());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
try {
cb.await(); // 2 秒后,线程个数够2,继续运行
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("线程2继续向下运行..." + new Date());
}).start();
JDK 7 HashMap 并发死链
test
package com.zzs.demo;
import java.util.HashMap;
public class 死链 {
public static void main(String[] args) {
// 测试 java 7 中哪些数字的 hash 结果相等
System.out.println("长度为16时,桶下标为1的key");
for (int i = 0; i < 64; i++) {
if (hash(i) % 16 == 1) {
System.out.println(i);
}
}
System.out.println("长度为32时,桶下标为1的key");
for (int i = 0; i < 64; i++) {
if (hash(i) % 32 == 1) {
System.out.println(i);
}
}
// 1, 35, 16, 50 当大小为16时,它们在一个桶内
final HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
// 放 12 个元素
map.put(2, null);
map.put(3, null);
map.put(4, null);
map.put(5, null);
map.put(6, null);
map.put(7, null);
map.put(8, null);
map.put(9, null);
map.put(10, null);
map.put(16, null);
map.put(35, null);
map.put(1, null);
System.out.println("扩容前大小[main]:" + map.size());
new Thread() {
@Override
public void run() {
// 放第 13 个元素, 发生扩容
map.put(50, null);
System.out.println("扩容后大小[Thread-0]:" + map.size());
}
}.start();
new Thread() {
@Override
public void run() {
// 放第 13 个元素, 发生扩容
map.put(50, null);
System.out.println("扩容后大小[Thread-1]:" + map.size());
}
}.start();
}
final static int hash(Object k) {
int h = 0;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
}
源码590行加断点
int newCapacity = newTable.length;
断点条件
newTable.length==32 &&
(
Thread.currentThread().getName().equals("Thread-0")||
Thread.currentThread().getName().equals("Thread-1")
)
JDK 8 虽然将扩容算法做了调整,不再将元素加入链表头(而是保持与扩容前一样的顺序),但仍不意味着能够在多线程环境下能够安全扩容,还会出现其它问题(如扩容丢数据)