Java知识点查漏补缺-Java基础,
Java知识点查漏补缺-Java基础,
这系列的blog有点扎心的,大多是面试不会的,或者觉得回答不够完美的题目,不过话说回来,被问到不会一次,回来看看,下次就不会扎心啦啦啦!
1. JDK vs JRE vs JVM
JDK:Java Development Kit,JDK顾名思义是java开发工具包,是程序员使用java语言编写java程序所需的开发工具包,是提供给程序员使用的。JDK包含了JRE,同时还包含了编译java源码的编译器javac,还包含了很多java程序调试和分析的工具:jconsole,jvisualvm等工具软件,还包含了java程序编写所需的文档和demo例子程序。
JRE:Java Development Kit,JRE顾名思义是java运行时环境,包含了java虚拟机,java基础类库。是使用java语言编写的程序运行所需要的软件环境,是提供给想运行java程序的用户使用的。
JVM:Java Virtual Machine,JVM是java虚拟机,是Java编程语言的核心。当我们运行一个程序时,JVM负责将字节码转换为特定机器代码。JVM也是平台特定的,并提供核心的Java方法,例如内存管理、垃圾回收和安全机制等。JVM 是可定制化的,我们可以通过Java 选项(java options)定制它,比如配置JVM 内存的上下界。JVM之所以被称为虚拟的是因为它提供了一个不依赖于底层操作系统和机器硬件的接口。这种独立于硬件和操作系统的特性正是Java程序可以一次编写多处执行的原因。
区别:
- JDK是用于开发的而JRE是用于运行Java程序的,JDK包含JRE。
- JDK和JRE都包含了JVM,从而使得我们可以运行Java程序。
- JVM是Java编程语言的核心并且具有平台独立性。
2. Java中final,finally和finalize()
final:修饰变量---值不可以被修改;修饰方法---方法不可以被重写;修饰类---类不可以被继承。
finally:一般是用于异常处理中,提供finally块来执行任何的清楚操作,try{} catch(){} finally{}。finally关键字是对java异常处理模型的最佳补充。finally结构使代码总会执行,不关有无异常发生。使得finally可以维护对象的内部状态,并可以清理非内存资源。finally在try,catch中可以有,可以没有。如果try-catch中有finally则必须执行finally块中的操作。一般情况下,用于关闭文件的读写操作,或者是关闭数据库的连接等等。
finalize():finalize()是方法名。在java中,允许使用finalize()方法在垃圾收集器将对象从内存中清理出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是Object类中定义的,因此,所有的类都继承了它。
3. Java中创建线程的几种方法
- 继承Thread类创建线程类
(1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。
(2)创建Thread子类的实例,即创建了线程对象。
(3)调用线程对象的start()方法来启动该线程。
public class ThreadTest extends Thread{
int i = 0;
//重写run方法,run方法的方法体就是现场执行体
public void run()
{
for(;i<100;i++){
System.out.println(getName()+" "+i);
}
}
public static void main(String[] args)
{
for(int i = 0;i< 100;i++)
{
System.out.println(Thread.currentThread().getName()+" : "+i);
if(i==20)
{
new FirstThreadTest().start();
new FirstThreadTest().start();
}
}
}
}
上述代码中Thread.currentThread()方法返回当前正在执行的线程对象。GetName()方法返回调用该方法的线程的名字。
- 通过Runnable接口创建线程类
(1)定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
(2)创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
(3)调用线程对象的start()方法来启动该线程。
- 通过Callable和Future创建线程
(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
(3)使用FutureTask对象作为Thread对象的target创建并启动新线程。
(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
下面具体整理一下继承Callable和Runnable的两种创建线程的方法。
Runnable封装一个异步运行的任务,可以把它想想成为一个没有参数和返回值的异步方法;
Callable与Runnable方法类似,但是有返回值。
Future保存异步计算的结果。
FutureTask包装器是一种非常便利的机制,可将Callable转换成Future和Runnable,同时实现二者的接口。
4. Java中的克隆
为什么要克隆:需要使用一个对象在某个时刻的状态(比如某个时刻对象中已经被赋值的属性),为了能够保存当时对象的状态,Java出现了克隆的操作。
操作:首先需要实现Cloneable接口,然后重写clone()方法即可。Cloneable接口是一个空接口,它的作用是做标记;clone()方法是Object类中的protected方法,也是一个native方法(本地方法,和平台有关,native方法一般可移植性不高,JVM中运行时在本地方法区,native方法主要用于加载文件和动态链接库)。
深克隆vs浅克隆vs多层克隆:
深克隆:当拷贝的对象中含有引用类型属性时,我们在重写clone()方法时除了调用父类中的clone()方法,也需要调用引用类型的clone()方法,这样才能保证克隆后的对象跟原对象完全相互独立,没有任何关联。
首先修改引用类型,让它能够进行克隆:
public class Student implements Cloneable{
private String name;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
然后重写本类的clone()方法:
public class School implements Cloneable {
private int year;
private Student student;
@Override
protected Object clone() throws CloneNotSupportedException {
School school = (School) super.clone();
school.student = (Student) student.clone();
return school;
}
}
这样就能够实现对School对象的深克隆。
深克隆适用于对含有引用类型属性的对象的克隆。
浅克隆:如果重写时没有调用引用类型的clone()方法,只是调用父类中的clone()方法,那么两个对象虽然在内存地址上彼此独立,但是对象中的引用类型属性却指向同一个对象,当原对象中的引用类型属性发生变化时,克隆的对象也会跟着发生变化。
浅克隆适用于对只包含原始数据域或者不可变对象域(比如String类型)的克隆。
public class Car implements Cloneable {
private String name;
public Car(String name){
this.name = name;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone(); //只调用了父类的clone()方法,没有调用引用类型的clone()方法
}
public static void main(String[] args) {
try {
Car c1 = new Car("Benz");
Car c2 = (Car) c1.clone();
System.out.println(c1 == c2); //false
System.out.println(c1.name.equals(c2.name)); //true
System.out.println(c1.name == c2.name); //true
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
这段代码实现了对Car对象的一个浅克隆,重写的clone()方法中只是返回了Car的父类clone()方法,String是一个引用类型,因此System.out.println(c1.name == c2.name)
会输出为true,表示原对象跟克隆对象中的String属性指向了同一个字符串。但是String类型是不可变对象域,所以这段代码实现的克隆不会有任何问题。
原理图:
多层克隆:如果一个对象中包含了很多引用类型属性,或者内层引用类型的类里面又包含引用类型,使用clone()方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深克隆。
public class School implements Serializable{
private static final long serialVersionUID = 1L;
private Student student;
public School doClone() {
School school = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
school = (School) ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return school;
}
}
School中的引用类型Student也要实现序列化:
public class Student implements Serializable{
private String name;
}
这样就可以通过序列化来对对象进行深拷贝。
总结一下:需要使用一个对象在某个时刻的状态(比如某个时刻对象中已经被赋值的属性),为了能够保存当时对象的状态,Java出现了克隆的操作。克隆有两种方法:实现Cloneable接口,重写clone()方法;实现Serializable接口使用Java对象串行化的特性。
克隆分为浅克隆和深克隆,浅克隆没有克隆对象中引用的其他对象,适用于数值或其他基本类型,如果原对象和浅克隆的子对象不可变,则共享线程安全,如果子对象属于一个不可变类(如String),则也使用浅克隆;而深克隆要克隆所有的子对象。
clone方法是Object类的protected方法,必须重新定义clone为public才能允许所有方法克隆对象。
Cloneable是一个标记接口,不包含任何方法,唯一作用就是在类型查询中使用instanceof。
5. 线程池
使用线程池的原因:
a. 一个线程池中包含许多准备运行的空闲线程;
b. 线程池可以减少并发线程的数目。
创建线程池的方法:
newCachedThreadPool | 对于每个任务,如果有空线程可以用,立即让它执行任务,如果没有可用的空闲线程,则创建一个新线程。 |
newFixedThreadPool | 构建一个具有固定大小的线程池,如果提交的任务数目多于空闲的线程数目,那么把得不到服务的任务放到队列中。当其他任务完成后再运行它们。 |
newSingleThreadPool | 退化了的大小为1的线程池:由一个线程执行提交的任务,一个接一个。 |
newScheduledThreadPool | 用于预定执行而构建的固定线程池,替代java.util.Timer。 |
newSingleThreadScheduledExecutor | 用于预定执行而构建的单线程的“池”。 |
6. 在一个单独线程中执行一个任务的简单过程:
- 将任务代码移到实现了Runnable接口的类的run方法中。
public interface Runnable{
void run();
}
- 由Runnable创建一个Thread对象
Thread t = new Thread(r);
- 启动线程
t.start();
run()和start()区别:
Thread类或Runnable类的run方法,只会执行一个线程中的任务,而不会启动新的线程;Thread.start()方法将创建一个执行run方法的新线程。
7. 中断线程:
没有可以强制线程中断的方法,但interrupt方法可以用来请求终止线程。党对一个线程调用interrupt方法时,线程的中断状态将被置位。被中断的线程可以决定如何响应中断。
8. 线程状态:
New(新创建),Runnable(可运行),Blocked(被阻塞),Waiting(等待),Timed waiting(计时等待),Terminated(被终止)
New | 用new操作符创建一个新线程,如new Thread,该线程还没有开始运行。 |
Runnable | 一旦调用start方法,线程处于runnable状态。一个可运行的线程可能正在运行也可能没有运行,这取决于操作系统给线程提供运行的时间。(抢占式调度系统:时间片)(协作式调度系统:调用yield或者被阻塞或等待时,才失去控制权)。 |
Blocked | 一个线程试图获取一个内部对象锁,而该锁被其他线程持有,则该线程进入阻塞状态。当所有其它线程释放该锁,并且县城调度器允许本线程持有它时,该线程编程非阻塞状态。 |
Wait | 当线程等待另一个另一个线程通知调度器的一个条件时,自己进入等待状态。 |
Timed Waiting | 等待方法有超时函数,调用它们导致线程进入计时等待状态。 |
Terminated |
a. 因为run方法正常退出而自然死亡; b. 因为一个没有捕获的异常终止了run方法而意外死亡。 |
9. 同步和异步:
同步交互:指发送一个请求,需要等待返回,然后才能够发送下一个请求,有个等待过程;
异步交互:指发送一个请求,不需要等待返回,随时可以再发送下一个请求,即不需要等待。
区别:一个需要等待,一个不需要等待,在部分情况下,我们的项目开发中都会优先选择不需要等待的异步交互方式。
10. 锁对象:
有两种机智防止代码块受并发访问的干扰。
- synchronized关键字:
- ReentrantLock类:
myLock.lock(); //a ReentrantLock object try{ critical section } finally{ myLock.unlock(); //make sure the lock is unlocked even if an exception is thrown }
这一结构确保任何时刻只有一个线程进入临界区(一个访问共用资源的程序片段,而这些共用资源又无法同时被多个线程访问的特性)。一旦一个线程封锁了对象,其他任何线程都无法通过该lock语句。当其他线程调用lock时,它们被阻塞,知道一个线程释放锁对象。
ReentrantLock():构建一个可以被用来保护临界区的可重入锁。
ReentrantLock(boolean fair):构建一个带有公平策略的锁。一个公平锁偏爱等待时间最长的线程,因而大大降低性能。
相关文章
- 暂无相关文章
用户点评