并发编程
1.共享模型-内存
共享变量在多线程间的<可见性>问题与多条指令执行时的<有序性>问题
1.1Java内存模型
JMM它定义了主存、工作内存抽象概念,底层对应着CPU寄存器、缓存、硬件内存CPU指令优化等.
JMM体现在:
- 原子性-保证指令不会受到线程上下文切换的影响
- 可见性-保证指令不会受cpu缓存的影响
- 有序性-保证指令不会受cpu指令并行优化的影响
1.2可见性
问题:通过变量控制while程序不能够停下来
@Slf4j
public class KeJianXingThreadD2 {
static boolean flag = true;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (flag) {
}
});
thread.start();
Thread.sleep(3);
log.debug("停止thread");
flag = false;
}
}
分析问题:
- thread从内存读取了flag的值到工作内存中
- thread线程频繁从主内存中读取flag的值,JIT编译器会将flag的值缓存到自己的工作内存中,减少对主内存中flaga的访问,提高效率
- 3秒之后,main线程修改了flag的值,并同步到主内存中,而thread线程一直在读取自己的工作内存的值,一直是旧值,所以线程不会停下来
解决方案:
volatile(关键字)
可以修饰成员变量和静态成员变量,可以避免线程从自己的工作缓存中查找变量,必须到主内存获取它的值,线程操作volatile变量都是直接操作主
public class KeJianXingThreadD2 {
volatile static boolean flag = true;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (flag) {
}
});
thread.start();
Thread.sleep(3);
log.debug("停止thread");
flag = false;
}
}
只能保证可见性 不能保证原子性
1.3有序性
程序执行的顺序按照代码的先后执顺序执行.
编译器和处理器为了提高执行的效率性能,会对执行进行重排序,指令重排序对单线程应用没有影响,但是在多线程环境下可能出现问题.<重排序的代码没有执行完>
解决:volatile关键字解决
1.4volatile原理
- 读屏障
- 写屏障
2.共享模型-无锁
- CAS与volatile
- 原子整数
- 原子引用
- 原子累加器
- Unsafe
2.1CAS与volatile
/**
* @program: springboot-demo-liuan
* @description:
* @author: Mr.Lh
* @create: 2023-03-18 21:29
**/
public class CasController {
public static void main(String[] args) throws InterruptedException {
Acout acout = new Acout(10000);
acout.demo(acout);
}
}
class Acout {
public AtomicInteger balance;
public Acout(int count) {
this.balance = new AtomicInteger(count);
}
public void with(Integer amout) {
while (true) {
int i = balance.get();
int b = i - amout;
if (balance.compareAndSet(i, b)) {
break;
}
}
}
void demo(Acout acout) throws InterruptedException {
for (int i = 0; i < 1000; i++) {
new Thread(()->{
acout.with(10);
}).start();
}
Thread.sleep(1000);
System.out.println(acout.balance.get());
}
}
关键点是compareAndSet,它的简称是CAS,它必须是原子性的操作
CAS特点
CAS和volatile可以实现无锁并发,适用于线程数小,多核CPU的场景下
- CAS是基于乐观锁的思想
- synchronized是基于悲观锁的思想
- CAS体现的是无锁并发,无阻塞并发
CAS缺点
- ABA问题:A值变成了B,然后又从B值变回了A,而使用CAS并不会感知到这个情况
- 自旋时间过长:单次CAS并一定能够成功,CAS配合循环来使用
原子整数
- AtomicInteger
- AtomicBoolean
- AtomicLong
原子引用
- AtomicReference(会有ABA的问题)
- AtomicStampedReference(版本号解决ABA的问题)
3.线程池
1.线程池状态
ThreadPoolExecutor 使用 int 的高 3 位来表示线程池状态,低 29 位表示线程数量
状态名 | 高 3 位 | 接收新任务 | 处理阻塞队列任务 | 说明 |
---|---|---|---|---|
RUNNING | 111 | Y | Y | |
SHUTDOWN | 000 | N | Y | 不会接收新任务,但会处理阻塞队列剩余 任务 |
STOP | 001 | N | N | 会中断正在执行的任务,并抛弃阻塞队列 任务 |
TIDYING | 010 | - | - | 任务全执行完毕,活动线程为 0 即将进入 终结 |
TERMINATED | 011 | - | - | 终结状态 |
2.构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- corePoolSize 核心线程数
- maximumPoolSize 最大线程数
- keepAliveTime 生存时间
- unit 生存时间单位
- workQueue 当核心线程数满时,存放的任务队列
- threadFactory 创建线程的工厂
- handler 拒绝策略
四种策略
- CallerRunsPolicy 让调用者运行任务
- DiscardOldestPolicy 放弃队列中最早的任务,本任务取而代之
- AbortPolicy 让调用者抛出异常 默认策略
- DiscardPolicy 放弃本次任务
工厂创建线程
1.newFixedThreadPool
特点:
- 核心线程数==最大线程数,无需超时时间
- 阻塞队列是无界的,可以任意数量的任务
2.newCachedThreadPool
特点:
- 核心线程数是0,最大线程数无限制,60s回收救急线程,救济线程可以无限的创建
- 队列采用的SynchronousQueue 没有容量,没有线程来取是放不进去的
- 没有上限的一个线程池
3.newSingleThreadExecutor
特点
- 线程数固定为1,会放入无界队列中,任务执行完毕,这唯一的线程也不会被释放
任务调度线程池
//延时执行任务
public class TimeExcetor {
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
scheduledExecutorService.schedule(() -> {
System.out.println("aaaa");
}, 2, TimeUnit.MILLISECONDS);
scheduledExecutorService.schedule(() -> {
System.out.println("bbbbbb");
}, 1, TimeUnit.MILLISECONDS);
}
}
//定时执行任务
@Slf4j
public class TimeExcetor {
public static void main(String[] args) {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
log.debug("start...");
pool.scheduleAtFixedRate(() -> {
log.debug("running...");
}, 1, 1, TimeUnit.SECONDS);
}
}
正确处理执行任务异常
-
主动捕获异常
public static void main(String[] args) { ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2); scheduledExecutorService.execute(() -> { try { int i = 10 / 0; } catch (Exception ex) { ex.printStackTrace(); } System.out.println("aaa"); }); }
-
Future
public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService executorService = Executors.newFixedThreadPool(2); Future<Boolean> submit = executorService.submit(() -> { int i = 10 / 0; return true; }); //如果任务执行成功则获取值,如果任务失败了 则会打印出异常信息 System.out.println(submit.get()); }
4.JUC
4.1 AQS原理
AbstractQueuedSynchronizer,是一个用于实现锁和同步器的基础框架
特点:
- state state 表示同步状态(分独占锁和共享锁) 使用compareAndSetState机制实现加锁
- Node表示队列元素,每个 Node 对象内都保存着当前线程的引用,这些 Node 构成了一个双向队列
4.2ReentrantReadWriteLock
ReentrantReadWriteLock 同样基于 AQS 实现了一套高效的同步机制。它维护着两个锁,一个读取锁和一个写入锁。读取锁允许多个线程同时共享读操作,而写入锁则只允许一个线程进行写入操作。如果有一个线程已经获取了写入锁,那么其他所有线程都无法获得读取或写入锁,它们只能等待写入锁被释放后才能进行读取或写入操作。
4.3Semaphore
是一种在并发编程中经常使用的同步原语。它可以用来管理一组共享的资源,防止共享资源的并发使用。Semaphore包含一个计数器和两个方法:acquire和release
@Slf4j
public class SemaphoreDemo1 {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(2);
for (int i = 0; i < 10; i++) {
new Thread(()->{
try {
semaphore.acquire();
log.info("获取信号量");
Thread.sleep(10002);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}finally {
semaphore.release();
}
}).start();
}
}
}
4.4CountDownLatch
是一个同步工具,它可以让一个线程等待一组操作的完成。CountDownLatch包含一个计数器和两个方法:countDown和await
countDown方法将计数器的值减一,await方法阻塞线程,直到计数器值为零。每次调用countDown方法都会减少计数器的值,当计数器值为零时,await方法会解除所有等待线程的阻塞状态
@Slf4j
public class CountDownLatchDemo1 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(3);
new Thread(()->{
try {
Thread.sleep(1000);
countDownLatch.countDown();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}).start();
new Thread(()->{
try {
Thread.sleep(2222);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
countDownLatch.countDown();
}).start();
new Thread(()->{
try {
Thread.sleep(3333);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
countDownLatch.countDown();
}).start();
//主线程阻塞
countDownLatch.await();
}
}
4.5CyclicBarrier
循环栅栏,用来进行线程协作,等待线程满足某个计数。构造时设置『计数个数』,每个线程执 行到某个需要“同步”的时刻调用 await() 方法进行等待,当等待的线程数满足『计数个数』时,继续执行
public class CyclicBarrierDemo {
public static void main(String[] args) {
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();
}
}