1.final、static、this、super 关键字总结
final关键字主要用在三个地方:变量、方法、类。
对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便成为常量;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象,值可以改变。
当用final修饰一个类时,表明这个类不能被继承,成为最终类。final类中的所有成员方法都会被隐式地指定为final方法。
使用final方法的原因有两个:第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。类中所有的private方法都隐式地指定为final。
static 关键字主要有以下四种使用场景:
- 修饰成员变量和成员方法: 被 static 修饰的成员属于类,随着类的加载而加载,被类中所有对象共享,可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量,静存放在 Java 内存区域的方法区。
- 静态代码块: 静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—>非静态代码块—>构造方法)。 该类不管创建多少对象,静态代码块只执行一次.
- 静态内部类(static修饰类的话只能修饰内部类): 静态内部类与非静态内部类之间存在一个最大的区别: 非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:1. 它的创建是不需要依赖外围类的创建。2. 它不能使用任何外围类的非static成员变量和方法。
- 静态导包(用来导入类中的静态资源,1.5之后的新特性):
import static这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法。
this关键字用于引用类的当前实例。 例如:
class Manager {
Employees[] employees;
void manageEmployees() {
int totalEmp = this.employees.length;
System.out.println("Total employees: " + totalEmp);
this.report();
}
void report() { }
}在上面的示例中,this关键字用于两个地方:
- this.employees.length:访问类Manager的当前实例的变量。
- this.report():调用类Manager的当前实例的方法。
此关键字是可选的,这意味着如果上面的示例在不使用此关键字的情况下表现相同。 但是,使用此关键字可能会使代码更易读或易懂。
super关键字用于从子类访问父类的变量和方法。 例如:
public class Super {
protected int number;
protected showNumber() {
System.out.println("number = " + number);
}
}
public class Sub extends Super {
void bar() {
super.number = 10;
super.showNumber();
}
}在上面的例子中,Sub 类访问父类成员变量 number 并调用其其父类 Super 的 showNumber() 方法。
使用 this 和 super 要注意的问题:
- 在构造器中使用
super()调用父类中的其他构造方法时,该语句必须处于构造器的首行,否则编译器会报错。另外,this 调用本类中的其他构造方法时,也要放在首行。 - this、super不能用在static方法中。
2. Java 中的异常处理?
Java中异常来自于同一一个父类-----java.lang包中的 Throwable类。
Throwable类下有两个重要的子类:Exception(异常) 和 Error(错误) ,二者都是 Java 异常处理的重要子类,各自都包含大量子类。
关系图如下:

Error(错误):是程序无法处理的错误,Exception(异常):是程序本身可以处理的异常
异常处理总结try 块: 用于捕获异,其后可接catch块,如果没有catch块,则必须跟一个finally块。 catch 块: 用于处理try捕获到的异常。 finally 块: 无论是否捕获或处理异常,finally块里的语句都会被执行。当在try块或catch块中遇到return 语句时,finally语句块将在方法返回之前被执行。 (注意,返回值覆盖的问题)
3. 简述程序、进程、线程,以及他们之间关系是什么?
程序是为完成特定任务,用某种语言编写含有指令和数据的集合,存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码、静态的对象。
进程是正在运行的程序或者说是程序的一次执行过程,一个动态有新生、执行、销毁的过程。程序是静态的,进程是动态的进程作为资源的分配单位,每一个进程都会有自己独有的数据资源,系统会在程序运行时为每一个进程分配不同的内存区域。
线程是进程的进一步细化,是进程内部的一条执行路径。线程作为调度和执行的单位,每一个线程拥有独立的运行栈和程序计数器。同一个进程下是允许多个线程的,这些线程共享进程的数据资源(存在安全隐患,在之后Jvm篇中(如果有的话)将会详细解释为什么产生安全隐患,怎么解决)。
4. 线程的调度?
时间片轮转;同优先级采用先到先服务;
抢占式:优先级高线程抢占CPU;
5. 线程的生命周期?
Thtread.State类中定义五种状态:新建、运行、阻塞、等待、死亡。
新建:一个Thread类及其子类对象声名或者创建时,新生线程对象处于新建状态;
运行:线程获得cpu资源,run()方法定义线程的操作和功能;
阻塞:线程被人为挂起,或者执行输入输出操作,线程主动让出cpu中断当前操作;
等待:新建的线程状态被start后或者被中断阻塞资源条件具备,等待cpu分配;
死亡:线程完成自己任务或者被提前强制中断或者发生异常。
6. 怎么保证线程安全?
什么是线程安全问题:
多线程执行不确定性导致引起执行结果不稳定;多线程对共享资源变量的操作,会造成操作原子性的破坏,无法保证数据的一致性。
解决方向:要么保证线程对资源操作的原子性;要么保持数据的一致性。
同步监视器:俗称锁可以是任何类对象,必须保证所有线程对同一把锁操作(锁唯一性)。
synchronized (同步监视器){同步代码块}
Lock锁
(未完待续。。。)
7. 死锁?
相互占有资源,同时等待对方释放资源,若无外力破坏,永久等待。
/** * @author moan
* @date 2019/10/26
*/
public class ThreadDemo {
public static void main(String [] args){
StringBuffer sb = new StringBuffer();
StringBuffer sb1= new StringBuffer();
new Thread(){
@Override
public void run() {
synchronized (sb) {
sb.append("m");
sb1.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (sb1) {
sb.append("n");
sb1.append("2");
System.out.println(sb);
System.out.println(sb1);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (sb1) {
sb.append("x");
sb1.append("3");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (sb) {
sb.append("y");
sb1.append("4");
System.out.println(sb);
System.out.println(sb1);
}
}
}
}).start();
}
}8. synchronized与lock的异同?
同步,解决线程安全问题
synchronized 同步代码块、同步方法执行完,自动释放同步监视锁;lock需要自己手动开启释放;
9.wait、notify、notifyAll总结
wait():执行此方法,当前线程进入阻塞状态,并释放同步监视器锁;
notify():执行此方法,会唤醒wait的一个等待线程,按照优先级策略来唤醒多个线程阻塞中的某一个;
notifyAll():执行此方法,会唤醒所有的等待线程。
出现在同步代码块和同步方法中,实现线程之间的通信;当前对象调用这三个方法。
10.sleep()和wait()的异同?
都会让当前线程进入到阻塞状态
位置不同,Object类中声明wait(),Thread类中声明sleep();
调用要求不同,wait()需要在同步代码块或者同步方法中调用,sleep()可以在任何需要调用的地方使用;
释放同步监视器,wait()在同步代码块或者同步方法中,因此有同步监视器锁,会释放同步监视器锁;若sleep用在同步代码块或者同步方法中,并不会释放同步监视器锁。11.创建线程有几种方式?
一:继承Thread类,子类重写run()方法;
步骤:
1.定义类继承Thread类;
2.目的是复写run方法,将要让线程运行的代码都存储到run方法中;
3.通过创建Thread类的子类对象,创建线程对象;
4.调用线程的start方法,开启线程,并执行run方法;
注意:为什么没有直接执行run()?
调用start方法,启动当前线程。二:实现Runable接口,重写run()方法,将功能代码放到方法内;
步骤:
1.定义类实现Runable接口;
2.覆盖接口中的run方法(用于封装线程要运行的代码);
3.通过Thread类创建线程对象;
4.将实现了Runnable接口的子类对象作为实际参数传递给Thread类中的构造函数;
5.调用Thread对象的start方法。开启线程,并运行Runnable接口子类中的run方法。三:实现Callable 接口,利用FutureTask;
步骤:
1.定义类实现Callable接口;
2.覆盖接口中的call方法;
3.通过Thread类创建线程对象;
4.将实现Callable接口的子类创建线程对象;
5.新建FutureTask对象,将线程对象放入构造中;
6.将FutureTask对象传入线程。调用Thread对象的start方法。开启线程,并运行Callable接口子类中的call方法。四:利用线程池,创建线程。
步骤:
1.定义类实现Runable接口;
2.覆盖接口中的run方法(用于封装线程要运行的代码);
3.通过Executors类创建线程对象;4.调用Executors类创建线程对象的execute()方法,将子类对象作为实参传入;
5.调用shutdown(),关闭线程池。注意:后两种在jdk5 以后就已经声明
12.线程池线程有什么好处?
1.节省资源开销(不需要新建线程,可重复利用池中的线程);
2.提高响应速度(减少创建线程时间);
3.便于管理线程。