Java面试基础

Java面试基础

面向对象特征

  • 抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。
  • 继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类、基类);得到继承信息的类被称为子类(派生类).
  • 封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。
  • 多态性:多态性是指允许不同子类型的对象对同一消息作出不同的响应。

内部类

静态内部类:不可访问外部的非静态资源,可以有public static abstract class Demo

成员内部类:可以访问外部所有资源(但本身内部不可有静态属性,因为自己本身就需要依靠外部类实现)

局部内部类:不可被访问修饰符和static修饰,只能访问final变量和形参

匿名内部类:没有构造器,没有静态资源,无法被访问修饰符static修饰,只能创建匿名内部类的一个实例,
创建的时候一定是在new后面

1.String,Stringbuffer,StringBuilder的区别

String:
String类是一个不可变的类,一旦创建就不可以修改。
String是final类,不能被继承
String实现了equals()方法和hashCode()方法

StringBuffer:
继承自AbstractStringBuilder,是可变类。
StringBuffer是线程安全的
可以通过append方法动态构造数据。

StringBuilder:
继承自AbstractStringBuilder,是可变类。
StringBuilder是非线性安全的。
执行效率比StringBuffer高。

1.1.String s 与new String的区别

String str ="whx";
String newStr =new String ("whx");
String str ="whx"
先在常量池中查找有没有"whx" 这个对象,如果有,就让str指向那个"whx".如果没有,在常量池中新建一个“whx”对象,并让str指向在常量池中新建的对象"whx"。
String new str =new String ("whx");
是在堆中建立的对象"whx" ,在栈中创建堆中"whx" 对象的内存地址。

1.2String为什么不可变

虽然String、StringBuffer和StringBuilder都是final类,它们生成的对象都是不可变的,而且它们内部也都是靠char数组实现的,但是不同之处在于,String类中定义的char数组是final的,而StringBuffer和StringBuilder都是继承自AbstractStringBuilder类,它们的内部实现都是靠这个父类完成的,而这个父类中定义的char数组只是一个普通是私有变量,可以用append追加。因为AbstractStringBuilder实现了Appendable接口。

2.常用集合类型

 

Java面试基础

 

3.反射的原理,反射创建类实例的三种方式是什么

Java反射机制:
Java 的反射机制是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法; 并且对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息以及动态调用对象方法的功能成为Java语言的反射机制

获取 Class 类对象三种方式:
使用 Class.forName 静态方法
使用类的.class 方法
使用实例对象的 getClass() 方法

4.谈谈序列化与反序列化

序列化是指将对象转换为字节序列的过程,而反序列化则是将字节序列转换为对象的过程。
Java对象序列化是将实现了Serializable接口的对象转换成一个字节序列,能够通过网络传输、文件存储等方式传输 ,传输过程中却不必担心数据在不同机器、不同环境下发生改变,也不必关心字节的顺序或其他任何细节,并能够在以后将这个字节序列完全恢复为原来的对象。

5.int和Integer有什么区别?

为了编程的方便还是引入了基本数据类型,但是为了能够将这些基本数据类型当成对象操作,Java为每一个基本数据类型都引入了对应的包装类型(wrapper class),int的包装类就是Integer,从Java 5开始引入了自动装箱/拆箱机制,使得二者可以相互转换。

Java 提供两种不同的类型:引用类型和原始类型(或内置类型)。Int是java的原始数据类型,Integer是java为int提供的封装类。

  • 原始类型: boolean,char,byte,short,int,long,float,double
  • 包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double

7.请你解释什么是值传递和引用传递?

值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量.
引用传递一般是对于对象型变量而言的,传递的是该对象地址的一个副本, 并不是原对象本身 。 所以对引用对象进行操作会同时改变原对象.一般认为java内的传递都是值传递.

8.==与equlas有什么区别?

==:如果是基本类型,判断它们是否相等;如果是引用对象,判断两个对象指向的内存地址是否相同。
equals:如果是字符串,表示判断字符串内容是否相同(因为字符串重写了equals()方法);
如果是object对象的方法,比较的也是引用的内存地址值;
如果自己的类重写equals方法,可以自定义两个对象是否相等。

9.final关键字

当用final修饰一个类时,表明这个类不能被继承。
使用final方法的原因有两个。
第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。
对于一个final变量,
如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;
如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。

10.接口和抽象类的区别是什么?

接口中所有的方法隐含的都是抽象的。而抽象类则可以同时包含抽象和非抽象的方法。
接口中不能有构造方法,抽象类中可以有构造方法来初始化抽象类中的成员变量。
类可以实现很多个接口,但是只能继承一个抽象类
类可以不实现抽象类和接口声明的所有方法,当然,在这种情况下,类也必须得声明成是抽象的。
抽象类可以在不提供接口方法实现的情况下实现接口。
Java接口中声明的变量默认都是public static final的。抽象类可以包含非final的变量。
Java接口中的成员函数默认是public abstract的。抽象类的成员函数可以是private,protected或者是public。
接口是绝对抽象的,不可以被实例化。抽象类也不可以被实例化,如果它包含main方法的话是可以被调用的。

11.请你说说Iterator和ListIterator的区别?

Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。
Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。
ListIterator实现了Iterator接口,并包含其他的功能:增加元素,替换元素,获取前一个和后一个元素的索引。

12.throw和throws有什么区别?

1、Throw用于方法内部,Throws用于方法声明上
2、Throw后跟异常对象,Throws后跟异常类型
3、Throw后只能跟一个异常对象,Throws后可以一次声明多种异常类型

public static void testThrow (String filePath) throws IOException{

if (filePath==null) {

throw new IOException() ;//运行时异常不需要在方法上申明

}

}

13.同步方法和同步代码块的区别是什么?

同步方法默认用this或者当前类class对象作为锁;
同步代码块可以选择以什么来加锁,比同步方法要更细颗粒度,我们可以选择只同步会发生同步问题的部分代码而不是整个方法;
同步方法使用关键字synchronized修饰方法,而同步代码块主要是修饰需要进行同步的代码,用synchronized(object){代码内容}进行修饰;

14.如何确保N个线程可以访问N个资源同时又不导致死锁?

使用多线程的时候,一种非常简单的避免死锁的方式就是:指定获取锁的顺序,并强制线程按照指定的顺序获取锁。因此,如果所有的线程都是以同样的顺序加锁和释放锁,就不会出现死锁了。

15.“static”关键字是什么意思?Java中是否可以覆盖(override)一个private或者是static的方法?

static关键字表明一个静态成员变量或者是成员方法,可以在没有所属的类的实例变量的情况下被访问,静态变量是属于整个类,不属于某个对象。
Java中static方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而static方法是编译时静态绑定的。static方法跟类的任何实例都不相关,所以概念上不适用。
java中也不可以覆盖private的方法,因为private修饰的变量和方法只能在当前类中使用,如果是其他的类继承当前类是不能访问到private变量或方法的,当然也不能覆盖。

16.是否可以在static环境中访问非static变量?

因为静态的成员属于类,随着类的加载而加载到静态方法区内存,当类加载时,此时不一定有实例创建,没有实例,就不可以访问非静态的成员。

17.阐述静态变量和实例变量的区别?

静态变量是被static修饰符修饰的变量,也成为类变量,属于类,不属于类中任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且只有一个拷贝,静态变量可以实现让多个对象使用;
实例变量必须存在于某一实例,需要先创建对象然后通过对象才能访问它。

18.Error和Exception有什么区别?

首先Exception和Error都是继承于Throwable 类

Exception是java程序征程运行中可预料的异常情况,咱们可以获取到这种异常,并且对这种异常进行业务外的处理。

Error是正常情况下不可能发生的错误,会直接导致JVM不可处理或者不可恢复的情况。所以这种异常不可能抓取到,比如OutOfMemoryError、NoClassDefFoundError等。

其中的Exception又分为检查性异常和非检查性异常。两个根本的区别在于,检查性异常必须在编写代码时,使用try catch捕获(比如:IOException异常)。非检查性异常在代码编写使,可以忽略捕获操作(比如:
ArrayIndexOutOfBoundsException),这种异常是在代码编写或者使用过程中通过规范可以避免发生的。

19.访问修饰符public,private,protected,以及不写(默认)时的区别?

答:区别如下:
作用域 当前类 同包 子类 其他
public √ √ √ √
protected √ √ √ ×
default √ √ × ×
private √ × × ×

运行时异常与受检异常有何异同?

受检异常跟程序运行的上下文环境有关,即使程序设计无误,仍然可能因使用的问题而引发。Java 编译器要求方法必须声明抛出可能发生的受检异常,但是并不要求必须声明抛出未被捕获的运行时异常。

22.列出一些你常见的运行时异常?

ArithmeticException(算术异常)
ClassCastException (类转换异常)
IllegalArgumentException (非法参数异常)
IndexOutOfBoundsException (下表越界异常)
NullPointerException (空指针异常)
SecurityException (安全异常)

23.sleep()和wait()的区别?

1.wait()属于Object类,sleep()属于Thread类
2.wait()会释放对象锁而sleep()不会
3.在调用sleep()方法的过程中,线程不会释放对象锁
当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待池,只有针对此对象调用notify()或notifyAll()方法后本线程才进入对象锁池准备获取对象锁进入运行状态
4.wait()需要在同步块中使用,sleep()可以在任何地方使用
5.sleep()需要捕获异常,wait()不需要

24.Comparable和Comparator的区别?

Comparable接口中只有一个方法compareTo(),就是调用方法的当前对象this和o进行比较,若返回值大于0则this > o,返回值等于0则是this = o,返回值小于0则是this < o;

public interface Comparable<T> {

public int compareTo(T o);

}

Comparator接口中方法很多,但是我们只需要实现一个compare()。
compare()比较o1和o2,返回值大于0则o1 > o2,以此类推。对于compare()来说this是谁不重要,所比较的两个对象都已经传入到方法中;

public interface Comparator<T> {

int compare(T o1, T o2);

// default ... ...

}

1.HTTP响应的结构是怎么样的?

HTTP的请求报文的组成:
请求方法 + 请求的资源的URI + 协议版本 + 可选的请求首部字段 + 内容实体。

HTTP的响应报文的组成:
协议版本 + 状态码 + 用于解释状态码的原因短语 + 可选的响应首部字段 + 实体主体。

3.cookie 和 session 的区别?

1、cookie数据存放在客户的浏览器上,session数据放在服务器上。
2、cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗,考虑到安全应当使用session。
3、session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用COOKIE。
4、单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。

4.说说你对get和post请求,并且说说它们之间的区别?

①get请求用来从服务器上获得资源,而post是用来向服务器提交数据;

②get将表单中数据按照name=value的形式,添加到action 所指向的URL 后面,并且两者使用"?"连接,而各个变量之间使用"&"连接;
post是将表单中的数据放在HTTP协议的请求头或消息体中,传递到action所指向URL;
③get传输的数据要受到URL长度限制(1024字节)
post可以传输大量的数据,上传文件通常要使用post方式;
④使用get时参数会显示在地址栏上,如果这些数据不是敏感数据可以使用get;对于敏感数据还是应用使用post;

5.转发和重定向之间的区别?

转发是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,然后把这些内容再发给浏览器,浏览器根本不知道服务器发送的内容是从哪儿来的,所以它的地址栏中还是原来的地址。

重定向就是服务器端根据逻辑,发送一个状态码,告诉浏览器重新去请求某个地址,因此从浏览器的地址栏中可以看到跳转后的链接地址,很明显重定向无法访问到服务器保护起来资源,但是可以从一个网站重定向到其他网站。

转发更加高效。在有些情况下,比如需要访问一个其它服务器上的资源,则必须使用重定向。

1.进程与线程的区别?

1、进程是资源分配的最小单位,线程是程序执行的最小单位。
2、进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间, 而线程是共享进程中的数据、地址空间。
3、线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,进程之间的通信有管道,共享内存,消息队列,信号量四种方式。
4、多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。

2.线程安全怎么理解?

当多个线程访问某个方法时,不管你通过怎样的调用方式或者说这些线程如何交替的执行,我们在主程序中不需要去做任何的同步,这个类的结果行为都是我们设想的正确行为,那么我们就可以说这个类是线程安全的

3.线程同步和线程调度的相关方法?

wait():使一个线程处于等待状态,并且释放所持有的对象的锁,属于Object类;
sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理异常,属于线程类(Thread);
notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关;
notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;

4.Java中有几种方法可以实现一个线程?用什么关键字修饰同步方法?

1继承Thread类创建线程
2.实现Runnable接口创建线程
3.实现Callable接口通过FutureTask包装器来创建Thread线程
4.使用线程池例如用Executor框架。

5.启动一个线程是用run()还是start()?

启动一个线程是调用start()方法,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由JVM调度并执行。这并不意味着线程就会立即运行。run()方法可以产生必须退出的标志来停止一个线程。

6.请说明一下sleep() 和 wait() 有什么区别?

1.wait()属于Object类,sleep()属于Thread类
2.wait()会释放对象锁而sleep()不会
3.在调用sleep()方法的过程中,线程不会释放对象锁
当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待池,只有针对此对象调用notify()或notifyAll()方法后本线程才进入对象锁池准备获取对象锁进入运行状态
4.wait()需要在同步块中使用,sleep()可以在任何地方使用
5.sleep()需要捕获异常,wait()不需要

7.请分析一下同步方法和同步代码块的区别是什么?

区别:同步方法默认用this或者当前类class对象作为锁;
同步代码块可以选择以什么来加锁,比同步方法要更细颗粒度,可以选择只同步会发生同步问题的部分代码而不是整个方法。

8.请详细描述一下线程从创建到死亡的几种状态都有哪些?

新建( new ):新创建了一个线程对象。
就绪( runnable ):线程对象创建后,其他线程(比如 main 线程)调用了该对象 的 start ()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取 cpu 的使用权 。
运行( running ):可运行状态( runnable )的线程获得了 cpu 时间片( timeslice ) ,执行程序代码。
阻塞( block ):阻塞状态是指线程因为某种原因放弃了 cpu 使用权,阻塞的情况分三种: 等待阻塞、同步阻塞、其他阻塞:
死亡( dead ):线程 run ()、 main () 方法执行结束,或者因异常退出了 run ()方法,则该线程结束生命周期。死亡的线程不可再次复生。

9.常用线程池,线程池有哪几个参数

Java通过Executors提供四种线程池,分别为:
1)newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
2)newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
3)newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
4)newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

参数
corePoolSize :核心线程数量
maximumPoolSize :线程最大线程数
workQueue :阻塞队列,存储等待执行的任务 很重要 会对线程池运行产生重大影响
keepAliveTime :线程没有任务时最多保持多久时间终止
unit :keepAliveTime的时间单位
threadFactory :线程工厂,用来创建线程
rejectHandler :当拒绝处理任务时的策略

线程池怎么用

ExecutorService cachePool = Executors.newCachedThreadPool();

cachePool.execute(getThread(i));

17.线程池运行原理分析

首先判断核心线程池里的线程是否都在执行任务,
如果不是则直接从核心线程池中拿出一个线程来执行,
如果都在忙则判断任务队列是否也满了,没满的话将任务放进去等待执行,满了就判断线程池的全部线程是否都在忙,如果都在忙就交给饱和策略来处理,否则就创建一个普通线程来帮助核心线程处理任务。

18.请说明一下线程池有什么优势?

第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。

线程池参数介绍:

 

Java面试基础

 

10.乐观锁和悲观锁

悲观锁
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)
传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronizedReentrantLock等独占锁就是悲观锁思想的实现

乐观锁
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现
乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。Java中
java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的

10.请问什么是死锁(deadlock)?

两个线程或两个以上线程都在等待对方执行完毕才能继续往下执行的时候就发生了死锁。结果就是这些线程都陷入了无限的等待中。

11.如何避免线程死锁?

只要破坏产生死锁的四个条件中的其中一个就可以了。
破坏互斥条件:这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)。
破坏请求与保持条件:一次性申请所有的资源。
破坏不剥夺条件:占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
破坏循环等待条件:靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放

12.Synchronized和lock

1.Lock是一个接口,而synchronized是Java中的关键字;

2.synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;
而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;

3.Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;

4.通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。

15.synchronized 和 CAS 的区别

synchronized *采用的是CPU *悲观锁机制,即线程获得的是独占锁, 其他线程只能依靠阻塞来等待线程释放锁。性能较低。

CAS是比较并替换,采用的是一种乐观锁的机制,它不会阻塞任何线程,所以在效率上,它会比 synchronized 要高。
CAS当中使用了3个基本操作数:内存地址 V,旧的预期值 A,要修改的新值 B。更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B,具体过程:
1.在内存地址V当中,存储着值为10的变量
2.此时线程1想要把变量的值增加1。对线程1来说,旧的预期值A=10,要修改的新值B=11
3.在线程1要提交更新之前,另一个线程2抢先一步,把内存地址V中的变量值率先更新成了11
4.线程1开始提交更新,首先进行A和地址V的实际值比较Compare,发现A不等于V的实际值,提交失败
5.线程1重新获取内存地址V的当前值,并重新计算想要修改的新值。此时对线程1来说,A=11,B=12。这个重新尝试的过程被称为自旋
6.这一次比较幸运,没有其他线程改变地址V的值。线程1进行Compare,发现A和地址V的实际值是相等的
7.线程1进行SWAP,把地址V的值替换为B,也就是12 从思想上来说,Synchronized属于悲观锁,悲观地认为程序中的并发情况严重,所以严防死守。CAS属于乐观锁,乐观地认为程序中的并发情况不那么严重,所以让线程不断去尝试更新。

CAS的缺点:

CPU开销较大。在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力。
不能保证代码块的原子性。CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用Synchronized了

16.synchronized关键字和volatile关键字比较:

volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized关键字要好。
多线程访问volatile关键字不会发生阻塞,而synchronized关键字可能会发生阻塞
volatile关键字能保证数据的可见性,但不能保证数据的原子性。synchronized关键字两者都能保证。
volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized关键字解决的是多个线程之间访问资源的同步性。

20.线程阻塞

BIO,同步阻塞式IO,在此种方式下,用户进程在发起一个IO操作以后,必须等待IO操作的完成,只有当真正完成了IO操作以后,用户进程才能运行。JAVA传统的IO模型属于此种方式!
NIO,同步非阻塞IO,在此种方式下,用户进程发起一个IO操作以后边可返回做其它事情,但是用户进程需要时不时的询问IO操作是否就绪,这就要求用户进程不停的去询问,从而引入不必要的CPU资源浪费。其中目前JAVA的NIO就属于同步非阻塞IO。
AIO,异步非阻塞IO,在此种模式下,用户进程只需要发起一个IO操作然后立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的IO读写操作,因为真正的IO读取或者写入操作已经由内核完成了。目前Java中还没有支持此种IO模型

同步:自己亲自出马持银行卡到银行取钱(使用同步IO时,Java自己处理IO读写);
异步:委托一小弟拿银行卡到银行取钱,然后给你(使用异步IO时,Java将IO读写委托给OS处理,需要将数据缓冲区地址和大小传给OS(银行卡和密码),OS需要支持异步IO操作API);
阻塞:ATM排队取款,你只能等待(使用阻塞IO时,Java调用会一直阻塞到读写完成才返回);
非阻塞:柜台取款,取个号,然后坐在椅子上做其它事,等号广播会通知你办理,没到号你就不能去,你可以不断问大堂经理排到了没有,大堂经理如果说还没到你就不能去(使用非阻塞IO时,如果不能读写Java调用会马上返回,当IO事件分发器会通知可读写时再继续进行读写,不断循环直到读写完成)

21.AQS原理

抽象的队列式同步器,是除了java自带的synchronized关键字之外的锁机制,AQS的核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态,如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
CLH队列是一个虚拟的双向队列,虚拟的双向队列即不存在队列实例,仅存在节点之间的关联关系。
AQS是将每一条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node),来实现锁的分配。

基于AQS实现的lock, CountDownLatch、CyclicBarrier、Semaphore介绍

lock:
是一种可重入锁,除了能完成 synchronized 所能完成的所有工作外,还提供了诸如可响应中断锁、可轮询锁请求、定时锁等避免多线程死锁的方法。默认为非公平锁,但可以初始化为公平锁; 通过方法 lock()与 unlock()来进行加锁与解锁操作;

CountDownLatch:
通过计数法(倒计时器),让一些线程堵塞直到另一个线程完成一系列操作后才被唤醒;该⼯具通常⽤来控制线程等待,它可以让某⼀个线程等待直到倒计时结束,再开始执⾏。
​ (假设我们有这么一个场景,教室里有班长和其他6个人在教室上自习,怎么保证班长等其他6个人都走出教室在把教室门给关掉。)

CyclicBarrier:
​ 字面意思是可循环(Cyclic)使用的屏障(Barrier)。他要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活,线程进入屏障通过CyclicBarrier的await()方法。
(我们假设有这么一个场景,每辆车只能坐4个人,当车满了,就发车。)

Semaphore:
​ 信号量主要用于两个目的,一个是用于多个共享资源的互斥作用,另一个用于并发线程数的控制。
(假设我们有 3 个停车位,6 辆车去抢;指定多个线程同时访问某个资源。)

22.ThreadLocal的应用场景?

在Java的多线程编程中,为保证多个线程对共享变量的安全访问,通常会使用synchronized来保证同一时刻只有一个线程对共享变量进行操作。这种情况下可以将类变量放到ThreadLocal类型的对象中,使变量在每个线程中都有独立拷贝,不会出现一个线程读取变量时而被另一个线程修改的现象。最常见的ThreadLocal使用场景为用来解决数据库连接、Session管理等。

每一个线程内部都有一个
ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这是一个Map对象,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal<t>变量的弱引用,value为变量副本(即T类型的变量)。
初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。 然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。</t>

1.通过ThreadLocal创建的副本是存储在每个线程自己的threadLocals中的;
2.为何threadLocals的类型ThreadLocalMap的键值为ThreadLocal对象,因为每个线程中可有多个threadLocal变量.
3.在ThreadLocal对象进行get()之前,必须先set(),否则会报空指针异常;如果想在get()之前不需要调用set()就能正常访问的话,必须重写initialValue()方法。 因为在上面的代码分析过程中,我们发现如果没有先set()的话,即在map中查找不到对应的存储,则会通过调用setInitialValue方法返回i,而在setInitialValue()方法中,有一个语句是T value = initialValue(), 而默认情况下,initialValue()方法返回的是null。
4.因为ThreadLocal对象是弱引用的原因,当为null时,会被当成垃圾回收,如果突然我们ThreadLocal是null了,也就是要被垃圾回收器回收了,但是此时我们的ThreadLocalMap生命周期和Thread的一样,它不会回收,这时候就出现了一个现象。那就是ThreadLocalMap的key没了,但是value还在,这就造成了内存泄漏。(解决办法就是使用完ThreadLocal对象后,执行remove操作,避免出现内存溢出情况。)

深拷贝和浅拷贝的理解与实现方式

浅拷贝(Shallow Copy):
①对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。因为是两份不同的数据,所以对其中一个对象的该成员变量值进行修改,不会影响另一个对象拷贝得到的数据。
②对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。

浅拷贝的实现方式主要有三种:

一、通过拷贝构造方法实现浅拷贝:
拷贝构造方法指的是该类的构造方法参数为该类的对象。使用拷贝构造方法可以很好地完成浅拷贝,直接通过一个现有的对象创建出与该对象属性相同的新的对象。
二、通过重写clone()方法进行浅拷贝:
Object类是类结构的根类,其中有一个方法为protected Object clone() throws 
CloneNotSupportedException,这个方法就是进行的浅拷贝。有了这个浅拷贝模板,我们可以通过调用clone()方法来实现对象的浅拷贝。但是需要注意:1、Object类虽然有这个方法,但是这个方法是受保护的(被protected修饰),所以我们无法直接使用。2、使用clone方法的类必须实现Cloneable接口,否则会抛出异常CloneNotSupportedException。对于这两点,我们的解决方法是,在要使用clone方法的类中重写clone()方法,通过super.clone()调用Object类中的原clone方法。

深拷贝:首先介绍对象图的概念。设想一下,一个类有一个对象,其成员变量中又有一个对象,该对象指向另一个对象,另一个对象又指向另一个对象,直到一个确定的实例。这就形成了对象图。那么,对于深拷贝来说,不仅要复制对象的所有基本数据类型的成员变量值,还要为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象图进行拷贝!

简单地说,深拷贝对引用数据类型的成员变量的对象图中所有的对象都开辟了内存空间;而浅拷贝只是传递地址指向,新的对象并没有对引用数据类型创建内存空间。

深拷贝模型如图所示:可以看到所有的成员变量都进行了复制。

 

Java面试基础

 

深拷贝的实现方法主要有两种:

一、通过重写clone方法来实现深拷贝
与通过重写clone方法实现浅拷贝的基本思路一样,只需要为对象图的每一层的每一个对象都实现Cloneable接口并重写clone方法,最后在最顶层的类的重写的clone方法中调用所有的clone方法即可实现深拷贝。简单的说就是:每一层的每个对象都进行浅拷贝=深拷贝。

二、通过对象序列化实现深拷贝
虽然层次调用clone方法可以实现深拷贝,但是显然代码量实在太大。特别对于属性数量比较多、层次比较深的类而言,每个类都要重写clone方法太过繁琐。
将对象序列化为字节序列后,默认会将该对象的整个对象图进行序列化,再通过反序列即可完美地实现深拷贝。

单例模式

复制代码

class Signal {

private static volatile Single s = null;

private Singal(){}

public stattic Single getInstance(){

if(s == null){

synchronized(Signal.class){

if(s == null){

s = new Single();

}

}

}

return s;

}

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值