[核心技术36问]13-24

13.谈谈接口和抽象类有什么区别?

    接口和抽象类都是java面向对象设计的两个基础机制。

    接口是对行为的抽象,它是抽象方法的集合,利用接口可以实现API定义和实现分离的目的。接口,不能实例化,不能包含任何非常量成员,即所有的field默认隐含都是public static final的;接口中不能包含任何非静态方法的实现,即接口中的方法要么是抽象方法,要么是静态方法。java标准类库中,定义了非常多的接口,比如java.util.List。

    抽象类是不能实例化的类,用abstract关键字修饰class,其主要目的是代码重用。除了不能实例化,抽象类和一般的java类没有太大的区别,抽象类中可以有一个或者多个抽象方法,也可以没有抽象方法。抽象类大多用于抽取相关java类的共同方法实现或者共同成员变量,然后通过继承的方式达到代码复用的目的。java标准库中,比如collection框架,很多通用部分就被抽取成为抽象类,例如java.util.AbstractList。

    java类实现interface使用implements关键词,继承抽象类则是使用extends关键词。

14.谈谈你知道的设计模式?请手动实现单例模式,Spring等框架中使用了哪些模式?

    设计模式是人们为软件开发中相同表征的问题,抽象出的可重复利用的解决方案。

    大致按照模式的应用目标分类,设计模式可以分为创建型模式、结构型模式和行为型模式。

    创建型模式,是对对象创建过程的各种问题和解决方案的总结,包括各种工厂模式(Factory、AbstractFactory)、单例模式(Singleton)、构建器模式(Builder)、原型模式(ProtoType)。

    结构型模式,是针对软件设计结构的总结,关注于类、对象继承、组合方式的实践经验。常见的结构型模式,包括桥接模式(Bridge)、适配器模式(Adapter)、装饰者模式(Decorator)、代理模式(Proxy)、组合模式(Composite)、外观模式(Facade)、享元模式(Flyweight)等。

    行为型模式,是从类或对象之间交互、职责划分等角度总结的模式。比较常见的行为型模式有策略模式(Strategy)、解释器模式(Interpreter)、命令模式(Command)、观察者模式(Observer)、迭代器模式(Iterator)、模板方法模式(Template Method)、访问者模式(Visitor)。

    Spring等如何在API设计中使用设计模式?

(1)BeanFactory和ApplicationContext使用了工厂模式;

(2)在Bean的创建中,Spring也为不同scope定义的对象,提供了单例和原型等模式实现;

(3)AOP使用了代理模式,适配器模式,装饰器模式;

(4)各种事件监听器,是观察者模式的典型应用;

(5)类似JDBCTemplate等则是使用了模板模式。

15.synchronized和RenntrantLock区别?有人说synchronized最慢,这话靠谱吗?

    synchronized是Java内建的同步机制,所以也有人称其为IntrinsicLocking,它提供了互斥的语义和可见性,当一个线程已经获取当前锁时,其他试图获取的线程只能阻塞或者等待。

    在Java 5以前,synchronized是仅有的同步手段,在代码中,synchronized可以用来修饰方法,也可以用来修饰代码块,本质上用synchronized修饰方法相当于把方法全部语句用synchronized块包起来。

    ReentrantLock,通常翻译为再入锁,是Java 5提供的锁实现,他的语义和synchronized基本相同。再入锁通过代码直接调用lock()获取锁,代码书写也更加灵活。与此同时,ReetrantLock提供了很多实用的方法,能够实现很多synchronized无法做到的细节控制,比如可以控制fairness,也就是公平性,或者利用定义条件等。但是,编码中也需要注意,必须明确调用unlock()方法释放,不然就会一直持有该锁。

    synchronized和ReentrantLock的性能不能一概而论,早期版本中synchronized在很多场景中性能相差较大,在后续版本进行了较多改进,在低竞争场景中表现可能优于ReetrantLock。

    再入锁可以设置公平性(fairness),我们可以在创建再入锁时选择是否是公平的。这里所谓的公平性指的是在竞争场景中,当公平性为真时,会倾向于将锁赋予等待时间最久的线程。公平性是减少线程“饥饿”(个别线程长期等待锁,但始终无法获取)情况发生的一个办法。

    如果使用synchronized,我们根本无法进行公平性的操作,其永远是不公平的,这也是主流操作系统线程调度的选择。通用场景中,公平性未必有想象的那么重要,Java默认的调度策略很少会导致饥饿发生。与此同时,若要保证公平性则会引入额外开销,自然会导致一定的吞吐量下降。

16.synchronized底层如何实现?什么是锁的升级、降级?

    synchronized代码块是由一对monitorenter/monitorexit指令实现的,Monitor对象是同步的基本实现单元。

    在Java 6之前,monitor的实现完全是依靠操作系统内部的互斥锁,因为需要进行用户态到内核态的切换,所以同步操作是一个无差别的重量级操作。

    现代的(oracle)JDK中,JVM对此进行了大刀阔斧地改进,提供了三种不同的monitor实现,也就是常说的三种不同的锁,偏斜锁(Biased Locking)、轻量级锁和重量级锁,大大改进了其性能。

    所谓锁的升级、降级,就是JVM优化synchronized运行的机制,当JVM检测到不同的竞争状况时,会自动切换到适合的锁实现,这种切换就是锁的升级、降级。

    当没有竞争出现时,默认会使用偏斜锁。JVM会利用CAS在对象头上的Mark World部分设置当前线程ID,表示这个对象偏向于当前线程,所以并不涉及真正的互斥锁。这样做的假设是基于在很多应用场景中,大部分对象在整个生命周期中都只会被一个线程锁定,使用偏斜锁可以降低无竞争开销。

    如果有另外的线程试图锁定某个被偏斜过的对象,JVM就需要撤销偏斜锁,并切换到轻量级锁实现。轻量级锁依赖CAS操作Mark World来试图获取锁,如果重试成功,就使用普通的轻量级锁,否则升级为重量级锁。

    有的观点认为Java不会进行锁降级,实际上锁降级确实会发生,当JVM进入安全点(safe point)时,会检查是否有闲置的Monitor,然后试图进行降级。

    

    

    

猜你喜欢

转载自blog.csdn.net/hellodake/article/details/81103947