JUC基础
思路:
CAS --> UnSafe类 --> CAS底层思想 --> ABA问题 --> 原子引用更新 --> 如何规避ABA问题
1、volatile关键字
是JVM提供的一种轻量级的同步机制
三个特性:保证可见性、不保证原子性、禁止指令重排
JMM三大特性:可见性、有序性、原子性
单例模式volatile分析
SingletonDemo instance = new SingletonDemo();可以分为三步完成
1.分配对象内存空间
2.初始化对象
3.设置instance指向刚分配的内存地址,测试instance!=null
由于2和3不存在数据依赖关系,而且重排前后执行结果在单线程中没有变,因此132这种顺序是允许的
所以当一个线程访问instance不为null时,instance未必初始化完成,所以有线程安全问题
DCL(双端检锁机制)在高并发情况下还是有可能会线程不安全,需要搭配volatile进行指令重排使用
public class SingletonDemo {
private static volatile SingletonDemo singletonDemo = null;
private SingletonDemo(){
System.out.println("=====SingletonDemo已被初始化=====");
}
public SingletonDemo getInstance(){
//DCL 双端检索机制,在多线程情况下由于指令重排,还是有可能线程不安全,解决方案为使用volatile禁止指令重排
if (null == singletonDemo){
synchronized (SingletonDemo.class){
if (null == singletonDemo){
singletonDemo = new SingletonDemo();
}
}
}
return singletonDemo;
}
}
2、CAS(compareAndSet) 比较并交换
底层原理:自旋锁+Unsafe类(用AtomicInteger举例)
例题:
总结:
CAS缺点:
1、循环时间长,开销大
2、只能保证一个共享变量的原子性
3、引出来ABA问题
原子引用
@Data
class User{
int age;
String name;
}
public class AtomicReferenceDemo {
public static void main(String[] args) {
AtomicReference<User> atomicReference = new AtomicReference();
User z3 = new User();
User li4 = new User();
atomicReference.set(z3);
System.out.println(atomicReference.compareAndSet(z3,li4));
}
}
ABA问题的解决 原子引用+版本号(时间戳),AtomicStampedReference类
public class ABADemo {
static AtomicReference<Integer> atomicReference = new AtomicReference<Integer>(100);
static AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<Integer>(100,1);
public static void main(String[] args) {
System.out.println("=====ABA问题复现=====");
new Thread(() -> {
atomicReference.compareAndSet(100,200);
atomicReference.compareAndSet(200,100);
},"T1").start();
new Thread(() -> {
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
atomicReference.compareAndSet(100,2019);
},"T2").start();
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println("=====第一次执行结果为:"+atomicReference.get());
System.out.println("=====ABA问题解决=====");
new Thread(() -> {
stampedReference.compareAndSet(100,200,1,stampedReference.getStamp()+1);
stampedReference.compareAndSet(200,100,2,stampedReference.getStamp()+1);
},"T3").start();
new Thread(() -> {
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
boolean result = stampedReference.compareAndSet(100,2019,1,stampedReference.getStamp()+1);
},"T4").start();
System.out.println("=====第二次执行结果为:"+atomicReference.get());
}
}
3、集合类不安全问题
复现:ArrayList
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
for (int i = 1;i <= 10; i++){
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
报错:ConcurrentModificationException(并发修改异常)
java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at java.util.AbstractCollection.toString(AbstractCollection.java:461)
at java.lang.String.valueOf(String.java:2994)
at java.io.PrintStream.println(PrintStream.java:821)
at com.lvhx.test.集合类不安全问题.ContainerNotSafeDemo.lambda$main$0(ContainerNotSafeDemo.java:13)
at java.lang.Thread.run(Thread.java:748)
导致原因:并发修改问题,比如生活中签名场景
解决方案
1.new Vector();
2.Collections.synchronizedList(new ArrayList<>());
3.写时复制:CopyOnWriteArrayList(读写分离思想)
//源代码
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
写时复制:当有写操作的时候,先将原来的复制一份,然后向复制的里面进行写操作,然后进行合并
Set
HashSet是线程不安全的,底层是HashMap,set集合添加的值是底层map的key,底层map的value是一个Object常量
解决方案:CopyOnWriteArraySet
private static final Object PRESENT = new Object();
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
map
解决方案:CocurrentHashMap或者用Collections.synd........
4、锁
公平锁和非公平锁
公平锁:是指多个线程按照申请锁的顺序来获取锁,有先来后到。
非公平锁:是指多个线程过去锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程先获取锁,再高并发的情况下,有可能会造成优先级反转或者饥饿现象。
非公平锁比公平锁吞吐量大
可重入锁
ReentrantLock和Synchronized默认都是非安全的可重入锁
可重入锁的最大作用就是避免死锁
class phone implements Runnable {
public synchronized void sendMSM(){
System.out.println(Thread.currentThread().getName()+",sendMSM");
sendEmail();
}
public synchronized void sendEmail(){
System.out.println(Thread.currentThread().getName()+",sendEmail");
}
@Override
public void run() {
get();
}
ReentrantLock lock = new ReentrantLock();
public void get(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName()+",get");
set();
}finally {
lock.unlock();
}
}
public void set(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName()+",set");
}finally {
lock.unlock();
}
}
}
public class RennterLockDemo {
public static void main(String[] args) {
phone phone = new phone();
new Thread(() -> {
phone.sendMSM();
},"T1").start();
new Thread(() -> {
phone.sendMSM();
},"T2").start();
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
Thread t3 = new Thread(phone);
Thread t4 = new Thread(phone);
t3.start();
t4.start();
}
}
自旋锁
是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少上线下文切换,缺点是循环会消耗CPU。
手写一个自旋锁
public class SpinLockDemo {
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void myLock(){
Thread thread = Thread.currentThread();
while (!atomicReference.compareAndSet(null,thread)){ }
System.out.println(Thread.currentThread().getName()+"lock");
}
public void myUnlock(){
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread,null);
System.out.println(Thread.currentThread().getName()+"unlock");
}
public static void main(String[] args) {
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(() -> {
spinLockDemo.myLock();
try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
spinLockDemo.myUnlock();
},"T1").start();
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> {
spinLockDemo.myLock();
spinLockDemo.myUnlock();
},"T2").start();
}
}
独占锁(写锁)、共享锁(读锁)、互斥锁
读锁和写锁,都会发生死锁
读锁:1在修改时,在等2的读完成。2在修改的时候再等1的读完成
写锁:1持有记录1的写锁,想修改记录2。2持有记录一的写锁,想修改记录1
class MyCache{
private volatile Map<String,Object> map = new HashMap<>();
private ReadWriteLock rwLock = new ReentrantReadWriteLock();
public void put(String key,Object value){
try {
rwLock.writeLock().lock();
try { TimeUnit.MICROSECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
map.put(key,value);
System.out.println(Thread.currentThread().getName()+" 正在放入"+value);
} catch (Exception e) {
e.printStackTrace();
} finally {
rwLock.writeLock().unlock();
}
}
public Object get(String key){
Object result = null;
try {
rwLock.readLock().lock();
try { TimeUnit.MICROSECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
result = map.get(key);
System.out.println(Thread.currentThread().getName()+" 正在取出"+result);
} catch (Exception e) {
e.printStackTrace();
} finally {
rwLock.readLock().lock();
}
return result;
}
}
5、CountDownLatch、CyclicBarrier、SemaPhore
SemaPhore(信号灯):抢车位
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);
for (int i = 1;i <= 6; i++){
new Thread(() -> {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"抢到车位");
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName()+"离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
},String.valueOf(i)).start();
}
}
CountDownLatch:倒计时
public static void main(String[] args) throws Exception {
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1;i <= 6; i++){
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"\t 同学下课");
countDownLatch.countDown();
},String.valueOf(i)).start();
}
//当计数器值变为0时,因await阻塞的方法会被唤醒
countDownLatch.await();
System.out.println("班长关门+ \t");
}
CyclicBarrier:正计数(循环栅栏)
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,() -> System.out.println("---------"));
for (int i = 1;i <= 7; i++){
final int temp = i;
new Thread(() -> {
System.out.println(temp);
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
synchronized和ReentrantLock得区别
6、阻塞队列
7、线程池
作用及优点
线程池7大参数
线程池工作原理
线程池的四大拒绝策略
线程池参数配置分析
由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,比如CPU核数*2
8、死锁及分析
定义
原因
系统资源不足
进程推进顺序不合适
资源分配不当
手写一个死锁demo
class DeadLockSource implements Runnable {
private String lockA;
private String lockB;
public DeadLockSource(String lockA, String lockB) {
this.lockA = lockA;
this.lockB = lockB;
}
@Override
public void run() {
synchronized (lockA) {
System.out.println(Thread.currentThread().getName() + "\t 自己持有" + lockA + ",尝试获得" + lockB);
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
synchronized (lockB) {
System.out.println(Thread.currentThread().getName() + "\t 自己持有" + lockB + ",尝试获得" + lockA);
}
}
}
}
public class DeadLockDemo {
public static void main(String[] args) {
String lockA = "A";
String lockB = "B";
new Thread(new DeadLockSource(lockA,lockB),"threadA").start();
new Thread(new DeadLockSource(lockB,lockA),"threadB").start();
}
}
现象分析
jsp命令查到进程号、jstack(JVM自带的堆栈跟踪工具) 进程号 命令会打印死锁信息
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义