程序猿每日面经[3]

1.单例模式?

  1. 饿汉式:优点:线程安全;缺点:对象加载时间过长。
  2. 懒汉式:优点:延迟对象的创建;缺点:线程不安全。
//饿汉式
class Bank{
    //私有化类的构造器
    private Bank(){
    
    }
    //内部创建类的对象
    //要求此对象也必须声明为静态的
    private static Bank instance = new Bank();
    
    //提供公共的静态的方法,返回类的对象
    public static Bank getInstance(){
        return instance;
    }    
}
//懒汉式
class Order{
    //私有化类的构造器
    private Order(){
    
    }
    //声明当前类的对象,没有初始化
    //此对象也必须声明为static的
    private static Order instance = null;

    //声明pulic,static的返回当前类对象的方法
    public static Order getInstance(){
        if(instance==null){
            instance = new Order();
        }
        return instance;
    }
}
//使用同步机制将懒汉式改写为线程安全的

//方式一
class Bank{
    private Bank(){
    
    }

    private static Bank instance = null;

    public static sychronized Bank getInstance(){
        if(instance == null){
            instance = new Bank();
        }
        return instance;
    }
}


//方式二
class Bank{
    private Bank(){
        
    }

    private static Bank instance=null;

    public static Bank getInstance(){
        //效率较差
        sychronized (Bank.class){
            if(instance == null){
                instance = new Bank();
            }
           return instance;
        }
    }

    //效率更高
    if(instance==null){
        sychronized (Bank.class){
            if(instance == null){
                instance = new Bank();
            }
        }
    }
    return instance;
}


 2.乐观锁、悲观锁的使用场景?

  • 悲观锁(Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。

        悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。

        Java synchronized 就属于悲观锁的一种实现,每次线程要修改数据时都先获得锁,保证同一时刻只有一个线程能操作数据,其他线程则会被block。

  • 乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在提交更新的时候会判断一下在此期间别人有没有去更新这个数据。乐观锁适用于读多写少的应用场景,这样可以提高吞吐量。

    乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。

    乐观锁一般来说有以下2种方式: 
    1. 使用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。 
    2. 使用时间戳(timestamp)。乐观锁定的第二种实现方式和第一种差不多,同样是在需要乐观锁控制的table中增加一个字段,名称无所谓,字段类型使用时间戳(timestamp), 和上面的version类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。

    Java JUC中的atomic包就是乐观锁的一种实现,AtomicInteger 通过CAS(Compare And Set)操作实现线程安全的自增。


3.GC算法?

1.标记-清除算法(Mark-Sweep):

分为“标记”和“清除”两个阶段,首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象;或者可以标记存活的对象,统一回收所有未被标记的对象,标记过程就是判断对象是否属于垃圾。

两个缺点:一是执行效率不稳定,若Java堆中包含大量对象,而大部分都需要被回收,就必须进行大量标记和清除动作,导致这两个过程的执行效率都会随着对象数量增长而降低;二是内存空间的碎片化问题,标记、清除之后会产生大量不连续的内存碎片,空间碎片太多会导致以后程序运行需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次GC动作。

2.标记-复制算法:

“半区复制”的GC算法:将可用内存按容量划分为大小相等的两块,每次只使用其中一块。这一块用完了,就将还存活着的对象复制到另一块上,然后再把已使用过的内存空间一次性清理掉。

缺点:若内存中多数对象都是存活的,这种算法会导致大量内存复制的开销,但如果多数对象都是可回收的,算法需要复制的就是占少数的存活对象,而且每次只针对半区进行回收,分配内存时也不必考虑有空间碎片的复杂情况,只要移动堆顶指针,按顺序分配即可。但可用内存减少为了原来的一半,造成空间浪费。

3.标记-整理算法(Mark-Compact):针对老年代的死亡特征提出的。

与标记-清除算法的区别就是前者是非移动式的回收算法,后者是移动式的。在标记之后,让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存。

        此外,是否移动存活对象是一个风险决策:若移动存活对象,对于老年代这种每次回收都有大量对象存活区域,移动所有对象并更新所有引用这些对象的地方是一种负重操作,这种对象移动操作需要全程暂停用户应用程序才能进行。

        是否移动对象都存在弊端:移动——>内存回收复杂,不移动——>内存分配复杂。不移动对象会使收集器的效率提高,但内存分配频率高,耗时增加,总吞吐量(赋值器与收集器的效率总和)下降。还有一种“和稀泥式”,可以不在内存分配和访问上增加负担,让虚拟机平时多采用标记-清除算法,暂时容忍内存碎片的存在,直到内存空间的碎片化程度已经大到影响对象分配,再采用标记-整理算法收集一次,以获得规整的内存空间。

        

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值