万丈高楼平地起之Java基础三

237 阅读9分钟

 1.final、static、this、super 关键字总结

final关键字主要用在三个地方:变量、方法、类。

  1. 对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便成为常量;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象,值可以改变。

  2. 当用final修饰一个类时,表明这个类不能被继承,成为最终类。final类中的所有成员方法都会被隐式地指定为final方法。

  3. 使用final方法的原因有两个:第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。类中所有的private方法都隐式地指定为final。

static 关键字主要有以下四种使用场景:

  1. 修饰成员变量和成员方法: 被 static 修饰的成员属于类,随着类的加载而加载,被类中所有对象共享,可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量,静存放在 Java 内存区域的方法区。
  2. 静态代码块: 静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—>非静态代码块—>构造方法)。 该类不管创建多少对象,静态代码块只执行一次.
  3. 静态内部类(static修饰类的话只能修饰内部类): 静态内部类与非静态内部类之间存在一个最大的区别: 非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:1. 它的创建是不需要依赖外围类的创建。2. 它不能使用任何外围类的非static成员变量和方法。
  4. 静态导包(用来导入类中的静态资源,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方法中。

参考来源:gitee.com/SnailClimb/…

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.便于管理线程。