JUC基础

思路:

CAS --> UnSafe类 --> CAS底层思想 --> ABA问题 --> 原子引用更新 --> 如何规避ABA问题

1、volatile关键字

是JVM提供的一种轻量级的同步机制

三个特性:保证可见性、不保证原子性、禁止指令重排

JMM三大特性:可见性、有序性、原子性

单例模式volatile分析

SingletonDemo instance = new SingletonDemo();可以分为三步完成
    1.分配对象内存空间
    2.初始化对象
    3.设置instance指向刚分配的内存地址,测试instance!=null
由于23不存在数据依赖关系,而且重排前后执行结果在单线程中没有变,因此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自带的堆栈跟踪工具) 进程号 命令会打印死锁信息

posted @   微醺的小布  阅读(29)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
点击右上角即可分享
微信分享提示

目录导航