0
点赞
收藏
分享

微信扫一扫

JAVA面试题JAVA锁相关面试题总结(一)

booksmg2014 2022-04-04 阅读 50
java后端

JAVA基础篇面试题

文章目录


1. 什么是JMM

JMM(Java Memory Model)本身是一种抽象的概念并不真实存在,它描述的是一组规则或规范,通过这组规定定义了程序中各个变量(实例字段,静态字段和构成数组对象的元素)的访问方式;

JMM关于同步的规定:

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

2. 介绍一下violated

定义:是java虚拟机提供的轻量级的同步机制;

特征:

  1. 保证可见性;

  2. 不保证原子性;

  3. 禁止指令重排序;

3. 写一个单例模式

DCL双端检锁机制不一定线程安全,可能出现指令重排序,加入violate可以避免重排序。例如单例下new一个对象需要进行以下3步:

  1. 分配对象内存空间;

  2. 初始化对象;

    1. 将引用指向内存地址;

    步骤2和步骤3不存在依赖关系,可以重排序;

    因此DCL应该如此实现:

    class SingletonDemo {
    private static violate SingletonDemo instance = null;
    public static SingletonDemo getInstance() {
    if (instance == null) {
    synchronized(SingletonDemo.class) {
    if (instance == null) {
    instance = new SingletonDemo();
    }
    }
    }
    return instance;
    }
    }

4. 介绍一下CAS

概念:compare and set。需要3个操作数,分别是内存位置V,旧的预期值A,准备设置的新值B。CAS执行时,当地址V对应的旧值是A时,处理器才会将V对应的值更新为B,否则就不执行更新。该操作为原子操作,不会被其他线程中断;

Java的实现:引入Unsafe类,其通过本地native方法直接操作特定的内存数据。通过对内存的偏移地址去获取值和循环修改数据直至成功。JVM会编译成CAS的字节码指令,通过硬件功能保证指令执行过程中是连续的,原子性的。

public class AtomicInteger extends Number implements java.io.Serializable {
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
// 获取对象值的内存偏移量
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
// 此处要设置为violatile
private volatile int value;
// ......
}

5. CAS的问题

  1. 长时间不成功,会造成自旋导致CPU带来很大的开销;

  2. 只能保证一个共享变量的操作;

  3. 出现ABA问题;

6. ArrayList线程不安全的替换方案

  1. 使用Vector

  2. 使用集合类方法Collections.synchronizedList()

  3. 使用CopyOnWriteList,写时复制容器;

7. 什么是公平锁

概念:在并发包中ReentrantLock的创建可以指定构造函数的boolean类型来得到公平锁或非公平锁,默认是非公平锁;

公平锁:在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果空,或者是队列首个就占有锁,否则就加入等待队列中,按照FIFO的规则获取锁;

非公平锁:先抢先得,否则就排队等待。 优点吞吐量大。synchronized也是非公平的。

8. 什么是可重入锁

指的是同一线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码,在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。加几次锁需要释放几次锁,否则死锁。synchronized, ReentrantLock都是可重入锁。

synchronized:java中最基本的互斥同步手段。javac编译后,会在同步块前后形成monitorentermonitorexit这两个字节码指令;如果synchronized指明了对象参数,那就锁定这个对象;如果未指定对象参数,则根据其修饰的方法类型(实例方法,或类方法)来决定是取代码所在的对象实例还是取类型对应的Class对象来作为线程要持有的锁。

具体的执行过程需要看[java基础知识-java内部的锁]文章,简述一下加锁解锁原理:

实现:每个锁对象头有一个锁的计数器,和一个指向持有锁的线程的指针;

原理:目标锁对象的计数器为0时,线程monitorenter将其占有,并计数器+1,每次加锁(重入时)计数器+1,当monitorexit退出时,计数器-1。当计数器为0时,表示锁已释放;

9. 什么是自旋锁

是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。

10. 什么是独占/共享/互斥锁

多个线程同时读一个资源类没有任何问题,所以为了满足开发量,读取共享资源应该可以同时进行,但是,如果有一个线程写共享资源,其他线程就无法读写。

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

private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

public Object get(String key) {
lock.readLock().lock();
System.out.println(String.format("线程%s 读取开始", Thread.currentThread().getName()));
try {
Object o = map.get(key);
return o;
}finally {
System.out.println(String.format("线程%s 读取结束", Thread.currentThread().getName()));
lock.readLock().unlock();
}
}

public void set(String key, Object v) {
lock.writeLock().lock();
System.out.println(String.format("线程%s 写开始", Thread.currentThread().getName()));
try {
map.put(key, v);
} finally {
System.out.println(String.format("线程%s 写结束", Thread.currentThread().getName()));
lock.writeLock().unlock();
}
}
}

11. CountDownLatch,CyclicBarrier,Semaphore

CountDownLatch递减,直到为0才不阻塞;

CyclicBarrier递增,直到预期值才不阻塞;

Semaphore信号量,同时可进入临界区线程数量;

Semaphore s = new Semaphore(3);// 同时允许三个线程访问
s.acquire();
s.release();

12. 什么是阻塞队列

特征:BlockingQueue 阻塞队列,当阻塞队列是空时,从队列中获取元素的操作将会被阻塞,当阻塞队列是空时,从队列中获取元素的操作将会被阻塞。

优势:在多线程环境下,我们必须自己取控制这些资源管理的细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度。

实现类:

  • ArrayBlockQueue:由数组结构组成的有界阻塞队列
  • LinkedBlockingQueue:由链表结构组成的有界(但是默认大小 Integer.MAX_VALUE)的阻塞队列
    • 有界,但是界限非常大,相当于无界,可以当成无界
  • PriorityBlockQueue:支持优先级排序的无界阻塞队列
  • DelayQueue:使用优先级队列实现的延迟无界阻塞队列
  • SynchronousQueue:不存储元素的阻塞队列,也即单个元素的队列
    • 生产一个,消费一个,不存储元素,不消费不生产
  • LinkedTransferQueue:由链表结构组成的无界阻塞队列
  • LinkedBlockingDeque:由链表结构组成的双向阻塞队列

核心方法:

在这里插入图片描述

抛出异常

当阻塞队列满时:在往队列中add插入元素会抛出 IIIegalStateException:Queue full 当阻塞队列空时:再往队列中remove移除元素,会抛出NoSuchException

特殊性

插入方法,成功true,失败false 移除方法:成功返回出队列元素,队列没有就返回空

一直阻塞

当阻塞队列满时,生产者继续往队列里put元素,队列会一直阻塞生产线程直到put数据or响应中断退出, 当阻塞队列空时,消费者线程试图从队列里take元素,队列会一直阻塞消费者线程直到队列可用。

超时退出

当阻塞队列满时,队里会阻塞生产者线程一定时间,超过限时后生产者线程会退出

举报

相关推荐

0 条评论