Java基础

1. 面向对象与面向过程

  • 面向过程(直接高效):

    • 注重于完成某件事的每一个步骤,将步骤封装成函数。
    • 打开洗衣机–>放衣服–>放洗衣粉–>清洗–>烘干
  • 面向对象(易于复用、拓展和维护):

    • 注重于完成某件事要参与的对象以及对象所需要完成的事情。
    • 人:打开洗衣机,放衣服,放洗衣粉
    • 洗衣机:清洗,烘干。
    • 概念:
      • 类:是对一类事物抽象的描述。
      • 对象:实际存在的该类事务的每个个体。
      • 成员变量:实例变量
      • 类变量:以static修饰的成员变量
      • 局部变量:方法的形参和方法或代码块中定义的变量
      • 代码块:本质上是一个只有方法体的方法,不是通过对象或类名显示调用,是隐式调用,在new这个类的时候运行初始化块,可多次加载。(先于构造器,用来初始化信息)。
      • 静态代码块:优先级最高,随着类的加载而加载,只加载一次。
  • 封装

    • 意义:明确的标识出允许外部使用的所有成员函数和数据项
    • 概念:内部细节对外部调用透明,外部调用无需修改或者关心内部实现,提高数据安全性,隐藏细节,提高代码重用性,利于程序拓展
    • 应用场景:
      • javabean中的get、set方法 (set方法中有自己的命名规则)。
      • mybatis框架:不关心数据库建立连接等操作,直接使用增删改查。
  • 继承

    • 概念:继承基类的方法,并做出自己的改变和拓展。子类共性的方法或者属性直接使用父类的,而不需要自己再定义,只需扩展自己个性化的。

    • 意义:减少代码冗余。直接编写新特性与父类不同的地方。

    • 必须调用父类构造器,默认无参,没有无参时,显示通过super()调用。

    • instanceof:

      A instanceof B
          //B是不是a的子类
      
  • 多态

    • 概念:基于对象所属类的不同,外部对同一个方法的调用,实际执行逻辑不同。
    • 意义:只需要更改对象不需更改引用,易于维护和拓展。
    • 举例:
      • 对象多态:父类引用指向子类对象(无法调用子类特有功能,只能是继承的)。
      • 方法多态:方法的重载与重写。

2. JDK,JRE,JVM三者的区别和联系

  • JDK:Java Develpment Kit (java开发工具)

    • jre
    • java工具:javac,java,jconsole
  • JRE:Java Runtime Environment (java运行时环境) 只运行

    • bin(jvm)
    • lib(类库)
  • JVM:Java Virtual Machine (虚拟机)解释.class文件转成机器码,不同系统对jvm进行选择安装

  • .java文件运行过程。

    • .java通过javac编译成.class文件
    • .class文件放到jvm上
    • jvm根据类库中的目录解释.class成机器码
    • 映射到操作系统执行

3. ==和equals的区别

  1. ==对比的是栈中的值

  2. 基本数据类型比的是变量值(栈中放i=20)

  3. 引用类型是堆中的内存对象的地址(栈中放堆中呢new出对象的地址)

  • eauals:object中默认也是采用==比较,通常会重写

  • 栈:当程序进入一个方法时,会为这个方法单独分配一块私属存储空间,用于存储这个方法内部的局部变量,当这个方法结束时,分配给这个方法的栈会被释放,这个栈中的变量也将随之释放。

    • 存放基本数据类型
    • 对象的引用
  • 堆:存放不在当前方法栈中的那些数据。

    • new出来的引用
    • 全局变量
  • String s = “123” 引用在栈中,“123”存放在常量池中

4.简述final的作用

  • 简述final的作用
    • 修饰类:类不可被继承
    • 修饰方法:方法不能被子类覆盖,但可以重载
    • 修饰变量:一旦赋值就不可以更改它的值
      • 修饰成员变量:
        • 修饰类(静态)变量:只能在静态化初始化块中指定初始值或者声明该类变量时指定初始值。
        • 修饰成员(普通)变量:可以在非静态化初始块声明该变量或者构造器中执行初始值。
      • 修饰局部(main方法中变量)变量:系统不会为局部变量初始化,局部变量必须由程序员显示初始化。因此可以在定义时指定默认值(后面代码不能对变量再赋值),也可以不指定默认值,而在后面代码使用时对变量赋初值(只一次)。
    • 修饰基本类型数据和引用类型数据
      • 基本数据类型:数值一旦初始化不能更改。
      • 引用类型:初始化后便不能再指向另一个对象,但引用值可变。(数组可改变元素值,不能指向另一个数组或null)
  • 为什么局部内部类和匿名内部类只能访问局部final变量
    • 在编译后会生成两个.class文件
    • 内部类和外部类同一级别,不会随着外部类方法运行完而回收。
    • 外部类方法结束时,局部变量会被回收,内部类就没办法引用局部变量,实际访问的是局部变量的”copy“,好像延长了局部变量的生命周期。需要保证copy的和本来的数据一样,否则在内部类中进行修改,局部变量中的值也得修改,此问题就无法解决。所以就将局部变量变成final,内部类就无法改变局部变量的值。

简述static的作用

  • 修饰成员方法:可以不需要实例化来修饰类成员的主要作用。(称为类方法)
  • 修饰成员变量:此变量成为成员变量,内存中只存一遍,无论哪个方法修改了此变量,此变量就会改变。只会在第一次new的时候初始化。
  • 修饰静态代码块:优先初始化,只会在第一次new的时候初始化。

5.String,StringBuffer,StringBuilder区别及应用场景

  • String:由final修饰的,不可变,每次操作都会产生新的String对象(造成内存浪费)。

  • StringBuffer:在原对象上操作,线程安全,方法都是由synchronized修饰的,不需要额外再加

  • StringBuilder:在原对象上操作,线程不安全

  • 性能:3>2>1

  • 频繁改变字符串使用后面两个。优先使用StringBuilder,多线程使用共享变量时使用StringBuffer。

6.重载和重写的区别

  • 重载:发生在同一个类中,方法名必须相同,参数类型不同,个数不同,顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。
  • 重写:发生在父子类中,方法名,参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于父类,访问修饰符范围大于等于父类,父类方法为private修饰则子类不能重写。

可变参数(实参可以有0个或者多个,最多可以有一个可变参数,多个参数时只能放在最后):

  • java5出现的新特性

  • 提高代码的重用性和维护性

  • 	修饰符	返回类型 方法名(参数类型...参数名){
    		
    	}
    
    

7.接口和抽象类的区别

  • 抽象类可以存在普通成员函数,接口中只能存在public abstract方法
  • 抽象类中的成员变量可以是各种类型的,接口中的成员变量只能是public static final的(接口可继承多接口,接口中是可以定义静态方法的,静态方法必须要有实现)
  • 抽象类只能继承一个,接口可以实现多个。
  1. 接口的设计目的:对类进行约束,约束行为的有无,不对如何实现进行限制。

  2. 抽象类的设计目的:减少代码复用,将子类存在差异化的特性进行抽象,交友子类去实现

    本质看抽象类,行为看接口

8. 单例设计模式

  • 设计模式:经过高度抽象化的在编程中可以被反复使用的代码设计经验的总结。

  • 单例设计模式:采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。

  • 应用场景:

    • 项目中只想多次调用该对象的方法时,为了提高效率
    • 如果创建多个对象,会导致逻辑错误时
  • 饿汉

    class Single{
     	//private,不能在类的外部创建该类的对象
         private Single() {} 
    	 //私有的,只能在类的内部访问
         private static Single onlyone = new Single();
     	//getSingle()为static,不用创建对象即可访问
         public static Single getSingle() {
    		return onlyone;
     	}
    }
     public class TestSingle{
    	public static void main(String args[]) {		
    		Single  s1 = Single.getSingle();      //访问静态方法
    		Single  s2 = Single.getSingle();
    		if (s1==s2){
    			System.out.println("s1 is equals to s2!");
    		}}}
    
    
  • 懒汉

    public class Lazy{
    private Lazy(){}
    //默认不会实例化,什么时候用什么时候new
    	private static Lazy lazy=null;
    public static synchronized Lazy getInstance(){
    if(lazy==null){
    lazy=new Lazy();
    }
    return lazy;
    }
    }
    

    区别:

    • 懒汉调用时实例化,饿汉在类加载时实例化。
    • 饿汉线程安全,懒汉线程不安全
    • 饿汉(没加锁)执行效率高
    • 饿汉相对来说占用内存

9. List和Set的区别

Collection下的两个接口

在 Java5 之前,Java 集合会丢失容器中所有对象的数据类型,把所有对象都当成 Object 类型处理;从 Java 5增加了泛型以后,Java 集合可以记住容器中对象的数据类型。

  • List:有序,按进入顺序保存对象,可重复,可多个null,可以使用iterator取出所有元素,再逐一遍历,还可使用使用get获取对应下标的元素

  • Set:无序,不能重复,只允许一个null,只能用iterator取出元素,不能下标获取。

10. hashCode与equals

  • hashCode:作用是获取哈希码,返回一个int整数,作用是获取对象在哈希表中的索引位置,hashcode()定义在JDK的Object.java中,java中任何类都包含hashCode()。哈希表存储的是键值对,特点是能根据”键“快速的检索出对应的值。

  • 存在的意义

    以”HashSet如何检查重复“来说明为什么要有hashcode

    • 对象加入hashset时,hashset会先计算对象的hashcode值来判断对象加入的位置,看该位置是否有值,没有的话,hashset会假设对象没有重复出现,如果有值会调用eauqls看是否值相同,相同则不会加入成功,不同则重新散列到其他位置,大大减少了equals的次数,相应就大大提高了执行速度。
  • equals:用来比较引用类型的值,不重写就是”==“用来比较栈中的值,可以根据需要重写。

    1. 对象相等,hashcode一定相同
    2. 对象相等,分别调用equals都返回true
    3. 对象具有相同hashcode值,不一定相等
    4. 因此equals被覆盖,hashcode也需要被覆盖
    5. hashcode默认对堆上的对象产生独特值(哈西码),则该class两个对象无论如何都不会相等(即使对象指向相同的数据)

只覆盖equals不覆盖hashcode会导致两个对象哈希码肯定不同,这样两个值相同的就直接进去了(相同对象hashcode直接不同)

都默认equals比较栈中的值,hashcode比较堆中的值

11.ArrayList和LinkList区别

  • ArrayList:
    • 基于动态数组(不定长,根据长度会重新创建大数组并将原来元素拷贝进去,往中间加更复杂),连续内存存储,适合下标访问(连续相同长度的元素)。
    • ArrayList使用得当会比LinkedList效率高
    • list的排序
  • LinkedList:
    • 基于链表,分散内存存储,适合插入和删除,不适合查询。
    • 插入虽然快,会创建node对象(节点),arrayList不需要
    • 遍历时必须使用iterator不能使用for循环,因为for循环每次通过get(i)获取元素都要从头找第i个节点。性能消耗极大。
    • 不要试图使用indexOf返回元素索引,也会全部遍历,当结果为空时遍历整个列表
  • ArrayList使用尾插法并指定初始容量可以极大提升性能,甚至超过linkedList;linkedList插入不一定比ArrayList快

迭代器

Iterator对象称为迭代器(设计模式的一种),主要用于遍历 Collection 集合中的元素。

jdk5之后遍历集合可以使用增强for循环(内部为迭代器),iter
在java中,迭代器共用一个对象 在循环遍历的时候,迭代器自己生成一个迭代器对象
返回值为iterator时,只能用while(iterator.hasNext())
迭代器里面不要进行增,删等操作,因为迭代器共用一个对象。

排序

  • list的排序:基本数据类型

    Collections.sort(list);
    Collections.reverse(list);
    
  • list对象类型的排序

    • 内部比较器

      public class Person implements Comparable<Person> {
          private String name;
          private int id;
          private int age;
          private String address;
          private int score;
      
          public Person() {
          }
      
          public Person(String name, int id, int age, String address, int score) {
              this.name = name;
              this.id = id;
              this.age = age;
              this.address = address;
              this.score = score;
          }
      
          public String getName() {
              return name;
          }
      
          public void setName(String name) {
              this.name = name;
          }
      
          public int getId() {
              return id;
          }
      
          public void setId(int id) {
              this.id = id;
          }
      
          public int getAge() {
              return age;
          }
      
          public void setAge(int age) {
              this.age = age;
          }
      
          public String getAddress() {
              return address;
          }
      
          public void setAddress(String address) {
              this.address = address;
          }
      
          public int getScore() {
              return score;
          }
      
          public void setScore(int score) {
              this.score = score;
          }
      
          @Override
          public String toString() {
              return "Person{" +
                      "name='" + name + '\'' +
                      ", id='" + id + '\'' +
                      ", age=" + age +
                      ", address='" + address + '\'' +
                      ", score=" + score +
                      '}';
          }
      
      
          @Override
          //内部比较器使用 this that
          public int compareTo(Person that) {
              if(this.getScore()==that.getScore())
              {//this在前升序
                  return this.getAge()-that.getAge();
              }
              else
              {
                  //降序
                  return that.getScore()-this.getScore();
              }
      
          }
      }
      
      
    • 外部比较器

      public class Test2 {
          public static void main(String[] args) {
              ArrayList<Person> people = new ArrayList<>();
              people.add(new Person("张三1",1,18,"山西",86));
              people.add(new Person("张三2",2,20,"山西",90));
              people.add(new Person("张三3",3,22,"山西",86));
              //外部比较器严于内部比较器
              //匿名内部类(是一个类,但没有名字)
              //当你去new 一个接口的时候,必须要实现接口内部方法具体的实现
              Collections.sort(people,new Comparator<Person>() {
                  @Override
                  public int compare(Person o1, Person o2) {
                      if(o1.getScore()==o2.getScore())
                      {
                          //o1-o2 升序
                          return o1.getAge()-o2.getAge();
                      }
                      else
                      {
                          return o2.getScore()-o1.getScore();
                      }
                  }
              });
      
              for (Person person : people) {
                  System.out.println(person);
              }
          }
      }
      
      

12. HashMap和HashTable的区别? 底层实现是什么?

  • hashMap(线程不安全):方法没有synchronized修饰,线程非安全,允许key和value为null。

    • 底层实现:数组+链表
      • jdk8开始,链表高度为8、数组长度超过64,链表转为红黑树,元素以内部类Node节点存在(链表)
      • 计算key的hash值,二次hash然后对数组长度取模,对应数组下标
      • 如果没有产生hash冲突(下标位置没有元素),则直接创建Node存入数组
      • 如果产生hash冲突,先进行equals比较,相同则取代,不同则判断链表高度插入链表,链表高度达到8,并且数组长度到64则转变成红黑树,长度低于6则将红黑树转回链表。
      • key为null,存在下标为0的位置
  • hashTable(线程安全):方法有synchronized修饰,线程安全,不允许为空。

  • 数组扩容:与arrayList扩容相同

    1. 继承:
      HashTable继承自Dirctionary,HashMap继承自AbstractMap,二者均实现了Map接口;

    2. 线程安全性:
      HashTable的方法是同步的,即是线程安全的。HaspMap的方法不是同步的,不是线程安全的的。在多线程并发的情况下,我们可以直接使用HashTable,如果 要使用HashMap,就需要自行对HashMap的同步处理。

    3. 键值:
      HashTable中不允许有null键和null值,HashMap中允许出现一个null键,可以存在一个或者多个键的值都为null。程序中,对于HashMap,如果使用get(参数为 键)方法时,返回结果为null,可能是该键不存在,也可能是该键对应的值为null,这就出现了结果的二义性。因此,在HashMap中,我们不能使用get()方法来查询键 对应的值,应该使用containskey()方法。

    4. 遍历:
      这两个在遍历方式的实现不同。HashTable和HashMap两者都实现了Iterator。但是,由于历史原因,HashTable还使用了Enumeration。

    5. 哈希值:
      HashTable是直接使用对象的hashCode。HashMap是重新计算hash值。

    6. 扩容:
      HashTable和HashMap的底层实现的数组和初始大小和扩容方式。HashTable初始大小为11,并且每次扩容都为:2old+1。HashMap的默认大小为16,并且一 定是2的指数{,每次扩容都为old2。

  • 代码:

    hashmap代码

    public class HashMap {
        public static void main(String[] args) {
            HashMap map = new HashMap();
    
            map.put(10,20);
            map.put("asd",12);
            map.put(null,10);
            map.put(50,null);
            map.put(null,null);
            System.out.println(map.get(null));//输出null,根据key值,获取value值,后面的覆盖了前面的
    
            //如果key存在,返回value,如果key不存在,设置value值
            System.out.println(map.getOrDefault(38, "我来了"));
    
            Collection value=map.values();
            System.out.println("所有的value值为:"+value);//输出所有的值
    
            //不会重复,变成了Set集合
            Set set = map.keySet();
            System.out.println("所有的key值为:"+set);//输出所有的键
            System.out.println(map.remove(null, null));//会有一个boolean类型的返回值
            System.out.println(map);
    
            //循环遍历map的方法一
            Set set1 = map.keySet();
            for (Object o : set1) {
                System.out.println(o+"-->"+map.get(o));
    
            }
            
            //循环遍历map的方法二
            Set set2 = map.entrySet();//将map变成Set
            for (Object o : set2) {
                System.out.println(o);
            }
        }
    }
    
    
  • 案例

    //使用HashMap添加几个员工对象,要求键:员工//值:员工工资//并遍历显示工资>18000的员工//员工类:姓名、工资、编号//要求:姓名和编号一样的员工为同一个员工!!public class Staff {    private String name;    private double salary;    private String id;    public Staff() {    }    public Staff(String name, double salary, String id) {        this.name = name;        this.salary = salary;        this.id = id;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public double getSalary() {        return salary;    }    public void setSalary(double salary) {        this.salary = salary;    }    public String getId() {        return id;    }    public void setId(String id) {        this.id = id;    }    @Override    public boolean equals(Object o) {      if(this==o)          return true;      if(!(o instanceof Staff))          return false;      Staff staff=(Staff) o;      if(this.getName().equals(staff.getName())&&this.getId().equals(((Staff) o).getId()))              return true;      else              return false;    }    @Override    public int hashCode() {        return getName().hashCode()+getId().hashCode();    }    @Override    public String toString() {        return "Staff{" +                "name='" + name + '\'' +                ", salary=" + salary +                ", id='" + id + '\'' +                '}';    }}public class Test {    public static void main(String[] args) {        HashMap<Staff,Integer> map = new HashMap();        map.put(new Staff("张三1",19000,"10"),19000);        map.put(new Staff("张三2",30000,"10"),30000);        map.put(new Staff("张三3",17000,"10"),17000);        map.put(new Staff("张三4",20000,"10"),20000);        map.put(new Staff("张三5",12000,"10"),12000);        map.put(new Staff("张三1",19000,"10"),19000);        Set<Map.Entry<Staff, Integer>> entries = map.entrySet();        ArrayList<Map.Entry<Staff, Integer>> list = new ArrayList<>(entries);        list.sort(new Comparator<Map.Entry<Staff, Integer>>() {            @Override            public int compare(Map.Entry<Staff, Integer> o1, Map.Entry<Staff, Integer> o2) {                return o2.getValue()-o1.getValue();            }        });        for (Map.Entry<Staff, Integer> staffIntegerEntry : list) {           if (staffIntegerEntry.getValue()>18000) System.out.println(staffIntegerEntry.getKey());        }    }}
    

快速失败机制

fail-fast 机制是java集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生 fail-fast 事件。例如:当某一个线程A通过 iterator 去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛 ConcurrentModificationException 异常,产生 fail-fast 事件

泛型

  • 为什么要有泛型?

    解决元素存储的安全性问题

    解决获取数据元素时,需要类型强转的问题

  • 泛型,JDK1.5新加入的,解决数据类型的安全性问题,其主要原理是在类声明时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这样在类声明或实例化时只要指定好需要的具体的类型即可

  • 使用泛型的主要优点是能够在编译时而不是在运行时检测错误。

  • 静态方法中不能使用类的泛型

  • 异常类不能是泛型的

  • 泛型不同的引用不能相互赋值

  • 泛型的指定中不能使用基本数据类型,可以使用包装类替换。

  • 如果B是A的一个子类型(子类或者子接口),而G是具有泛型声明的类或接口,G(B)并不是G(A)的子类型

使用类型通配符:?

List<?>是List、List等各种泛型List的父类

读取List<?>的对象list中的元素时,永远是安全的,因为不管list的真实类型是什么,它包含的都是Object。

13.ConcurrentHashMap原理,jdk7和jdk8版本的区别

concurrenthashMap:线程安全的hashmap

采用分段锁

  • jdk7:
    • 数据结构:ReentrantLock+Segment+HashEntry,一个Segment中包含一个hashentry的值,每个hashentry又是一个链表结构,并发度为Segment的个数,可以在构造函数中指定。数组扩容不会影响其他的segment,Segment继承于Reentranlock
    • 元素查询:第一次hash定位到segment,第二次hash定位到元素所在链表的头部
    • get方法无需加锁,volatile保证(保证可见性)
  • jdk8
    • 数据结构:synchronized+Cas+Node+红黑树。Node的val和next都用volatile修饰,保证可见性
    • 查找,替换,赋值都是用cas(乐观锁、效率更高),cas保证不了线程安全的时候(扩容、hash冲突)
    • cas锁:锁的是head节点,不影响其他元素的读写,锁粒度更细,效率更高,扩容时,阻塞所有的读写操作,并发扩容
    • 读操作无锁(volatile保证不会读到脏数据):
      • Node的val和next使用volatile修饰,读写线程对该变量互相可见
      • 数组用volatile修饰,保证扩容时被读线程感知

14. 如何实现一个ioc容器

  • ioc容器:
    • 配置文件配置包扫描路径
    • 递归包扫描获取.class文件
    • 反射、确定需要交给IOC管理的类
    • 对需要注入的类进行依赖注入

15. 什么是字节码?采用字节码的好处

  • 字节码:供虚拟机理解的代码(.class),只面向虚拟机,不面向任何处理器。(java是编译与解释并存)

  • 好处:通过字节码的方式,在一定程度上解决了传统型解释性语言执行效率低的问题,同时又保留了解释型语言可移植的特点,所以java程序运行时比较高效,而且由于字节码并不专对一种特定的机器,因此无需重新编译便可在多种不同的计算机上运行

    .java->javac(编译器)->.class->jvm->jvm中解释器->二进制码->程序运行

16. java类加载器

jdk自带三个:

类加载器负责动态加载 Java 类的字节代码到 Java 虚拟机中

  • bootstrap ClassLoader:是ExtClassLoader的父加载器,默认负责加载java_homelib下的jar包和class文件

  • ExtClassLoader:是AppClassLoader的父加载器,加载lib下ext文件夹中的jar包和class文件

  • AppClassLoader:自定义类加载器的父类,负责加载classpath下的类文件,系统类加载器,线程上下文加载器。

    自定义定义时需继承App

17.双亲委派(托)模型(内存模型)

18.java中异常体系

顶级父类Throwable

两个子类 Exception Error(OOM内存溢出)

Error无法处理的错误,一旦出现程序无法运行

Exception不会导致程序停止,又分为

  • RunTimeException:程序运行中,会导致程序当前线程执行失败
  • CheckedExpetion:编译不通过

自定义异常

class IllegalAgeException extends Exception {    //默认构造器    public IllegalAgeException() {    }    //带有详细信息的构造器,信息存储在message中    public IllegalAgeException(String message) {        super(message);    }}class Person {    private String name;    private int age;     public void setName(String name) {        this.name = name;    }     public void setAge(int age) throws IllegalAgeException {        if (age < 0) {            throw new IllegalAgeException("人的年龄不应该为负数");        }        this.age = age;    }     public String toString() {        return "name is " + name + " and age is " + age;    }} public class TestMyException {    public static void main(String[] args) {        Person p = new Person();        try {            p.setName("Lincoln");            p.setAge(-1);        } catch (IllegalAgeException e) {            e.printStackTrace();            System.exit(-1);        }        System.out.println(p);    }}

枚举类

java5新增、继承于java.lang包

  • 对象(常量的创建在第一行)

    对象名(实参列表),对象名(实参列表);

  • values()方法:返回枚举类型的对象数组。遍历所有枚举值。

    valueOf(String str):可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象的“名字”。如不是,会有运行时异常:IllegalArgumentException。

19. IO流

  • File类:用来创建、重命名、删除文件或目录(不能读写),展示目录下子目录和文件

  • 字节流:index作用是获取字节数组长度和接受字节

    • 读:单个字节读或字节数组读

    • 写:在写入一个文件时,如果使用构造器FileOutputStream(file),则目录下有同名文件将被覆盖。

      如果使用构造器FileOutputStream(file,true),则目录下的同名文件不会被覆盖,在文件内容末尾追加内容。

      在读取文件时,必须保证该文件已存在,否则报异常

  • 字符流:.flush()即不用等到缓冲区满再写

    • 读:单个字符和字符数组
    • 写:write()可以直接读取index
  • InputStreamReader OutputStreamWriter//解决中文乱码问题//把字节流读取的字节进行缓冲而后在通过字符集解码成字符返回,因而形式上看是字符流
    
  • Buffer

    • 读:new BufferedReader(new InputStreamReader(new FileInputStream(“filepath”),“utf-8”));
    • 写:new BufferedWriter(new OutputStreamWriter(new FileOutputStream(“filepath”), “utf-8”));
  • 对象流:transient 这个属性不去序列化它

    • 读:ObjectInputStream
    • 写:ObjectOutPutStream

    序列化:用ObjectOutputStream类保存基本类型数据或对象的机制

    反序列化:用ObjectInputStream类读取基本类型数据或对象的机制

    如果需要让某个对象支持序列化机制,则必须让其类是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一:

    • Serializable

    • Externalizable

    若某个类实现了 Serializable 接口,该类的对象就是可序列化的

  • 随机存取文件流(RandomAccessFile):new RandomAccessFile(“filepath”,“r”)

20. GC(JVM)如何判断对象可以被回收

  • 引用计数法:每个对象有一个引用计数属性,新增一个引用加1,引用释放时计数减1,技术为0表没有引用,可以回收。

    • 两个对象相互引用则无法释放
    • 效率高
  • 可达性分析:从GC Roots开始向下搜索,搜索所走过的路径成为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,那么虚拟机就判断是可回收对象。

  • GC Roots的对象有:

    • 虚拟机活动栈中引用的对象
    • 方法区中类静态属性引用的对象
    • 方法区中常量引用的对象
    • JVM自动调用的本地方法
  • 可达性算法的不可达对象并不是立即死亡的

    finalize()对象只能执行一次,代价高,不确定性大不推荐使用

    • 第一次做标记
    • 第二次由虚拟机自动建立的Finalizer队列中判断是否需要执行finalize()方法。看有没有覆盖finalize方法,未覆盖直接回收,覆盖后则执行。覆盖后若未执行finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法,执行finalize方法后,会再次判断该对象是否可达,若不可达则回收,否则对象复活。

21. 多线程

  • 相关概念

    • 程序:是为完成特定任务、用某种语言编写的一组指令的集合。静态
    • 进程:是程序的一次执行过程,或是正在运行的一个程序。动态:有其自身的产生、存在和消亡的过程。
    • 线程:进程可进一步细化为线程,是一个程序内部的一条执行路径。每个Java程序都有一个隐含的主线程: main 方法
  • 多线程的优点:

    1. 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。

    2. 提高计算机系统CPU的利用率

    3. 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改

  • 何时需要多线程:

    1. 程序需要同时执行两个或多个任务。

    2. 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。需要一些后台运行的程序时。

  • Thread类

    • 每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体。通过该Thread对象的start()方法来调用这个线程

    • Thread的构造器:

      • 构造器

        1. Thread():创建新的Thread对象

        2. Thread(String threadname):创建线程并指定线程实例名

        3. Thread(Runnable target):指定创建线程的目标对象,它实现了Runnable接口中的run方法

        4. Thread(Runnable target, String name):创建新的Thread对象

      • 有关方法

        1. void start(): 启动线程,并执行对象的run()方法

        2. run(): 线程在被调度时执行的操作

        3. String getName():返回线程的名称

        4. void setName(String name):设置该线程名称

        5. static currentThread():返回当前线程

        6. static void yield()

        7. join()

        8. static void sleep(long millis)

        9. stop():

        10. boolean isAlive():返回boolean,判断线程是否还活着

    • java中调度方法:

      • 同优先级采用队列,先进先出(时间片策略)
      • 高优先级优先调度的抢占式策略
  • 创建线程的方式:

    • 继承Thread类

      1. 定义子类继承Thread类。

      2. 子类中重写Thread类中的run方法。

      3. 创建Thread子类对象,即创建了线程对象。

      4. 调用线程对象start方法:启动线程,调用run方法。

      public class ThreadDemo {    public static void main(String[] args) {//        Thread thread=Thread.currentThread();//静态获取当前main线程的对象//        System.out.println(thread.getName());//        thread.setName("线程");//        System.out.println(thread.getName());        Thread_Test thread1 = new Thread_Test("线程1");        thread1.setPriority(10);//优先级        //开启线程用start方法        thread1.start();        Thread_Test thread2 = new Thread_Test("线程2");        thread2.setPriority(1);//优先级        thread2.start();        //打印线程的优先级//        System.out.println(thread1.getPriority());    }}class Thread_Test extends Thread{    public Thread_Test(String name)    {        super(name);    }    public void run()    {        for (int i=0;i<100;i++)            System.out.println(Thread.currentThread().getName()+"运行了"+i);    }}
      
    • 实现Runnable接口

      1. 定义子类,实现Runnable接口。

      2. 子类中重写Runnable接口中的run方法。

      3. 通过Thread类含参构造器创建线程对象。

      4. 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。

      5. 调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。

      public class RunnableDemo {    public static void main(String[] args) {        Runnable_Test runnable1 = new Runnable_Test();        Thread thread = new Thread(runnable1);        thread.start();    }}class Runnable_Test implements Runnable{    @Override    public void run() {        for(int i=0;i<100;i++)            System.out.println(Thread.currentThread().getName()+"运行了"+i);    }}
      

线程安全引入锁

  • Synchronize的使用方法

    • synchronized (对象){

      //需要被同步的代码;

      }

    • synchronized还可以放在方法声明中,表示整个方法为同步方法。

      public synchronized void show (String name){

      ….

      }

    • 同步锁::两个或两个以上的进程或线程在运行过程中协同步调,按预定的先后次序运行。比如 A 任务的运行依赖于 B 任务产生的数据。

    • 互斥锁:一个公共资源同一时刻只能被一个进程或线程使用,多个进程或线程不能同时使用公共资源。

    • 两种锁实现都需要Synchronize。

  • 释放锁的操作

    • 当前线程的同步方法、同步代码块执行结束

      当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、该方法的继续执行。

      当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束

      当前线程在同步代码块、同步方法中执行了线程对象的**wait()**方法,当前线程暂停,并释放锁。

  • 不会释放锁的操作:

    • 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行

      线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)。

  • 状态

    • 新建状态:新创建了一个线程对象
    • 就绪状态:线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权
    • 运行状态:就绪状态的线程获取了CPU,执行程序代码
    • 阻塞状态:阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行,直到线程进入就绪状态,才有机会转到运行状态
    • 死亡状态:线程执行完了或者因异常退出了run方法,该线程结束生命周期
  • 生命周期

    • 创建
    • 就绪
    • 运行
    • 阻塞
      • 等待阻塞:wait()方法,放到等待池,wait不能自动唤醒
      • 同步阻塞:没抢到锁,放到锁池
      • 其他阻塞:执行sleep方法,sleep超时则重新转入就绪态
    • 死亡

23. sleep、wait、join、yield

四种方法的区别:

锁池:所有竞争同步锁的线程。没争到锁的进入锁池,当前面线程释放同步锁后,锁池中的线程得到就会进入就绪队列进行等待cpu资源分配

等待池

当调用wait方法,线程会进入等待池中,等待池中不会竞争同步锁,只有调用了notify或notifyall后等待池的线程才会开始去竞争锁,notify是随机从等待池选出一个线程放到锁池,notifyall是将等待池的所有线程放到锁池中

  • sleep:是Thread类的静态方法,不会释放锁
    • sleep就是把cpu的执行资格和执行权释放出去,不再运行此线程,当定时时间结束再取回cpu资源,参与cpu的调度,获取到cpu资源后就可以继续运行了,而如果sleep时该线程有锁,那么sleep不会释放这个锁,而是把锁带着进入了冻结状态,也就是说其他需要这个锁的线程根本不可能获取到这个锁,也就是说无法执行程序,如果在睡眠期间其他线程调用了这个线程的interrupt方法,那么这个线程也会抛出interruptexception异常返回,这点和wait是一样的。
    • sleep方法不依赖于synchronized
    • sleep不需要被唤醒
    • 一般用于当前线程休眠,或者轮循暂停操作
    • 会让出cpu执行时间且强制上下文切换
  • wait:是Object类的本地方法,会释放,而且会加到等待池中。
    • 依赖于synchronized
    • 不指定时间但需要被别人中断
    • 多用于多线程之间的通信
    • wait后可能还是有机会重新竞争到锁继续执行
  • yield(重新起跑线):执行后线程直接进入就绪状态,马上释放了cpu的执行权,但是依然保留了cpu的执行资格,所以有可能cpu下次进行线程调度还会让这个线程获取到执行权继续执行
  • join(插到A后面):执行后线程进入阻塞状态,例如在线程B中调用线程A的join,那线程B会进入到阻塞队列,直到线程A结束或中断线程

24. 对线程安全的理解

不是线程安全,应该是内存安全,堆是共享空间,可以被所有线程访问。

当多个线程访问一个对象时,如果不用进行额外的同步控制或其他的协调操作,调用这个对象的行为都可以获得正确的结果,我们就说这个对象是线程安全的。(多线程访问和单线程访问结果一致)

堆:是进程和线程共有的空间,分全局堆和局部堆。全局堆是所有没有分配的空间。局部堆是用户自己定义的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是用完了要还给操作系统,要不然就是内存泄漏。

在java中,堆是java虚拟机所管理的内存中最大的一块,是所有线程共享的一块内存区域,在虚拟机启动时创建。堆所在的内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。

栈是每个线程独有的(线程安全的),保存其运行状态和局部变量的,栈在线程开始的时候初始化,每个线程的栈相互独立。因此,栈是线程安全的。操作系统在切换线程的时候会自动切换栈。栈空间不需要在高级语言里面显示的分配和释放

目前主流操作系统都是多任务的,即多个进程同时运行。为了保证线程安全,每个进程只能访问分配给自己的内存空间,而不能访问别的进程的,这是由操作系统保障的。

在每个进程的内存空间中都会有一块特殊的公开区域,通常称为堆(内存)。进程内的所有线程都可以访问到该区域,这就是造成问题的潜在原因

25. Thread、Runable的区别

  • Thread:类,实现了Runable,复杂的线程操作需求。
  • Runable:接口,简单的执行一个任务

使用时都new Thread,然后执行run方法

26. 对守护线程的理解

守护线程:为所有非守护线程提供服务的线程;任何一个守护线程都是整个JVM中所有非守护线程的保姆

  • 作用:

    举例:GC垃圾回收线程:就是一个经典的守护线程,当我们的程序不再有任何运行的Thread,程序就不会再产生任何垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是JVM上仅剩的线程时,垃圾回收线程会自动离开。它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。

  • 应用场景:

    • 来为其他线程提供服务支持的情况
    • 在任何情况下,程序结束时,这个线程必须正常且立刻关闭,就可以作为守护线程来使用;反之,如果一个正在执行某个操作的线程必须要正确地关闭掉否则就会出现不好的后果的话,那么这个线程就不能是守护线程,而是用户线程。通常就是些关键的事务,比如说数据库录入或更新,这些操作都是不能中断的。
  • 用法:先调用thread.setDaemon(true)必须在thread.start()之前设置,否则会抛出一个illegalThreadStateExpetion异常,不能把正在运行的常规线程设置为守护线程。

    • 守护线程中产生的新线程也是守护线程
    • 守护线程不能用于去访问固有资源,比如读写操作或者计算逻辑,因为它会在任何时候甚至在一个操作的中间发生中断。
  • java自带的多线程框架,比如ExecutorService,会将守护线程转换为用户线程,所以如果要使用后台(守护)线程就不能用java的线程池

27. ThreadLocal的原理和使用场景

每一个Thread对象均含有一个ThreadLocalMap类型的成员变量threadLocals,它存储本线程中所有ThreadLocal对象及其对应的值

ThreadMap由一个个Entry对象构成

Entry继承自weakReference<ThreadLocal<?>>,一个Entry由ThreadLocal对象和Object构成,由此可见,Entry的key是Thread对象,并且是一个弱引用,当没指向key的强引用后,该key就会被垃圾收集器回收。

当执行set方法时,ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,将值存储进ThreadLocalMap对象中

get方法执行过程类似。ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象,再以当前ThreadLocal对象为key,获取相应的value

由于每一条线程均含有各自私有的ThreadLocal容器,这些容器相互独立互不影响,因此不会存在线程安全性问题,从而也无需使用同步机制来保证多条线程访问容器的互斥性。

使用场景:

  1. 在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
  2. 线程间数据隔离
  3. 进行事务操作,用于存储线程事务信息。

28. TreadLocal内存泄露原因,如何避免

  • 内存泄漏:为程序申请内存后,无法释放已申请的内存空间,一次内存泄漏危害可以忽略,但内存泄漏堆积后果很严重,无论多少内存,迟早会被占光。

    不再会被使用的对象或者变量占用的内存不能被回收,就是内存泄露。

  • 强引用:使用最普遍的引用(new)(反射也是),一个对象具有强引用,不会被垃圾回收器回收。当内存空间不足,java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不回收这种对象。

    如果想取消强引用和某个对象的关联,可以显示地将引用赋值为null,这样可以使JVM在合适的时间就会回收该对象。

  • 弱引用:JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用java.lang.ref.WeakReference类来表示。可以在缓存中使用弱引用。

    ThreadLocal的实现原理,每一个Thread维护一个ThreadLocalMap,key为使用弱引用的ThreadLocal实例,value为线程变量的副本

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZKadrdrA-1626426335577)(C:\Users\吕袆雄\AppData\Roaming\Typora\typora-user-images\image-20210706173856516.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0q4uWD5y-1626426335579)(C:\Users\吕袆雄\AppData\Roaming\Typora\typora-user-images\image-20210706174019160.png)]

29.并发、并行、串行的区别

  • 并发:允许两个任务彼此干扰。同一时间点,只有一个任务运行,交替执行。A执行一会B执行一会。
  • 并行:时间上重叠。两个任务在同一时刻互不干扰的同时执行
  • 串行:在时间上不可能发生重叠,前一个任务没完成,下一个任务只能等待

30. 并发的三大特性

  • 原子性:
    • 指在一个操作中cpu不可以在中途暂停然后再调度,即不被中断操作,要不全部执行完成,要不都不执行。
  • 可见性:
    • 当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值
    • 若两个线程在不同的cpu,那么线程1改变了i的值还没刷新到主存,线程2又使用了i,那么这个i值肯定还是之前的,线程1对变量的修改线程没看到这就是可见性问题
  • 有序性:
    • 虚拟机在进行代码编译时,对于那些改变顺序之后不会对最终结果造成影响的代码,虚拟机不一定会按照我们写的代码顺序来执行,有可能将他们重排序,实际上,对于有些代码进行重排序后,虽然对变量的值没有造成影响,但有可能会出现线程安全问题。

使用synchronized都可以保证

volatile除了原子性

31. 悲观锁和乐观锁

  • 悲观锁:线程一和线程二同时访问数据,两个线程谁先加锁先访问,访问时另一个线程不能访问。

    • 共享锁:加锁后所有线程都只读。
    • 排他锁:加锁后其他线程不能执行。

    悲观并发控制实际上是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会。另外还会降低并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数据。

  • 乐观锁:线程一和线程二同时访问数据,版本一已经修改,版本二再访问时与之前预计数据比较,不同则交给用户处理。

    乐观锁通常是通过在表中增加一个版本(version)或时间戳(timestamp)来实现,其中,版本最为常用。事务在从数据库中取数据时,会将该数据的版本也取出来(v1),当事务对数据变动完毕想要将其更新到表中时,会将之前取出的版本v1与数据中最新的版本v2相对比,如果v1=v2,那么说明在数据变动期间,没有其他事务对数据进行修改,此时,就允许事务对表中的数据进行修改,并且修改时version会加1,以此来表明数据已被变动。如果,v1不等于v2,那么说明数据变动期间,数据被其他事务改动了,此时不允许数据更新到表中,一般的处理办法是通知用户让其重新操作。不同于悲观锁,乐观锁是人为控制的。

32. 反射和注解

  • 反射:反射是将类抽象为一个Class对象。将类看成对象,分析它的构造方法,成员变量,方法以及内部类。

    • 对类的分析,是将类抽象为Class对象
    • 对构造方法的分析,是将构造方法抽象为Constructor类的对象
    • 对成员变量的分析,是将变量抽象为Feild类的对象
    • 对方法的分析,是将方法抽象为Method类的对象。
    Class clz = Class.forName("java.lang.String");Class clz = String.class;String str = new String("Hello");Class clz = str.getClass();//无参构造Class clz = Apple.class;Apple apple = (Apple)clz.newInstance();Class clz = Apple.class;Constructor constructor = clz.getConstructor();Apple apple = (Apple)constructor.newInstance();//有参构造Class clz = Apple.class;Constructor constructor = clz.getConstructor(String.class, int.class);Apple apple = (Apple)constructor.newInstance("红富士", 15);//获取属性Class clz = Apple.class;Field[] fields = clz.getFields();for (Field field : fields) {    System.out.println(field.getName());}//获取全部属性,包括私有属性Class clz = Apple.class;Field[] fields = clz.getDeclaredFields();for (Field field : fields) {    System.out.println(field.getName());}获取的方法.invoke(反射得到的对象那个,参数)
    
  • 注解:注解(Annotation),也叫元数据(Metadata),是Java5的新特性,JDK5引入了Metadata很容易的就能够调用Annotations。注解与类、接口、枚举在同一个层次,并可以应用于包、类型、构造方法、方法、成员变量、参数、本地变量的声明中,用来对这些元素进行说明注释。

    • 内置注解
      • @Override - 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
      • @Deprecated - 标记过时方法。如果使用该方法,会报编译警告。
      • @SuppressWarnings - 指示编译器去忽略注解中声明的警告。
      • 元注解
        • @Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。指Annotation被保留的时间长短,标明注解的生命周期
        • @Documented - 标记这些注解是否包含在用户文档中。
        • @Target - 标记这个注解应该是哪种 Java 成员。标明注解的修饰目标
        • @Inherited - 标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)允许子类继承父类的注解

33. 为什么用线程池?解释下线程池参数?

  1. 降低资源消耗(创建和销毁都要损耗资源),提高线程利用率,降低创建和销毁线程的消耗
  2. 提高响应速度,任务来了直接有线程可用可执行,而不是先创建线程再执行
  3. 提高线程的可管理性:线程是稀缺资源,使用线程池可以统一分配调优监控。、
  • 参数:

    • corePoolSize:核心线程数,正常情况下创建工作的线程数。(常驻)

    • maxinumPoolSize:最大线程数,(最大允许被创建)

    • keepAliveTime、unit(时间单位):空闲时间后可回收

    • workQueue:存放待执行任务(创建),等此队列满才会创建新的线程

    • ThreadFactor:线程工厂,产生线程执行任务。创建线程的。

    • Handler:任务拒绝策略。池关了还有线程正在执行想提交任务;没有能力创建新的线程

34. 简述线程池处理流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qR4ivT6r-1626426335581)(C:\Users\吕袆雄\AppData\Roaming\Typora\typora-user-images\image-20210706190351242.png)]

根据拒绝策略处理任务

35. 网络编程

基于TCP协议的Socket网络通信,用来实现双向安全连接网络通信。

Socket通信模型:socket通信原理是一种“打开—读/写—关闭”模式的实现,服务器和客户端各自维护一个“文件”,在建立连接打开后,可以向文件写入内容供对方读取或者读取对方内容,通讯结束时关闭文件。

进行网络通信时,Socket需要借助数据流来完成数据的传递工作

TCP:

public class Server {    public static void main(String[] args) {        ServerSocket serverSocket = null;        try {            serverSocket = new ServerSocket(8000);            //获取客户端的信息            Socket accept = serverSocket.accept();            InputStream inputStream = accept.getInputStream();            //转换为字符串            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));            String line = null;            while ((line = bufferedReader.readLine()) != null)                System.out.println(line);            serverSocket.close();            accept.close();            inputStream.close();            bufferedReader.close();        } catch (IOException e) {            e.printStackTrace();        }    }}class Client {    public static void main(String[] args) {        //创建Socket        try {            Socket socket = new Socket("localhost",8000);            //获取输出流            OutputStream outputStream = socket.getOutputStream();            //客户端把数据写出去            String info="你好吗?";            outputStream.write(info.getBytes());            socket.close();            outputStream.close();        } catch (IOException e) {            e.printStackTrace();        }    }}

UDP:

public class UDPServer {    public static void main(String[] args) throws Exception {        //1.接收包裹        DatagramSocket socket = new DatagramSocket(9000);        //2.拿取数据    byte[] buf, int length        byte[] bytes = new byte[1024];        DatagramPacket datagramPacket = new DatagramPacket(bytes,bytes.length);        socket.receive(datagramPacket);        //3.展示数据        System.out.println(new String(bytes));        //4.给客户端返回数据        String info = "收到";        //5.发送包裹    byte[] buf, int length, SocketAddress address        DatagramPacket datagramPacket1 = new DatagramPacket(info.getBytes(),info.getBytes().length,datagramPacket.getSocketAddress());        socket.send(datagramPacket1);        //6.关流        socket.close();    }}class UDPClient {    public static void main(String[] args) throws Exception {        //1.创建一个数据包,把数据装到这个包裹中  host port        //byte[] buf, int length, InetAddress address, int port        //获取InetAddress对象        String line="你好";        InetAddress localHost = InetAddress.getLocalHost();        DatagramPacket datagramPacket = new DatagramPacket(line.getBytes(),line.getBytes().length,localHost,9000);        //2.发送包裹        DatagramSocket socket = new DatagramSocket();        socket.send(datagramPacket);        //3.接收包裹        byte[] bytes = new byte[1024];        DatagramPacket datagramPacket1 = new DatagramPacket(bytes,bytes.length);        socket.receive(datagramPacket1);        //4.处理包裹        System.out.println(new String(bytes));        //5.关流        socket.close();    }}

36. 线程池中阻塞队列的作用。为什么是先添加队列而不是先创建最大线程。

  1. 阻塞队列:一般的队列只能保证作为一个有限长度的缓冲区,如果超出了缓存长度,就无法保留当前的任务了,阻塞队列可以保留住当前想要继续入队的任务。

    阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入wait状态,释放cpu资源。

    阻塞队列自带阻塞和唤醒的功能,不需要额外处理,无任务执行阻塞队列的take方法挂起,从而维持核心线程的存活,不至于一直占用cpu资源

  2. 在创建新线程的时候,是要获取全局锁的,这个时候其他的线程就得阻塞,影响了整体效率。

    企业里有10个(core线程)正式工的名额,最多招10个正式工,要是任务超过正式工人数的情况下,还是这10个人,慢慢干,任务继续增加,就招外包,正式工+外包还不能完成任务,新来的任务就会被领导拒绝。

    避免频繁创建与频繁回收。

37. 线程池中线程复用原理

核心原理在于线程池对Thread进行了封装,并不是每次执行任务都会调用start方法创建新线程,而是让每个线程执行一个循环任务,在任务中不停检查是否有任务需要执行,如果有则直接执行,调用run方法,将run方法当成一个普通方法执行,通过这种方式只使用固定的线程就将所有任务的run方法串联起来。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值