java面试题

1、JDK 和 JRE 有什么区别?

jdk中包含了jre,jdk是java开发工具包,jre是java运行环境。

2、什么是面向对象?

面向对象注重的是完成一个功能的参与者,和参与者各自的功能是什么。

面向对象的三大特性:

封装:隐藏内部细节并向外部暴露响应的接口,让外界不能修改我的内部逻辑。比如实体类、

mybatis都用到了封装的思想。

继承:通过extends关键字,子类可以继承父类,拥有父类公开的方法和属性,并且可以开展自己

的属性和方法。

多态:编译看左边,运行看右边,Diamnal d = new Dog() 左边是父类,右边是子类就是多态。创

建集合也是多态,比如List list=new ArrayList()。实现多态的三个必要条件:继承、子类重写父类

中的方法、父类引用指向子类对象。

3、重写和重载的区别

重写发生在类和子类中,子类重写父类中的方法,方法名和参数列表必须相同,抛出的异常范围要

小于父类,修饰符的访问权限要大于等于父类。

重载发生在一个类中,方法名必须相同,参数,参数类型、参数个数、参数顺序不同,返回值和修

饰符可以不同。只是返回值不同,不能代表发生了重载。

4、四种权限修饰符的范围

public:当前类、子类、当前包和其他包下都可以访问,是最高的权限;

protected:当前类、子类、当前包下可以访问,其他包不可以访问;

默认:当前类、本包可以访问,子类何其他包不可以访问

private:只有在当前类下可以访问

java面试题_第1张图片

5、抽象类和接口有什么异同?

(1)抽象类使用abstract修饰,接口使用interface修饰;

(2)抽象类只能单继承,接口可以多实现;

(3)抽象类存在构造函数,接口不存在构造函数,抽象类存在构造函数也不能被实例化;

(4)抽象类中的方法可以用public、protected、default修饰,接口必须用public修饰。

6、如果Try{}语句中存在return语句,那finally{}中的代码会不会被执行?

会执行,且在return后执行。

7、简述final、finally、finalize的区别

final是java中的关键字,可以修饰类、方法和属性;finally是跟在try...catch后面的,是一定会执行

的代码,常常用来释放资源;finalize是object中定义的方法,重写finalize方法可以整理系统资源和

执行其他清理工作,由垃圾收集器调用。

8、Java 中,throw 和 throws 有什么区别

throw用于在方法内部抛出一个新的异常(Error或者Exception)对象,自己抛自己处理。而throws

用于在方法声明时,向上一个方法调用者抛出异常,由上一个方法调用者去处理异常。

9、JDK1.8中新增的新特性

  • Lambda表达式;
  • 函数式接口
  • 在接口中可以定义静态方法和默认方法;
  • 新增了一些稳定的时间和日期的API,比如Data、Time API等;

10、常见的异常报错

  • 401:Unauthorized 客户试图未经授权访问受密码保护的页面
  • 403:forbidden,资源不可用
  • 404:notFound,找不到这个资源
  • 405:MethodNotAllowed,请求方法对指定资源不适用
  • 408: RequestTimeout,在服务器许可的等待时间内,客户一直没有发出任何请求。客户可以在以后重复同一请求。
  • 500:InternalServerError,服务器遇到了意料不到的情况,不能完成客户的请求。

11.常见异常

throwable,又分为error异常和exception异常

exception又包含运行时异常和编译时异常

编译时异常:IOException,FileNotFoundException,ClassNotFoundException:

运行时异常:NullPointerException,ArrayIndexOutOfBoundsException,ClassCastException

NumberFormatException,ArithmeticException等

12、Object中的常用方法

wait()、notify()、notifyAll()、toString()、equals()、hashcode()、getClass()、finalize()

13、java创建对象的四种方法

  • 使用new关键字调用对象的构造器
  • 使用java反射的newInstance方法
  • 使用object的clone()方法
  • 使用对象流ObjectInputStream的readObject()方法来读取序列化对象

14、short s1 = 1; s1 = s1 + 1;有什么错? short s1 = 1; s1+=1;有什么错?

在S1+1运算时会自动提升表达式为int类型,如果再将它赋予short类型,会出现类型转换异常,而

+=是java的运算符,会做特殊处理,能正常编译

15、int和Interger有什么区别?

  • Interger是int的包装类,而int是java的基本类型
  • Interger默认值为null,int默认值为0
  • Interger实际上是对象的引用,当new一个对象时实际是生成一个指针指向此对象,int是直接存储该数据
  • Interger必须实例化后才能使用,int不用
  • 当比较int和Interger的值且两值相同时为true,无论integer是否是new的,进行了自动装箱或拆箱
  • 不是new出来的两个Interger对象,当他们的值在-128~127内相同时,比较结果为true(会进行缓存)
  • 两个new出来的对象比较时,即使值相同,比较也为false,因为他们是两个不同对象,内存地址不同,
  • 非new生成的Interger对象和new生成的对象值相等进行比较时,结果为false,以为非new生成的对象是存储在java常量池中的,而new生成的对象存储在堆中,内存地址不同

16、static

static修饰变量,该变量随着类的加载而初始化,内存中只有一个,jvm只为它分配一次内存,所用

类共用静态变量;

修饰方法,在类加载的时候就存在,不依赖任何实例;

修饰代码块,在类加载完就会执行代码块中的类容;

父类静态代码块->子类静态代码块->父类非静态代码块->父类构造函数->子类非静态代码块->子类构造函数

17、是否可以在static环境中访问非static变量?

不可以,因为static变量是属于类的,在类加载的时候就被初始化了,这时候非静态变量并没有加

载,故非静态变量不能访问。

18、equals和==的区别?

==比较基本数据类型时比较的是值,比较引用数据类型时比较的是地址,equals比较基本数据类型

时比较的是值,比较引用数据类型时,如果重写了equals,比较的是值,没有重写,比较的就是地

址。

19、comparable和comparator接口的区别

Integer、String等这些基本类型的JAVA封装类都已经实现了Comparable接口,这些类对象本身就

支持自比较,直接调用Collections.sort()就可以对集合中元素的排序,无需自己去实现Comparable

接口。

而有些自定义类的List序列,当这个对象不支持自比较或者自比较函数不能满足你的要求时,你可

以写一个比较器来完成两个对象之间大小的比较,也就是指定使用Comparator(临时规则排序,

也称作专门规则排序),如果不指定Comparator,那么就用自然规则排序,这里的自然顺序就是

实现Comparable接口设定的排序方式

java面试题_第2张图片

 实现方式,comparable:

package cn.lxx.test;
 
public class User implements Comparable {   //该类实现Comparable接口,参数为User
    private String name;
    private int age;
    private boolean sex;
 
    public User() {
    }
 
    public User(String name, int age, boolean sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
 
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", sex=" + sex +
                '}';
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public int getAge() {
        return age;
    }
 
    public void setAge(int age) {
        this.age = age;
    }
 
    public boolean isSex() {
        return sex;
    }
 
    public void setSex(boolean sex) {
        this.sex = sex;
    }
 
    /**
     * 重写compareTo方法,实现年龄升序,年龄相同则姓名升序
     **/
    @Override
    public int compareTo(User user) {
        if (this.age == user.age) return this.name.compareTo(user.name);
        return this.age - user.age; //将this想像成一排不变的对象(已经按照要求排好序的),而User就是当前要插入的对象,只有user属性小于this属性才插入从而升序,个人理解,希望有所帮助
    }
}
 
class Test {
    public static void main(String[] args) {
        User user1 = new User("ake", 25, true);
        User user2 = new User("reo", 24, false);
        User user3 = new User("fg", 24, false);
        List list = new ArrayList();
        list.add(user1);
        list.add(user2);
        list.add(user3);
        Collections.sort(list);
        System.out.println("Comparable:" + list);
    }

实现comparator:

 

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
 
public class User implements Comparator {
    private String name;
    private int age;
    private boolean sex;
 
    public User() {
    }
 
    public User(String name, int age, boolean sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
 
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", sex=" + sex +
                '}';
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public int getAge() {
        return age;
    }
 
    public void setAge(int age) {
        this.age = age;
    }
 
    public boolean isSex() {
        return sex;
    }
 
    public void setSex(boolean sex) {
        this.sex = sex;
    }
 
    @Override
    public int compare(User o1, User o2) {
        return o1.age-o2.age;
    }
}
class Test{
    public static void main(String[] args) {
        User user1=new User("dingli",25,true);
        User user2=new User("huxiaojuan",24,false);
        User user3=new User("xxx",24,false);
        List list=new ArrayList();
        list.add(user1);
        list.add(user2);
        list.add(user3);
        Collections.sort(list, new User()); //类实现了的Comparator能满足需求
        System.out.println("类自身实现Comparator:"+list);
        //现在我想要按照名字升序,显然类中实现的不能满足要求,于是可以在类外自己实现想要的比较器
        Collections.sort(list, new Comparator() {
            @Override
            public int compare(User o1, User o2) {
                return o1.getName().compareTo(o2.getName()); //按照名字升序
            }
        });
        System.out.println("匿名内部类方式:"+list);
        //由于Comparator接口是一个函数式接口,因此根据jdk1.8新特性,我们可以采用Lambda表达式简化代码
        Collections.sort(list,(u1,u2)->{return u1.getName().compareTo(u2.getName());});
        System.out.println("Lambda表达式方式:"+list);
    }
}

21.内部类

静态内部类(定义在类中,有static修饰)

静态内部类可以访问外部类的静态属性

java面试题_第3张图片

成员内部类(定义在类中),可以访问外部类的成员属性,即使是private修饰的

java面试题_第4张图片

局部内部类(定义在方法中)可以任意访问该方法中的局部变量

java面试题_第5张图片

匿名内部类()声明在方法中,抽象类或者接口  本不可以直接创建对象,可使用匿名内部类方法直接创建

java面试题_第6张图片

java面试题_第7张图片

20、Static修饰的方法,为什么不能重写?

 因为Static修饰的方法是编译时静态绑定的,而重写是在运行时动态绑定,两个不是一个概念

21、在Java中,如果将小范围的值赋值给大范围的变量那么它就可以实现自动类型转换

自动类型转换的顺序:byte -> short -> int -> long -> float ->double

22、序列化与反序列化

序列化和反序列化_JFS_Study的博客-CSDN博客

23、Java拼接字符串(包括空字符串)的多种方法

(1)使用+进行拼接,如果有空字符串,null值也会作为字符串拼接在其中。

(2)使用String.join() 静态方法拼接z,也会带有null值输出。如下

String[] values = {"I ", "want to ", "splicing ", "some ", "charactors ", null};
String result = String.join("", values);
 
// 结果
I want to splicing some charactors null

(3)使用String.concat()

(4)使用StringBuilder

StringBuilder 类提供了很多有用且方便的 String 构建方法。其中比较常用的是 append() 方法,使

用 append() 来拼接字符串,同时结合 nullToString() 方法来避免 null 值。

String[] values = {"I ", "want to ", "splicing ", "some ", "charactors ", null};
StringBuilder result = new StringBuilder();
for (String value : values) {
    result = result.append(nullToString(value));
}
 
// 结果
I want to splicing some charactors

(5)使用StringJoiner

tringJoiner 类是 Java 8以后加入的一种新的方法。

StringJoiner 类提供了更强大的字符串拼接功能,不仅可以指定拼接时的分隔符,还可以指定拼接

时的前缀和后缀,这里我们可以使用它的 add()方法来拼接字符串。同样的会用 nullToString() 方法

来避免 null 值。

String[] values = {"I ", "want to ", "splicing ", "some ", "charactors ", null};
StringJoiner result = new StringJoiner("");
for (String value : values) {
    result = result.add(nullToString(value));
}
 
// 结果
I want to splicing some charactors 

(6)使用Streams.filter()

Stream API 是 Java 8 引入的功能强大的流式操作类,可以进行常见的过滤、映射、遍历、分组、

统计等操作。其中的过滤操作 filter 可以接收一个 Predicate 函数,Predicate 函数接口同 Function

(opens new window)接口一样,是一个函数式接口,它可以接受一个泛型 参数,返回值为布尔类

型,Predicate 常用于数据过滤。

因此,可以定义一个Predicate 来检查为 null 的字符串,然后传递给 Stream API 的 filter() 方法。

最后再使用 Collectors.joining() 方法拼接剩余的非 null 字符串。

String[] values = {"I ", "want to ", "splicing ", "some ", "charactors ", null};
String result = Arrays.stream(values).filter(Objects::nonNull).collect(Collectors.joining());

24、String类的常用方法

int length() :返回某字符串的长度

char charAt(int index):返回某索引出的字符

boolean isEmpty() :判断是否是空字符串

String toLowerCase() :将字符串全部转为小写

String toUpperCase() :将字符串全部转为大写

String trim() :去除字符串前后端空格

boolean equals(Object obj):判断两个字符串内容是否相同

boolean equalsIgnoreCase(String anotherString):判断两个字符串内容是否相同,忽略大小写

String concat(String str):将指定字符串拼接到此字符串的后面

int compareTo(String str) :比较两个字符串大小

String substring(int beginIndex):截取字符串,从beginIndex索引出截取到最后一个字符串

String substring(int beginIndex,int endIndex):截取字符串,从beginIndex索引出截取到endIndex索引处,不包含 endIndex。

boolean endsWith(String sufix):测定字符串是否已指定字符串作为后缀

boolean startsWith(String sufix):测定字符串是否已指定字符串作为前缀

boolean startsWith(String sufix,int,toffset):测定字符串以指定索引开始字符串是否已指定字符串作为前缀

boolean contains(char c):测定字符串中是否包含指定的char

int indexof(String str):返回指定字符串在此字符串中第一次出现位置的索引

int indexof(String str,int fromIndex):返回指定字符串在此字符串中第一次出现位置的索引,从fromIndex开始。

int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索

注:indexOf和lastIndexOf方法如果未找到都是返回-1

String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所 oldChar 得到的。

String replace(CharSequence target, CharSequence replacement):使用指定的字面值替换序列替换此字符串所匹配字面值目标序列的子字符串。

String replaceAll(String regex, String replacement):使用给定的 replacement 替换此字符串所匹配给定的正则表达式的子字符串。

String replaceFirst(String regex, String replacement):使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。

boolean matches(String regex):告知此字符串是否匹配给定的正则表达式。

String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。

String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。

25、类型转换

String --> 基本数据类型、包装类:调用包装类的静态方法:parseXxx(str)

基本数据类型、包装类 --> String:调用String重载的valueOf(xxx)

 @Test
    public void test1(){
        String str1 = "123";
//        int num = (int)str1;//错误的
        int num = Integer.parseInt(str1);

      
        String str2 = String.valueOf(num);//"123"
        String str3 = num + "";

        System.out.println(str1 == str3);
    }

26、String、String Buffer、String Build的区别

String是用final修饰的,不能被改变,也不能被继承,每次对String的操作都会产生新的对象。

String Buffer、String Build都是对原对象进行操作,是可变的,并且String Buffer中的方法是使用

synchronize修饰的,是线程安全的,执行效率:String>String Build>String Buffer

27、StringBuffer、StringBuilder中的常用方法

StringBuffer reverse():反转字符串

StringBuffer append(String str):在尾部添加字符串

StringBuffer delete(int start, int end) :删除字符串中索引在[start,end)区间的字符

int charAt(int index):查询某个字符

StringBuff insert(int offset, String str):在offset插入字符str

28、什么是线程?

线程是操作系统能够进行运算调度的最小单位,被包含在进程之中,是进程中的实际运作单位。一

条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不

同的任务。

29、多线程有几种实现方式?

(1)继承Thread类重写run方法:

public class main(){
     public static void main(String[] args) {
        new Thread1().start();
    }
}
class Thread1 extends Thread{
    @override
    public void run(){
        sout("");    
    }
}

(2)实现runnable接口,重写run方法,然后使用thread类来包装:

public class main(){
     public static void main(String[] args) {
         Runnable1 runnable = new Runnable1();
        new Thread(runnable).start();
    }
}
class Runnable1 implement Runnable{
    @override
    public void run(){
        sout();    
    }
}

(3)实现callable接口 重写call方法,然后包装成FutureTask,再包装Thread:

public class main(){
     public static void main(String[] args) {
         Callable1 callable1 = new Callable1();
        FutureTask futureTask = new FutureTask<>(callable1);
        new Thread(futureTask)).start();
    }
}
class Callable1 implement Callable{
    @override
    public Interger call(){
        sout();    
    }
}

30、Thread和Runnable的区别

Thread是类只能单继承,而Runnable类是接口,可以多实现,使用runnable容易实现资源共享,可以避免java中资源共享的局限性,而且线程池中只能放入实现runnable和callable接口的类,不能放继承Thread类的类

31、Runnable和callable的区别

(1)实现callable接口的线程可以返回执行结果,实现runnable接口的线程不可以返回执行结果

(2)Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛;

32、Thread类中的常用的方法

start():启动当前线程,调用当前线程的run方法

run():通常要重写Thread类中的此方法,将创建的线程要执行的操作声明在这个方法中

currentThread():返回正在被执行的线程的信息

public class Run1{
    public static void main(String[] args){
        System.out.println(Thread.currentThread().getName());
    }
}

getName():获取当前线程的名称

setName():设置当前线程的名称

yield():释放当前cpu执行权,让当前线程回到就绪状态,以允许相同优先级的其他线程获得运

行机会,但是让步的线程可能再被线程调度程序再次选中

join():是其他线程进入阻塞转态直到自己线程执行完成,可以保证线程的执行顺序

public class ThreadStartOrderTest {
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(()->{
            System.out.println("this is thread1");
        });
        
        Thread thread2 = new Thread(()->{
            System.out.println("this is thread2");
        });
        
        Thread thread3 = new Thread(()->{
            System.out.println("this is thread3");
        });
        
        thread1.start();
        thread1.join();
        thread2.start();
        thread2.join();
        thread3.start();
        thread3.join();
    }
}

sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前线程是阻塞状态。

isAlive():判断当前线程是否存活

33、线程有几种状态,以及它的生命周期。

线程通常来说有五种状态,创建、就绪、运行、阻塞和死亡状态。

生命周期

(1)创建线程

(2)其他线程调用了start()方法,线程进入就绪状态

(3)就绪状态的线程得到了cpu的执行权,变为运行状态

(4)线程可能因为某种原因停止了cpu的使用权,进入了阻塞状态,阻塞状态的线程只有先进入就绪状态,才有机会转到运行状态

(5)线程执行完毕,或者因为异常退出了run()方法,该线程结束,死亡。

34、什么是线程安全

如果由多个线程在同时运行,而这些线程可能会同时运行这段代码,程序每次运行结果和单线程运行的结果一样,而且其他的变量的值也是和预期的一样,就是线程安全的。

35、怎么解决线程安全问题?

通过同步机制解决线程安全问题

(1)同步代码块

说明:

操作共享数据的代码,即为需要被同步的代码。

共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。

锁,任何一个类的对象,都可以充当锁。

要求:多个线程必须要共用同一把锁

补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器

在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器。

//线程类
 public class RunnableImpl implements Runnable {
    //定义一个多线程共享的票源
    private int ticket =100;
    //创建一个锁对象
    Object obj = new Object();
    //设置线程任务 卖票
    @Override
    public void run() {
        //使用死循环 让卖票操作重复执行
        while (true){
            //同步代码块
            synchronized (this){
                //先判断是否存在票
                if(ticket>0){
                    //提高安全问题出现的概率 让线程休眠
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //票存在,卖票ticket--
                    System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
                    ticket--;
                }
                if(ticket ==0){
                    return ;
                }
            }
        }
    }
}
//测试方法
 public class Test {
    public static void main(String[] args) {
        //创建线程任务对象
        RunnableImpl ticket = new RunnableImpl();
        //创建3个窗口对象
        Thread t1 = new Thread(ticket, "窗口1");
        Thread t2 = new Thread(ticket, "窗口2");
        Thread t3 = new Thread(ticket, "窗口3");
        //同时卖票
        t1.start();
        t2.start();
        t3.start();
    }

(2)同步方法

public class Ticket implements Runnable {
    private int ticket =100;
    /*
        卖票操作
     */
    @Override
    public void run() {
        //每个窗口卖票的操作
        //窗口永远打开
        while (true){
            sellTicket();
        }
    }
    //封装卖票方法
    public synchronized void sellTicket(){
        if(ticket>0){ //有票 可以卖
            //出票操作 用sleep模拟
            try {
                Thread.sleep(100);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            //获取当前线程对象的名称
            System.out.println(Thread.currentThread().getName()+"正在卖"+ticket--);
        }
    }
}

(3)锁

同步代码块/同步方法具有的功能lock都有,除此之外更强大,更体现面向对象。编写也更加简单。

Public void lock()  加同步锁

Public void unlock() 释放同步锁

 使用步骤:

  • 在成员位置创建一个RenntrantLock对象
  • 在可能出现安全问题的代码前调用一下lock方法获取锁
  • 在可能出现安全问题的代码后调用一下unlock方法释放锁。

36、我们执行main方法的时候,我们的虚拟机至少会运行几个线程?

两个线程,一个是main主线程,一个是垃圾回收机制

37、Lock 与 Synchronized 的区别

(1)lock是一个接口,而Synchronized 是java的一个关键字,、

(2)Synchronized在发生异常是会自动释放占用的锁,因此不会出现死锁;lock在发送异常时不会自动释放锁,必须手动释放锁,可能会发生死锁。

38、sleep 和 wait 的区别

相同点:两个方法都可以使线程进入阻塞状态

不同点:

  • sleep()来自Thread类,wait()来自Object类
  • sleep()不释放锁,而wait()会释放锁
  • sleep()可以在任何时候调用,而wait()只能在同步代码块或同步方法中调用(wait()释放锁,释放锁的前提是必须先获得锁,自身要有锁)

39、线程通信涉及到的三个方法

wait()

notify():唤醒一个被wait()的线程,如果有多个线程被wait(),优先唤醒优先级高的线程

notifyAll():唤醒所有被wait的线程

使用在同步代码块或同步方法中

40、什么是线程池?

线程池就是创建若干个可执行线程放入一个池中,需要的时候就获取,使用完也不用销毁再放入线程池中供下回调用,减少线程创建和销毁成本。

41、线程池的组成部分?

线程池管理器:用于创建线程池,销毁线程池,添加新任务。

工作线程:线程池中线程,可循环执行任务,在没有任务时处于等待状态。

任务队列:用于存放没有处理的任务,一种缓存机制。

任务接口:每个任务必须实现的接口,供工作线程调度任务的执行,主要规定了任务的开始和收尾工作,和任务的状态。

42、创建线程池的几个核心构造参数

corePoolSize:线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;如果当前线程数为 corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;

maximumPoolSize:线程数的上限;

keepAliveTime:线程空闲时的存活时间,即当线程没有任务执行时,继续存活的时间;默认情况下,该参数只在线程数大于 corePoolSize 时才有用;

unit:keepAliveTime的时间单位;

workQueue:用来保存等待被执行的任务的阻塞队列,且任务必须实现 Runable 接口,在 JDK 中提供了如下阻塞队列:

  • ArrayBlockingQueue:基于数组结构的有界阻塞队列,按 FIFO 排序任务;
  • LinkedBlockingQuene:基于链表结构的阻塞队列,按 FIFO 排序任务,吞吐量通常要高于ArrayBlockingQuene;
  • SynchronousQuene:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除

操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于 LinkedBlockingQuene;

  • priorityBlockingQuene:具有优先级的无界阻塞队列;

threadFactory:线程工厂。定义如何启动一个线程,可以设置线程名称,并且可以确认是否是后台线程等。

handler:线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了 4 种策略:

  • AbortPolicy:直接抛出异常,默认策略;
  • CallerRunsPolicy:用调用者所在的线程来执行任务;
  • DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
  • DiscardPolicy:直接丢弃任务;

当然也可以根据应用场景实现 RejectedExecutionHandler 接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。

43、线程池有哪些?

线程池接口是java.util.concurrent.ExecutorService,使用线程池对象的方法:

  Public Future submit(Runnable task) 获取线程池中的某一个线程对象,并执行,Futrue接口:用来记录线程执行任务完毕后产生的结果,线程池创建于使用。

(1)newSingleThreadExecutor:单线程池。

顾名思义就是一个池中只有一个线程在运行,该线程永不超时,而且由于是一个线程,当有多个任务需要处理时,会将它们放置到一个无界阻塞队列中(LinkedBlockingQueue)逐个处理,能保证所提交任务的顺序执行,实现代码如下:

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,
             new LinkedBlockingQueue

简单用例

public static void main(String[] args) throws ExecutionException,InterruptedException {
    // 创建单线程执行器
    ExecutorService es = Executors.newSingleThreadExecutor();
    // 执行一个任务
    Future future = es.submit(new Callable() {
        @Override
        public String call() throws Exception {
            return "";
        }
    });
    // 获得任务执行后的返回值
    System.out.println("返回值:" + future.get());
    // 关闭执行器
    es.shutdown();
}

2)newCachedThreadPool:缓冲功能的线程。

初始化一个可以缓存线程的线程池,默认缓存60s,一旦一个线程在60秒内一直处于等待状态时(也就是一分钟无事可做)则会被终止,线程数量可达到Integer的最大值,内部使用SynchronousQueue作为阻塞队列,其源码如下:

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,
                                      new SynchronousQueue());
}

(3)newFixedThreadPool:固定线程数量的线程池。

在初始化时已经决定了线程的最大数量,若任务添加的能力超出了线程的处理能力,则建立阻塞队列(linkedBlockingQueue)容纳多余的任务,其源码如下:

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue());
}

简单实例

Runnable实现类:
  public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("我要一个教练");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("教练来了:"+Thread.currentThread().getName());
        System.out.println("教我游泳,教完后,教练回到了游泳池");
    }
}

 测试类
public class ThreadPoolDome {
    public static void main(String[] args) {
        //创建线程池对象
        ExecutorService service = Executors.newFixedThreadPool(2); //包含2个线程
        //创建runnable示例对象
        MyRunnable r = new MyRunnable();
        //自己创建线程对象的方式
        //Thread thread = new Thread(r);
        //thread.start();
        //从线程池中获取线程对象,调用MyRunnable中的run()
        service.submit(r);
        //再获取一个线程,调用MyRunnable中的run()
        service.submit(r);
        //注意:submit方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭
        //关闭线程池  一般不做
        //service.shutdown();
    }
}

(4)newScheduledThreadPool()

特点:初始化的线程池可以在指定时间内周期性执行所提交任务,在实际业务场景中可以使用该线程池定期同步数据。

创建线程池对象步骤:

   1.创建线程池对象

   2.创建Runnable接口实现类对象(task)

   3.提交Runnable接口实现类对象,就会执行run方法

   4.关闭线程池(一般不做)

44、介绍集合框架

集合包含单列集合collection和双列集合map;collection包含list和set;list是无序可重复的元素,

set是有序不可重复的元素;

list常用的接口实现类有arraylist,linkedlist和vector;arraylist底层是动态数组实现的,linkedlist底

层是双向链表实现的,arraylist比linkedlist查询效率高,因为linkedlist需要移动指针从前往后依次

查找找,在添加和删除元素时,linkedlist比arraylist效率要高,因为arraylist需要改变数组类元素的

下标;arraylist初始容量为10,以1.5倍扩容,同时将原数组拷贝到新数组里面,vector和arraylist

基本相同,只是vector是以两倍扩容,底层用synchronized是用来修饰,保证了线程的安全。

set常用的实现类有HashSet,linkedhashset和treeset;hashset底层哈希表实现,无序且唯一,唯

一靠的是所存储元素的类型是否重写了equals和hashcode保证;linkedhashset底层是链表加哈希

表实现,无序且唯一,保证了元素的顺序和存储顺序相同;treeset有序且唯一,底层是二叉树实现

的。

map常用的实现类用hashmap,hashtable,linkedhashmap,currenthashmap和treemap;

hashmap底层是数组加链表加红黑树实现的,当数组长度大于64,链表长度大于8,转为红黑树,

在jdk1.8后,允许key和value为null,初始容量为16,以两倍扩容,扩展因子0.75,当容量达到最

大容量的75%就会触发扩容;

hashtable不允许存储key和value为null的值,初始容量为11,以两倍加1扩容,底层通过

synchronized修饰,保证了线程的安全;

treemap底层红黑树实现的,继承sortmap,对元素进行了排序;currenthashmap底层是数组加链

表加分段锁方式实现,分段锁继承lock锁,将全局加锁改成了局部加锁,提高了并发环境加的操作

速度,即内部拥有一个数组,数组的每个元素是一个链表,有包含一个lock锁。

45、collection和collections有什么区别?

collection是最基本的接口,有set和list两个子接口;collections是一个包装类,包含了操作集合的

静态方法,比如搜索、排序,是一个工具类,是服务于collection和map集合。

46、collections中常用的方法?

frequency(),sort(),max(),min(),replaceAll(),copy(),reverse(),shuffle(),swap()

47、collection中常用的方法

reverse(list):反转list集合中元素的数据;

shuffle(list):对list集合中的元素进行随机排序

sort(list):根据元素的自然顺序对list集合中的元素进行升序排序

sort(list,Comprator):根据Comprator指定的元素顺序对list集合中的元素进行排序

swap(list,int i ,int j):将list集合中位置i处的元素和j处的元素进行交换。

Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素

Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素

Object min(Collection)

Object min(Collection,Comparator)

int frequency(Collection,Object):返回指定集合中指定元素的出现次数

void copy(List dest,List src):将src中的内容复制到dest中

boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所旧值

48、数组和集合的区别

数组长度固定,无法扩容,只能存储一种数据类型;集合可以扩容,可以存储不止一种数据类型

49、Iterator是如何实现遍历的?

Iterator iterator = coll.iterator();
if(iterator.hasNext()){//判断是否还有下一个元素
    iterator.next()    //将指针下移,并获取到下一个元素
}

50、在判断语句里面执行iterator.remove()会抛出数据状态异常IllegalStateException。

51、list和map中的增删改查

list中的增删改查插:add(), remove() ,set(), get().

遍历的方式有iterater、增强for、普通for循环。

map中的增删改查:put(), remove(), put(), get()

长度查询:size()

52、遍历map的方式

遍历的方式有keyset、entryset、迭代器。

(1)ketSet方式

for(String key:map.keySet()){
Objectvalue=map.get(key);
sout(key+value);
}

(2)entrySet

for(Entry entey:map.entrySet){
    sout(entry)
}

(3)迭代器遍历

Iterator> it = map.entrySet().iterator() ;
while(it.hasNext())){
    sout(it.next);
}

53、普通线程和守护线程的区别

守护线程和普通线程是多线程编程中的两种不同类型的线程。

普通线程是一种常见的线程类型,它的运行不会影响整个程序的退出。当所有的普通线程执行完毕后,程序才会退出。

守护线程是一种特殊的线程类型,它的存在依赖于其他非守护线程。当所有的非守护线程执行完毕后,无论守护线程是否执行完毕,程序都会退出,并且守护线程会被强制终止。

守护线程通常用于在后台执行一些任务,比如定时任务、日志记录等。它们经常被用来处理一些不需要阻塞整个程序的任务。

需要注意的是,守护线程不能用于执行一些可能导致数据不一致或资源泄漏的任务,因为它们可能会在程序退出时被强制终止,从而无法完成清理工作。

总结来说,守护线程和普通线程在程序退出时的行为不同,守护线程会被强制终止,而普通线程不会。

你可能感兴趣的:(java)