Java面试总结---JUC

volatile

volatile是Java虚拟机提供的轻量级的同步机制。它保证可见性,不保证原子性,禁止指令重排序。
如何保证原子性:用AtomicInteger代替int,不用sync

JMM-java memory model

JMM是一种抽象的概念,并不真实存在。它描述的是一组规范或规则,通过这个规范定义了程序中各个变量的访问方式。

JMM关于同步的规范:

  1. 线程解锁之前,必须把共享变量的值刷新回主内存。
  2. 线程加锁前,必须读取主内存的最新值到自己的工作内存
  3. 加锁解锁是同一把锁

由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(栈空间),工作内存是每个线程的私有数据区域,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但是线程对变量 的操作必须在工作内存中进行,首先将变量从主内存拷贝到自己的工作内存空间,然后对变量进行操作,操作完成再将变量写回主内存。不能直接操控主内存中的变量,各个线程的工作内存保存着主内存的变量副本拷贝,因此不同的线程之间无法直接互相访问,访问都得通过主内存进行。

JMM三大特性:

  1. 可见性—一个线程修改了变量,其他线程必须马上知道
  2. 原子性—完整性,要么全部执行,要么全部不执行
  3. 有序性—只在多线程情况考虑—加内存屏障保证指令有序

哪里使用到了volatile:单例,JUC
DCL单例模式下会产生指令重排问题,所以要加volatile防止指令重排

CAS

compare and set
CAS的底层原理,对unsafe的理解?
CAS是一条CPU并发原语,它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值。

CAS并发原语体现在JAVA语言中就是sun.misc.unsafe类中的各个方法。调用CAS方法,JVM会帮我们实现出CAS的汇编指令。

public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
}
public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);//获得初始值
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
}
   
compareAndSwapInt是通过Atomic::cmpxchg实现。 

CAS缺点:

  1. 上述代码的getAndAddInt有个do–while循环;如果CAS失败,会一直进行循环尝试,如果CAS长时间一直不成功,可能会给CPU带来很大的开销【毕竟它没加锁】。
  2. 只能保证一个共享变量的原子操作
  3. 会出现ABA问题

原子类AtomicInteger的ABA问题,原子更新引用?

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

用带时间戳的AtomicStampedReference代替没有时间戳的AtomicReference

集合不安全

并发修改异常问题:ArrayList
Vector jdk1.0 就出来了,而ArrayList jdk1.2才出来。
Vector是用synchronized加锁的,而ArrayList没有加锁-----加锁效率太低

ArrayList线程不安全:
解决方案
* 1.用Vector,内部加了锁
* 2.Collections.synchronizedList(new ArrayList<>());
* 3.new CopyOnWriteArrayList<>();

利用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();
        }
}

各种锁–可重入锁(递归锁)、乐观锁、自旋锁、独占锁/共享锁、读写锁

公平锁—想要获得锁,就要在队列里排队等获取锁。
非公平锁—新来的线程可以直接插队获得锁,获取失败就去排队。
可重入锁指的是同一线程外层函数获得锁之后,内层递归函数仍然额能获取该锁的代码;同一线程在外层获得锁后,进入内层方法会自动获得锁
自旋锁—尝试获取锁的线程不会立即阻塞,而是采用循环的方式去上市获取锁,减少线程的上下文切换的消耗,缺点是循环消耗CPU。

public final int getAndAddInt(Objet var1, long var2, int var4){
	int var5;
	do{
		var5 = this.getIntVolatile(var1,var2);
	}while(this.compareAndSwapInt(var1,var2,var5,var5+var4));
	return var5;
}

自旋锁实现

/**
 * 自旋锁实现
 */
public class SpinLockDemo {
    AtomicReference<Thread> atomicReference = new AtomicReference<>();//null

    public void myLock(){
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName()+"    come in...");
        while (!atomicReference.compareAndSet(null,thread)){
            System.out.println("我他妈就不给你放锁,你能咋地");
        }
    }
    public void myUnlock(){
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName()+"    invoke unlock");
        atomicReference.compareAndSet(thread,null);
    }

    public static void main(String[] args) {
        SpinLockDemo spinLockDemo = new SpinLockDemo();

        new Thread(()->{
                spinLockDemo.myLock();
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    spinLockDemo.myUnlock();
                }

        },"t1").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            spinLockDemo.myLock();
            spinLockDemo.myUnlock();
        },"t2").start();
    }
}

独占锁:该锁一次只能被一个线程持有;如ReentrantLock和synchronized
共享锁:该锁可以被多个线程持有。如ReentrantLock对读操作是共享锁,写锁是独占锁

读写锁代码

class MyCache{
    private volatile Map<String,Object> map = new HashMap<>();
    private ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock();

    public void put(String key,Object value){
        rwlock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+" 正在写入: "+key);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            map.put(key,value);
            System.out.println(Thread.currentThread().getName()+" 写入完成。。");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            rwlock.writeLock().unlock();
        }
    }
    public void get(String key){
        rwlock.readLock().lock();
        try {
            System.out.printf(Thread.currentThread().getName()+" 正在读取:");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Object result = map.get(key);
            System.out.println(Thread.currentThread().getName()+" 读取完成。。。。");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            rwlock.readLock().unlock();
        }
    }
}
public class ReadWriteLock {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();
        for (int i=0;i<5;i++) {
            final int ii = i;
            new Thread(()->{
                myCache.put(ii+"",ii+"");
            },String.valueOf(i)).start();
        }
        for (int i=0;i<5;i++) {
            final int ii = i;
            new Thread(()->{
                myCache.get(ii+"");
            },String.valueOf(i)).start();
        }
    }
}

阻塞队列

分类:

  1. ArrayBkockingQueue:数组结构构成的有界阻塞队列
  2. LinkedBlockingQueue:链表结构组成的有界阻塞队列
  3. PriorityBlockingQueue:支持优先级排序的无界阻塞队列
  4. DelayQueue:优先级队列实现的延迟无界阻塞队列
  5. SynchronousQueue:不存储元素的阻塞队列,也就是单个元素的队列
  6. LinkedTransferQueue:链表结构构成的无界阻塞队列
  7. LinkedBlockigDeque:链表结构组成的双向阻塞队列

ArrayBkockingQueue:
在这里插入图片描述
在这里插入图片描述

synchronized和lock的区别

  1. 构成
    synchronized是关键字,属于JVM层面
    构成:monitorenter(底层是通过monitor对象完成,wait和notify等方法也是依赖于monitor对象)和monitorexit
    lock是具体的类 java.util.concurrent.locks.lock 是api层面的锁。

  2. 使用方法
    synchronized不需要用户手动去释放锁,执行完系统会自动让线程释放。
    Reentrantlock需要用户手动去释放锁,不释放可能就死锁了。需要lock()和unlock()方法配合使用

  3. 等待是否中断
    synchronized不可中断,除非抛出异常或正常结束
    Reentrantlock可中断:1.设置超时方法 trylock();2.lockInterruptibly()放在代码块中可以调用interrupt()方法中断。

  4. 是否公平
    sync非公平;Reentrantlock可选择是否公平

  5. 锁绑定多个条件
    sync没有;lock可以分组唤醒,精确唤醒,而不是像sync一样要么随机唤醒要么全部唤醒

线程池

特点:线程复用,控制最大并发,管理线程
优点:降低资源消耗,挺高响应速度,提高可管理性。
线程池重点:
Executor.newFixedThreadPool(int)----执行长期任务
Executor.newSignleThreadExecutor()—一个任务一个任务执行的场景
Executor.newCachedThreadPool()—执行许多短期异步的小程序或负载轻的服务器
上述线程池都是用的ThreadPoolExecutor实现

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
}
//七大参数
 public ThreadPoolExecutor(int corePoolSize,//核心线程数,常驻
                              int maximumPoolSize,//最大容纳的线程数
                              long keepAliveTime,//多余的空闲贤臣的存货时间,当前线程数超过核心线程数后,当空闲时间达到keepAliveTime值时,多余线程被销毁直到只剩核心个数个线程。
                              TimeUnit unit,//keepalive的单位
                              BlockingQueue<Runnable> workQueue,//被提交但是尚未执行的任务
                              ThreadFactory threadFactory,//生成线程池中工作线程的线程工厂
                              RejectedExecutionHandler handler//拒绝策略) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

当前线程满了并且阻塞队列满了并且还没达到最大线程数,那么开新线程并且新任务直接加塞到新开的线程。 -----也即是非公平锁
线程池的底层工作原理
在这里插入图片描述

线程池的拒绝策略:

  1. AbortPolicy(默认):直接抛出RejectedExecutionException异常阻止系统运行。
  2. CallerRunsPolicy:“调用者运行”一种调节机制,不抛弃任务也不抛异常,而是将某些任务回退给调用者,从而降低流量。
  3. DiscardOldestPolicy:抛弃队列里等待最久的任务,然后把当前任务加入队列再次尝试提交。
  4. DiscardPolicy:直接丢弃任务,无异常不处理。—如果允许任务丢失这个是最好的方法。

线程必须由线程池创建,不能new
不允许用Executors,必须用ThreadPoolExecutor方式。
Executors中的阻塞队列长度为Integer.MAX_VALUE,不符合实际。

如何合理配置线程数:
CPU密集型 CPU*2
IO密集型 C P U 1 − D C , D C ≈ 0.9 \frac{CPU}{1-DC},DC \approx 0.9 1DCCPU,DC0.9,

死锁编码及定位分析

在对应文件夹下用 jps -l 定位进程号,再用jstack 进程号 找到死锁看

©️2020 CSDN 皮肤主题: 书香水墨 设计师:CSDN官方博客 返回首页