8/19知识点整理

一、抽象类和接口的相同点和不同点

相同点:

  • 都不能被实例化
  • 接口的实现类或抽象类的子类都只有实现了接口或抽象类中的抽象方法才能被实例化。

不同点:

  • 接口只有方法的定义,不能有方法的实现。抽象类中可以有非抽象方法的实现。
  • 实现接口的关键字为implements,继承抽象类的关键字为extends。
  • 一个类可以实现多个接口,但一个类只能继承一个抽象类
  • 接口中成员变量默认为public static final,因此只能有静态的不能被修改的数据成员,而且必须赋初值;所有成员方法都是public、abstract的,而且只能被这两个关键字修饰。而抽象类可以有自己的数据成员变量,也可以有非抽象的成员方法;抽象类中的成员变量默认为default,当然也可以被定义为private、protected和public,这些成员变量可以在子类中被重新定义、重新赋值;抽象类中的抽象方法不能用private、static、synchronized和native等访问修饰符修饰,同时方法必须以分号结尾,并且不带花括号{}。
  • 接口强调的是特定功能的实现,设计理念是“like-a”关系;抽象类强调所属关系,设计理念是“is-a”关系。
  • 接口被运用于实现比较常见的功能,便于日后维护或者添加删除方法;抽象类更倾向于充当公共类的角色,不适用于日后重新对里面的方法进行修改。

二、举一个平常用到的抽象类,为什么要用抽象类

比如AbstractStringBuilder,子类是我们常用的StringBuilder和StringBuffer。

 

抽象类往往用来表征我们在对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。

把那些和“东西”差不多的类写成抽象的。而水杯一样的类就可以不是抽象的了。

(“麻烦你,小王。帮我把那个东西拿过来好吗” 在生活中,你肯定用过这个词--东西。 小王:“你要让我帮你拿那个水杯吗?” 你要的是水杯类的对象。而东西是水杯的父类。通常东西类没有实例对象,但我们有时需要东西的引用指向它的子类实例。)

其实不是说抽象类有什么用,一般类确实也能满足应用;而写成抽象类,这样别人看到你的代码,或你看到别人的代码,你就会注意抽象方法,而知道这个方法是在子类中实现的,所以,有个提示作用。

三、谈谈java堆和栈

简单的说: Java把内存划分成两种:一种是栈内存,一种是堆内存。

1.

(1)堆内存用来存放由new创建的对象和数组。在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。

(2)在函数中定义的一些基本类型的变量和对象的引用变量都在函数的栈内存中分配。 当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。

与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。

2.

(1)堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。

(2)栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。另外,栈数据可以共享

3.

(1)包装类数据,如Integer, String, Double等将相应的基本数据类型包装起来的类。这些类数据全部存在于堆中,Java用new()语句来显式地告诉编译器,在运行时才根据需要动态创建,因此比较灵活,但缺点是要占用更多的时间。

(2) Java中的数据类型有两种。 一种是基本类型, 共有8种.这种类型的定义是自动变量,自动变量存的是字面值,不是类的实例,也不是类的引用。如int a = 3;a是一个int类型的引用,指向3这个字面值。由于大小可知,生存期可知。java为了追求速度,就存在栈中。同时,存在栈中的数据可以共享,比如int a = 3;int b = 3;首先它会在栈中创建一个变量为a的引用,然后查找有没有字面值为3的地址,没找到,就开辟一个存放3这个字面值的地址,然后将a指向3的地址。接着处理int b = 3;在创建完b的引用变量后,由于在栈中已经有3这个字面值,便将b直接指向3的地址。这样,就出现了a与b同时均指向3的情况。

4.

String是一个特殊的包装类数据。即可以用String str = new String("abc");的形式来创建,也可以用String str = "abc";的形式来创建

(1)关于String str = "abc",并没有通过new来创建对象实例,则会把str存在栈中,指向常量abc,如果存在String对象strs也等也abc,则strs也会指向栈中同一个abc常量。

(2)只要是用new()来新建对象的,都会在堆中创建,而且其字符串是单独存值的,即使与栈中的数据相同,也不会与栈中的数据共享。

数据类型包装类的值不可修改。不仅仅是String类的值不可修改,所有的数据类型包装类都不能更改其内部的值。

四、什么时候FullGC

(1)调用System.gc时,系统建议执行Full GC,但是不必然执行

(2)老年代空间不足

(3)方法去空间不足

(4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存

(5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小

五、写代码的时候如何避免FullGC对系统的影响

(1)首先,删掉所有System.gc(),让jvm自己去管理内存

(2)不要创建长度过大的数组和大对象,因为会直接进入老年代,容易触发FullGC

(3)通过JVM调参,增大java堆和方法区中的空间。

例如:-Xms4g:初始化堆内存大小为4GB 。

          -XX:PermSize=100m:初始化永久代大小为100MB。

六、多线程如何实现线程安全?

(1)使用锁机制 synchronize,为资源加锁,保证每时每刻一个资源最多只能被一个线程拥有。

(2)使用 java.util.concurrent下的类库来保存数据

(3)使用ThreadLocal,就是为每一个线程维护一个副本变量,从而让线程拥有私有的资源,就不再需要去竞争进程中的资源。每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。 

七、解释一下信号量

信号量是操作系统提供给用户使用的一种机制,帮助用户进程协调使用资源,用户编程的时候可以直接调用,不必自己设计。信号量是解决资源竞争最有效的途径。

比如信号量S=3代表资源目前还有3个,没有进程阻塞;S=-2代表资源已经都被占用,且阻塞队列中等待资源的进程有2个。最重要的是信号量还代表这个资源是互斥的。

 

例:若有一售票厅只能容纳300人,当少于300人时,可以进入;否则,需在外等候。若将每一个购票者作为一个进程,请用P(wait)、V(signal)操作编程,并写出信号量的初值。(强调:只有一个购票窗口,每次只能为一位购票者服务)

分析:题中有两类资源,售票厅和售票窗口,售票厅可以容纳300人,窗口只能服务1个人。用户到达后要想买到票,首先要进入售票厅,然后申请到窗口才行。因此用户需要获得两类资源才能成功购票。好了,我们定义两个信号量一个是S1,代表售票厅还能容纳的人数,一个是S2,代表窗口,它们的初始值一个是300,一个是1。程序如下:

解:semophore S1=330,S2=1;

    用户进程Pi {

                    Wait(S1)

                    进入大厅

                    Wait(S2)

                    窗口购票

                   退出购票窗口

                    Signal(S2)

                     退出大厅

                    Signal(S1)

}

八、介绍一下乐观锁和悲观锁

1、乐观锁

总是认为不会产生并发问题,每次去取数据的时候总认为不会有其他线程对数据进行修改,因此不会上锁,但是在更新时会判断其他线程在这之前有没有对数据进行修改

2、悲观锁

总是假设最坏的情况,每次取数据时都认为其他线程会修改,所以都会加锁(读锁、写锁、行锁等),当其他线程想要访问数据时,都需要阻塞挂起。在Java中,synchronized的思想也是悲观锁。

九、乐观锁如何实现?乐观锁在什么时候进行加锁?

前提是,不会发生并发抢占资源,只有在提交操作的时候检查是否违反数据完整性。只能防止脏读后数据的提交,不能解决脏读。

(1)使用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。

(2)CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。

CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。”这其实和乐观锁的冲突检查+数据更新的原理是一样的。

在JDK1.5 中新增java.util.concurrent(J.U.C)就是建立在CAS之上的。相对于对于synchronized这种阻塞算法,CAS是非阻塞算法的一种常见实现。所以J.U.C在性能上有了很大的提升。

十、线程死锁如何调试?用什么工具来调试

Java中jdk 给我们提供了很便利的工具,帮助我们定位和分析死锁问题

使用Jconsole查看死锁

进入java安装的位置,输入Jconsole

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值