Java基础知识大全

一、java面向对象的三大特性与含义

  • 继承:从已有的类得到继承信息创建新类的过程,继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序的可变因素的重要手段。

  • 封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已经定义的接口。面向对象的本质就是将现实世界描绘成一系列完全自制、封闭的对象。可以说,封装就是隐藏一切可隐藏的东西,只向外界提供简单的编程接口;

  • 多态:允许不同子类型的对象对同一消息作出不同的响应。简单说就是用同样的对象引用调用同样的方法但是做了不同的事情。

  • 实现多态需要做的两件事:①、方法重写;②、对象造型


二、java多态

  • 多态定义:允许不同类的对象对同一消息作出响应,即同一消息可以根据发送对象的不同而表现出多种不同的行为方式。

  • 多态的作用:消除类型间的耦合关系。

  • 实现多态的技术:动态绑定

  • 多态存在的必要条件:①有继承;②有重写;③父类引用指向子类对象。

  • 多态的好处:①可替换性、②可扩充性、③接口性、④灵活性、⑤简化性。

  • 多态的实现方式:接口实现、继承父类进行方法重写、同一个类内进行方法重载。


三、Java中的SoftReference是什么?

  • Java 中的 SoftReference 即对象的软引用。如果一个对象具有软引用,内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。使用软引用能防止内存泄露,增强程序的健壮性。

  • SoftReference的特点:它的一个实例保存对一个Java对象的软引用,该软引用的存在不妨碍垃圾收集线程对该Java对象的回收。也就是说,一旦SoftReference保存了一个Java对象的软引用后,在垃圾线程对这个Java对象回收前,SoftReference类所提供的get()方法返回Java对象的强引用。但是,一旦垃圾线程回收该Java对象之后,get()方法将返回null。


四、abstract关键字

  • abstract可以修饰类、方法

  • 如果一个类被abstract修饰,此类必须被继承使用,此类不可以生成对象,但是可以声明。

  • abstract可以 将子类的共性最大限度的抽取出来放在父类,以提高代码的简洁性。

  • abstract修饰方法时,方法为抽象方法,该方法不需要进行实现,实现留给子类,子类覆盖该方法之后才能生效

  • abstract不能与static、final放在一起否则会出现错误


五、重写和重载

  • 重写:如果子类中定义的某个方法与其父类有相同的名称和参数,则该方法被重写了。

  • 重载:如果一个类内有多个重名的方法,它们或有不同的参数个数、或有不同的参数类型、或有不同的参数次序,则称为重载。

  • 重载的规则:必须改变参数列表、可以改变返回类型、可以改变访问修饰符、可以声明新的或更广的检查异常、能够在同一个类中或者在一个子类中被重载、无法通过返回值类型来判断方法是否重载。

  • 重写的规则:参数列表、返回值类型必须相同,访问权限不能低于父类的方法,被final修饰的方法不能被重写,static修饰的方法不能被重写但是可以再次声明,构造方法不能被重写,重写方法不能抛出更广泛的强制异常,访问权限不能下降。


六、Java中如何定义常量

  • 接口中常量默认为static final;

  • Java 5.0 中引入类Enum类型;

  • 在普通类中使用static final修饰的变量;


七、abstract、interface、final、static总结

abstract

  • 只要含有抽象方法的类必须声明为抽象类;

  • 抽象类可以有具体的实现方法;

  • 抽象类内可以没有抽象方法;

  • 抽象方法必须被子类实现,或者子类也是抽象的;

  • 抽象类不可以被实例化,但是可以被声明;

  • 若使用抽象类内部的方法,则必须有一个子类继承这个抽象类,并且实现抽象方法,通过子类实例化去调用;


interface

  • 接口内成员变量必须定义初始化;

  • 接口内的成员方法只能是方法原型,不能有方法体;

  • 接口内的成员变量和方法只能是public的;

  • 实现接口的类必须全部实现接口的方法;


final

  • 可以修饰成员变量、非抽象类、非抽象类成员方法、方法参数;

  • final方法不能被子类重写,但是可以被继承;

  • final类不可以被继承,final类内部的方法也无法被继承;

  • final修饰的变量只能被赋值一次,赋值之后不能被修改;

  • final不能修饰构造方法;

  • final的参数:只能被使用,不能修改该参数的值


static

  • 可以修饰成员变量和成员方法,但是不能修饰类以及构造方法;

  • static修饰的成员变量和成员方法独立于这个类,被类的所有实例共享;

  • static修饰的类和成员方法可以通过类名直接访问,也可以通过实例来访问;

  • private修饰的static变量和static方法只能在声明的本类方法及静态块内访问,但不能使用this方法,因为this是非静态变量。


static+final同时修饰

  • 该变量或者方法可以简单理解为全局常量;

  • 对于变量一但给值就不可以修改,可以通过类名进行访问;

  • 对于方法,表示不可覆盖,可以通过类名直接访问;


switch是否可以使用string来作为参数?

  • Java 5之后switch可以引用enum、byte、short、char、int作为参数
    Java 7 之后引入string


八、Object有哪些公用方法

  • clone方法:保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则会抛出CloneNotSupportException异常。

  • getClass()方法:final方法,获得运行时类型

  • toString()方法:不用多说了( ̄▽ ̄)~*

  • finalize方法:释放资源使用,很少使用;

  • equals()方法:只有在Object中equals方法和==是一样,其他时候两者不一样。

  • hashCode()方法:该方法用于哈希值的查找,可以减少equals方法的使用,提高代码运行效率。

  • wait()方法:wait方法就是使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait方法一直等待,直到获得锁或者被中断。调用该方法后当前线程进入睡眠状态,直到发生以下事件:

    ①、其他线程调用了该对象的notify方法;
    ②、其他线程调用该对象的notifyAll方法;
    ③、其他线程调用了interrupt方法中断该线程;
    ④、时间间隔到了。


八、int和integer的区别

  • int是基本数据类型

  • integer是int的封装类

  • int和integer都可以表示某一个数值

  • int和integer不能够互用,因为他们两种不同的数据类型


九、string是否可以被继承

  • 不可以,因为String为final类


十、常量 final String str=“ab”可不可以变成“abc”,为什么?

  • 不可以,因为使用final修饰的变量不可改变


十一、String、StringBuffer、StringBuilder之间的区别

  • 三者执行效率为:StringBuilder>StringBuffer>String

    String字符串为常量,不可改变;
    StringBuilder字符串变量(非线程安全)
    StringBuffer字符串变量(线程安全)

    String类型进行改变的时候其实等同于生成 一个新的String对象,然后指针指向新的String对象。会导致jvm的gc开始工作导致速度变慢;

    StringBuffer一个类似于String的字符串缓冲区,但是不能修改。但是可以通过某些方法来改变该序列的长度和内容。

    StringBuffer上主要操作的是append方法和insert方法,append方法是将任意类型的数据转换成字符串添加到缓存区末端,而insert方法是指定的添加字符串。

    String s=new String(“abc”);new了几个对象?

    两个

String源码分析

  • String对象是不可变类型,返回类型为String的String方法每次返回的都是新的String对象,除了某些方法的某些特定条件返回自身。

    String对象的三种比较方式:

    1、==内存比较:直接对比两个引用所指向的内存值,精确简洁直接明了。
    2、equals字符串值比较:比较两个引用所指对象字面值是否相等。
    3、hashCode字符串数值化比较:将字符串数值化。两个引用的hashCode相同,不保证内存一定相同,不保证字面值一定相同。


十二、内部类

静态内部类和非静态内部类的区别

  • 静态内部类不持有外部类的引用,非静态内部类持有外部类的引用

  • 静态内部类可以有静态成员,非静态内部类不能持有静态成员;

  • 静态内部类只能访问外部类的静态成员和静态方法,非静态内部类可以访问外部类的所有成员和方法;

  • 实例化一个静态内部类不需要外部类的实例;实例化一个非静态内部类需要通过外部类的实例生成内部类的实例;

  • 调用静态内部类的方法或变量可以通过类名直接调用。

为什么内部类拥有外部类的所有元素的访问权?

  • 内部类对象只能在与其外部类对象关联的情况下才能被创建,构建内部类需要一个外部类的引用,内部类正是通过这个引用去访问外部类的。

内部类的种类

  • 成员内部类、方法内部类、匿名内部类、静态内部类

内部类的作用

  • 内部类可以 用多个实例,每个实例都有自己的状态信息,并且与其他外围对象的信息相互独立;

  • 单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或者继承同一个类。

  • 创建内部类对象的时刻并不依赖于外围类对象的创建;

  • 内部类是一个独立的实体;

  • 内部类提供了更好的封装,除了外围类,其他类都不能进行访问

静态内部类、内部类、匿名内部类,为什么内部类会持有外部类的引用?持有的引用是this?还是其它?

  • 因为内部类的产生依赖于外部类,持有的引用是“类名.this”;


十三、抽象类和接口

抽象类的总结

  • 抽象类不能被实例化

  • 抽象类内部不一定包含抽象方法,有抽象方法的类一定是抽象类

  • 抽象类内部的方法只是声明,不包含方法体,就是不给出具体的实现也就是方法的具体功能;

  • 构造方法、类方法不能声明为抽象方法

  • 抽象类的子类必须给出抽象类中抽象方法的具体实现,除非这类也是抽象类。

接口与类的区别

  • 接口不能用于实例化对象;

  • 接口没有构造方法;

  • 接口内的方法必须是抽象方法;

  • 接口不能包含成员变量,除非是static 和 final修饰的;

  • 接口不是被类继承而是被类实现;

  • 接口支持多重继承

接口特性

  • 接口中的每一个方法都是隐式抽象的,接口中的方法会被隐式的指定为 public abstract;

  • 接口内可以含有变量,但是接口中的变量会被隐式的指定为public static final 变量;

  • 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口内部的方法;

抽象类与接口的区别

  • 抽象类内部的方法可以有方法体,但是接口内部的方法不行;

  • 抽象类中的成员变量可以是各种类型的,而接口的成员变量只能是public static final类型的;

  • 接口中不能有静态代码块以及静态方法,而抽象类内可以有静态代码块和静态方法,java8新特性允许接口内存在静态方法;

  • 一个类只能继承一个抽象类,而一个类可以实现多个接口;

  • 接口中不能有构造函数和main方法;抽象类内可以有;

  • 接口是被实现;抽象类类是被继承。

接口的意义

  • 提供一个规范,要求类必须实现指定的方法;

  • 解决Java中的单继承问题,可以用接口来实现多继承的功能,简单化多重继承中继承树的复杂程度;

  • 增强程序的扩展性。

抽象类的意义

  • 为其子类提供一个公共的类型,封装子类中的重复内容,定义抽象方法,子类虽然有不同的实现,但是定义是一致的。


十四、接口是否可继承接口? 抽象类是否可实现(implements)接口? 抽象类是否可继承实体类(concreteclass)?

  • 接口可以继承接口,使用关键字extends

  • 抽象类可以实现接口。AbstractCollection类就实现了Collection接口;

  • 抽象类可以继承实体类。


十五、abstract的方法是否可以同时是static,是否可同时是native,是否可同时是final?

  • 都不行

    abstract的方法不可以是static的,因为抽象的方法是要被子类实现的,而static与子类没有关系!

    native方法表示该方法要用另外一种依赖平台的编辑语言实现的,不存在者被子类实现的问题,所以,他也不能是抽象的,不能与abstract混用。


十六、final关键字

  • final修饰的类不能被继承,final类内部的方法都被隐式的指定为final方法;

  • final修饰的方法的原因:明确禁止该方法在子类中被覆盖;

  • 修饰变量:①、基本数据类型的变量,数值一旦被初始化便不能更改;②、引用类型变量,初始化之后不能再让其指向另一个对象

  • final修饰的引用形变量其指向的对象内容可变。


十七、finally final finalize的作用?

  • final用于声明属性、方法和类;finally是异常处理语句结构的一部分,表示总是执行;finalize是Object类的一个方法。


十八、Collection与Collections的区别

  • Collection是一个接口,它是Set、List等容器的父接口;Collections是一个工具类,提供了一系列的静态方法来辅助容器操作,这些方法包括对容器的搜索、排序、线程安全化等


十九、Set和List的区别

  • Set接口存储的是无序的、不重复的数据;List接口存储的是有序的、可以重复的数据;

  • Set检索效率低,删除和插入效率高、删除和插入不会引起元素位置变化,实现类有HashSet、TreeSet。
    List检索效率高、插入和删除效率低,因为引起其他元素的位置变化,实现类有ArrayList、LinkdList、Vector。


二十、hashCode方法的作用

  • hashCode方法主要作用是为了配合基于散列的集合一起正常的运行,这样的散列的集合包括hashSet、HashMap、HashTable。
    why?考虑到一种情况就是当向集合内部插入对象时,不允许有重复对象。
    当向集合内部添加新对象时,首先调用对象的hashCode方法得到对象的哈希值,实际上在HashMap的具体实现中会用到一个table保存已经存进去的对象的hashCode,如果table内没有该哈希值,它就可以直接存进去不用进行比较了,如果存在则调用它的equals方法与新元素进行比较如果相同的话就不存了,不同的话存进去。这样的话就大大降低了equals方法的使用,提高了程序的运行效率。

    如果equals方法得到的结果为true,则两个对象的hashcode值必定相等;
    如果equals方法得到的结果为false,则两个对象的hashcode值不一定不同;
    如果两个对象的hashcode值不等,则equals方法得到的结果必定为false;
    如果两个对象的hashcode值相等,则equals方法得到的结果未知。

多线程环境中安全使用集合API

  • 在集合API中最初设计的Vector和HashTable是多线程安全的。例如Vector,用来添加和删除元素的方法是同步的,如果只有一个线程与其进行交互,那么要求获取和释放对象锁是一种浪费,另外在不必要的时候滥用同步化也可能带来死锁现象。因此集合的本质上是非多线程安全的,当与多线程交互时,为了使它多线程安全,必须采取额外措施。

HashMap的实现原理

  • HashMap底层就是一个数组结构,数组中的每一项又是一个链表。当新建一个HashMap的时候就会初始化一个数组。


二十一、容器之间的区别

HashMap与HashTable的区别

  • 继承不同   HashMap继承自Dictionary 、HashTable继承自AbstractMap

  • HashTable中的方法是同步的,HashMap的方法不是同步的;

  • HashTable内的key和value都不允许出现null,HashMap中的null可以作为键,这样的键只能有一个;可以有一个或者是多个键对应的值是null;

  • 两个遍历方式不同

  • HashTable直接使用对象的哈希值、HashMap重新计算哈希值;

ArrayMap和HashMap的区别

  • 存储方式不同。ArrayMap使用的两个数组进行存储,HashMap使用链表进行存储;

  • 扩容处理方法不一样。ArrayMap使用System.arraycopy()copy数组,而 HashMap 使用 new HashMapEntry 重新创建数组。

  • ArrayMap 提供了数组收缩的功能,在clear或remove后,会重新收缩数组,节省空间。

    4、ArrayMap 采用二分法查找。

ArrayList Vector LinkedList 三者的区别

  • 三者都实现了List 接口;但 LinkedList 还实现了 Queue 接口。

  • ArrayList 和 Vector 使用数组存储数据;LinkedList 使用双向链表存储数据。

  • ArrayList 和 LinkedList 是非线程安全的;Vector 是线程安全的。

  • ArrayList 数据增长时空间增长50%;而 Vector 是增长1倍;

  • LinkedList 在添加、删除元素时具有更好的性能,但读取性能要低一些。

  • LinkedList 与 ArrayList 最大的区别是 LinkedList 更加灵活,并且部分方法的效率比 ArrayList 对应方法的效率要高很多,对于数据频繁出入的情况下,并且要求操作要足够灵活,建议使用 LinkedList;对于数组变动不大,主要是用来查询的情况下,可以使用 ArrayList。


二十二、内存相关知识点

Java程序运行时的内存分配策略有三种,分别是静态分配、栈式分配和堆式分配。对应的就是静态存储区、栈区、堆区。

  • 静态存储区:主要存放静态数据、全局static数据和常量。这块内存在程序编译时已经分配确定,并且在程序整个运行期间都会存在;

  • 栈区:当方法执行时,方法体内部的局部变量都在栈上创建,并在方法执行结束时这些局部变量持有的内存将会自动释放

  • 堆区:又称之为动态内存分配,通常指程序运行时直接new出来的内存,也就是对象的实例。这部分内存不使用时会由垃圾回收器进行回收

栈与堆的区别

  • 栈:在方法体内定义的一些基本类型变量和对象引用的变量都是在方法的栈内存中进行分配的。

  • 堆:用来存放所有由new创建的对象和数组。在堆中分配的内存,将由Java垃圾回收器自动管理。当在堆中产生一个数组或者对象后,还可以在栈中定义一个特殊的变量,这个变量取值等于数组或者对象在堆内存中的首地址,这个特殊变量就是引用变量。可以通过引用变量来访问堆中的对象或者是数组。

结论:
  • 局部变量的基本数据类型和引用存储于栈中,引用对象的实体存储于堆中。

  • 成员变量存储于堆中。因为他们属于类,类对象终究是要被new出来使用的。

  • 栈存取速度仅次于寄存器,存储效率比堆高,可以共享存储数据,但是其中数据大小和生存期必须在运行前确定。

  • 堆是运行时可动态分配的数据区,从速度看比栈慢,堆里面的数据不共享大小和生存期都可以在运行时再确定。

Java如何管理内存?

  • Java的内存管理就是对象的内存分配和释放问题。在Java中程序员通过new来为每个对象在堆中申请内存空间,所有的对象都在堆中分配空间。对象的释放由GC决定和执行,由于内存分配和释放由两条线来完成简化了程序员的工作,但是增加了jvm的工作。这也是Java运行速度较慢的原因之一。因为GC为了能够正确释放对象,GC必须监控每一个对象的运行状态,监视对象的目的是为了更加准确及时的释放对象,而释放对象的原则是该对象不被引用。
    为了更好理解 GC 的工作原理,我们可以将对象考虑为有向图的顶点,将引用关系考虑为图的有向边,有向边从引用者指向被引对象。另外,每个线程对象可以作为一个图的起始顶点,例如大多程序从 main 进程开始执行,那么该图就是以 main 进程顶点开始的一棵根树。在这个有向图中,根顶点可达的对象都是有效对象,GC将不回收这些对象。如果某个对象 (连通子图)与这个根顶点不可达(注意,该图为有向图),那么我们认为这个(这些)对象不再被引用,可以被 GC 回收。
    Java使用有向图的方式进行内存管理,可以消除引用循环的问题,例如有三个对象,相互引用,只要它们和根进程不可达的,那么GC也是可以回收它们的。这种方式的优点是管理内存的精度很高,但是效率较低。另外一种常用的内存管理技术是使用计数器,例如COM模型采用计数器方式管理构件,它与有向图相比,精度行低(很难处理循环引用的问题),但执行效率很高。

什么是Java内存泄漏?

  • 这些对象具有如下两个特点。①、这些对象是可达的即在有向图内存在通路与其相连;②、这些对象是无用的,即以后程序不会再使用这些对象。如果对象满足这两个条件,那么我们就可以认为这些对象为Java中的内存泄漏,但是这些对象不会被GC回收,然而占用内存。

Java的内存回收机制

  • 所有的编程语言的内存分配方式都需要返回分配内存的真实地址,也就是返回一个指针到内存块的首地址,Java中对象是采用new或者反射的方式创建的,这些对象的创建都是在堆中分配的,所有对象的回收都是由Java虚拟机通过垃圾回收机制完成的。GC为了能够准确的释放对象会监控每一个对象的运行状况(申请、引用、被引用、赋值),Java使用由向意图的方法进行管理内存,实时监控对象是否可以到达,如果不可到达则进行回收。这样也可以消除引用循环的问题。

判断内存空间是否符合垃圾回收标准?

  • ①、对象被赋予了空值null,再也没有调用过;
    ②、给对象赋予了新值,这样重新分配了内存空间;

Java内存泄漏引起的原因

  • 内存泄漏的定义:无用的对象持续占有内存或无用对象的内存得不到及时释放,从而导致内存空间的浪费。严重的内存泄漏会导致内存溢出(OOM)。

内存泄漏的根本原因:

长生命周期的对象持有短生命周期对象的引用就很可能发生内泄漏。

具体导致内存泄漏的原因:

  • 静态集合引起的内存泄漏:HashMap、Vector等的使用容易出现内存泄漏,这些静态变量的生命周期和应用程序一致,他们所引用的所有的对象Object也不能被释放,因为他们也将一直被Vector等引用着。
    解决方法:及时将集合对象设置为null。

  • 监听器:在释放对象的时候忘记删除对象的监听器,从而增加了内存泄漏的机会。
    解决方法:及时删除监听器

  • 各种连接:比如数据库连接(dataSourse.getConnection()),网络连接(socket)和io连接。
    解决方法:显示调用close()方法,否则是不会自动被GC回收的。

  • 内部类和外部模块的引用:解决方法:细节,时刻提醒提供相应方法去除引用

  • 单例模式:不正确使用单例模式导致。单例对象在初始化后将在jvm的整个生命周期中存在,如果单例对象持有外部类的引用,那么这个对象将不能被JVM正常回收导致内存泄漏。


Java内存回收机制,GC 垃圾回收机制,垃圾回收的优点和原理,并说出3种回收机制。

优点:

  • Java语言显著的特点就是引入了垃圾回收机制,它使程序员在编写程序时不再考虑内存管理问题,直接解放了程序员的大脑;

  • 由于有了垃圾回收机制,Java对象不再有“作用域的概念”,只有引用对象才有“作用域”的概念;

  • 垃圾回收机制有效的防止了内存泄漏,可以有效的使用空闲的内存;

  • 垃圾回收器通常作为一个单独的低级别的线程运行,在不可预知的情况下进行对内存堆中的已经死亡的或者长时间没有用过的对象进行清除和回收。

  • 程序员实时的对某个对象调用垃圾回收器进行垃圾回收。

垃圾回收机制:

  • 分带复制垃圾回收、标记垃圾回收、增量垃圾回收

垃圾回收算法

  • 引用计数算法:缺点是无法处理循环引用问题

  • 标记-清除算法:标记所有从根节点开始的可达的对象。缺点是会造成空间不连续,导致不容易分配内存

  • 标记-压缩算法:标记清除算法的升级版,原理:清除未标记对象时还将所有的存活对象压缩到内存的另一端,之后清理边界所有的空间既避免碎片产生,又不需要两块同样大小的内存块。适用于老年代。

  • 复制算法:将内存空间分为两块,每次将正在使用的内存中存活对象复制到未使用的内存块中,之后清除正在使用的内存块。算法效率高,但是代价是系统内存折半。适用于新生代。

  • 分代

Java虚拟机的特性

  • Java语言的一个非常重要的特性就是与平台无关性,而Java虚拟机是实现这一特性的关键。一般的高级语言在不同的平台上运行,至少需要编译成不同的目标代码。然而引入Java语言虚拟机之后,Java在不同平台运行的时不需要重新编译,Java语言使运行时Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。

哪些情况下的对象会被垃圾回收器回收掉

  • Java的回收机制最基本的做法就是分代回收。内存中的区域被划分为不同的世代,对象根据其存活时间被保存在在不同的世代内。一般被分为三个世代分别为:年轻、年老、永久。内存分配是发生在年轻世代内,对于不同的世代可以才用不同的垃圾回收算法。基于这一点针对世代的垃圾回收算法就很有针对性了。


二十三、多线程

一个线程的生命周期

  • 新建状态:使用new关键字和Thread类或其子类建立一个线程对象,该线程对象就处于新建状态。它保持这个状态直达程序start()这个程序。
    就绪状态:当线程对象调用start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,等待JVM里线程调度器的调度。
    运行状态:如果就绪状态的线程获取CPU资源,就可以执行run()方法,此时的线程处于运行状态。处于运行时的状态最为复杂,可以变为阻塞状态、就绪状态、死亡状态。
    阻塞状态:线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。
    阻塞状态可以分为三种:①、等待阻塞:运行状态中的线程执行wait()方法;②、同步阻塞:线程在获取synchronized同步锁失败; ③、其他阻塞
    死亡状态:一个运行时状态的线程完成任务或者其他终止条件发生时,该线程切换到终止状态。

线程与进程的区别

  • 程序是一段静态的代码,一个进程可以有一个或者多个线程,每个线程都有唯一的标识

    区别:①、进程大体分为数据区、代码区、栈区、堆区。多个进程之间的内部数据和状态是完全独立的;线程共享进程的数据区、代码区、堆区、只有栈区是独立的。所以线程切换代价小于进程切换代价。
    ②、一个程序至少有一个进程,一个进程至少有一个线程;
    ③、线程划分尺度小于进程、多线程的程序并发高;
    ④、进程在执行过程中拥有独立的内存单元,而多线程共享内存,从而极大地提高了程序运行效率;

什么导致线程阻塞

  • 调用sleep(毫秒),使得线程进入睡眠状态;

  • 调用suspend()暂停线程执行。除非线程收到resume()消息否则返回不可运行状态;

  • 调用wait()方法暂停线程执行。除非调用notify()或者notifyAll()消息,否则不可运行;

  • 线程正在等候IO操作的完成;

  • 线程试图调用另一个对象的同步方法,但是那个对象处于锁定状态暂时无法使用。

多线程的实现方法

  • 实现Runnable接口;

  • 继承Thread类本身;

  • 通过callable和Future创建线程(Java 5 之后出现);

实现Runnable接口相比继承Thread类有如下优势

  • 可以避免Java单继承的特性带来的局限性;

  • 增强程序的健壮性,代码可以被多个程序共享,代码与数据是独立的;

  • 适合多个相同程序代码的线程区处理同一资源的情况

同步有几种实现方法,都是什么?

  • 两种   synchronized   wait和notify

锁的等级

  • 方法锁、对象锁、类锁

同步和异步的区别?

  • 同步:A线程请求某资源时,B线程占用该资源,则A只能等待下去

  • 异步:A线程请求某个资源时,B线程占用该资源,则A无需等待可以请求。

sleep和wait的区别

  • ①、sleep:线程休息;wait:线程挂起。
    ②、sleep方法属于Thread类的方法;wait属于Object类方法
    ③、sleep:sleep正在执行的线程主动放出cpu,指定时间后cpu回到该线程继续向下执行。注意sleep只是让出CPU而并不是释放同步资源锁;
    wait:当前线程让自己暂时退让出同步资源锁,以便其他正在等待该资源的线程得到该资源而运行,只有调用notify方法之后调用wait方法的线程才会接触wait状态进而参与竞争同步资源锁,进而执行

Java线程池线程同步

  • 同步操作产生的原因:当多个线程同时访问对象并要求操作相同资源时,分割了原子操作就有可能出现数据的不一致或数据不完整情况,为了避免发生这种情况,我们会采取同步机制。

    synchronized修饰符实现的同步机制叫做互斥锁机制。

    互斥锁:每个对象都有一个锁标记,当线程拥有锁机制的时候才可访问这个资源,没有锁标记便进入锁池。任何一个对象系统都会创建一个互斥锁,这个锁为了分配给线程,防止打断原子操作,每个对象锁只能分配给一个线程。

当一个线程进入一个对象的一个synchronized方法后,其他线程是否可进入此对象的其他方法?

  • 可以调用此对象的其他非synchronized方法;

  • 可以调用此对象synchronized static方法;

线程加锁的作用:

  • 互斥行为和内存可见性

内存可见性定义

  • 某个线程正在使用对象状态而另一个线程在同时修改该状态,当该状态被修改之后可以立刻被其他线程感知该对象的状态改变了

Atomic、volatile、synchronized区别

  • synchronized是解决多线程共享资源的问题同步机制采用了“以时间换取空间”的做法,访问串行化、对象共享化。同步机制是提供一份变量,让所有线程都可以访问。

  • Atomic是通过原子操作指令+Look-Free完成,从而实现非阻塞的并发问题。

  • Volatile是为了多线程资源共享问题解决了部分需求,在非依赖自身的操作的情况下,对变量的改变将对任何线程可见。

  • ThreadLocal是为了解决多线程资源共享问题,用来提供线程内局部变量,省去参数传递这个不必要的麻烦,做到了“以空间换取时间”的方式。

  • 如果是一个类的多个对象想公用对象内部的一个变量,而不想使用static,可以使用浅复制的方式。

并发编程实现内存可见性的方式- - - ->加锁和volatile变量

volatile变量

  • volatile变量是一种脆弱的同步机制,由于访问volatile变量时不会执行加锁操作,因此也就不会执行线程阻塞,因此volatile变量是一种比synchronized关键字轻量级的同步机制;

  • 从内存可见度角度来看,写入volatile操作相对于退出同步代码块,读取volatile变量相对于进入同步代码块;

  • 代码中过度使用volatile编写来控制可见性,通常会比使用锁更加脆弱和难以理解。

  • volatile只能保证可见性,不能保证原子性。

使用volatile的情景:

  • 对变量的读写操作不依赖变量的当前值,或者可以保证只有单个线程更新变量的值;

  • 该变量没有包含在具有其他变量的不变式中。

守护线程

  • 用户线程:运行在前台的线程;

  • 守护线程:运行在后台的线程,为其他前台线程的运行变量提供便利服务的,仅当非守护线程运行时,才需要守护线程例如垃圾回收线程;

    可以人为创建守护线程,通过Thread的setDeamon(ture)方法设置当前线程为守护线程。

    禁止在守护线程内创建耗时操作,比如IO操作
    setDeamon(ture)方法必须在调用线程的start()方法之前设置,否则会抛出IllegalThreadStateException异常。
    守护线程内产生的新线程也是守护线程。

死锁

  • 线程A持有互斥锁lock1,线程B持有互斥锁lock2。当线程A持有lock1时,师徒获取lock2,因为B持有lock2,因此线程A会阻塞等待线程B对lock2的释放。如果此时B在持有lock2的时候也试图获取lock1,因为A持有lock1,因此线程B会阻塞等待A对lock1的释放。二者都在等待对方所持有的锁释放,而二者却又都没释放自己所持有的锁,这时二者会一直阻塞下去,这种情况为死锁现象。

如何避免死锁现象产生

  • 只在必要的最短时间内持有锁,可以考虑使用同步语句块代替整个同步方法;

  • 尽可能不编写在同一时刻需要持有多个锁的代码,如果不可避免则确保线程持有第二个锁的时间尽量短;

  • 创建一个大锁来代替若干个小锁。

可重入内置锁,使用wait/notify/notifyAll实现线程间通信

  • 可以通过调用Object对象的wait()方法和notify()方法或者说notifyAll方法来实现线程间通信。在线程中调用wait()方法,将阻塞等待其他线程的通知,在线程中调用notify()方法或notifyAll方法,将通知其他线程wait()方法处返回。
    notify()、notifyAll()、wait()、wait(long)和wait(long,int),他们都被声明为final,因此在子类中不能覆写任何一个方法。

    如果调用wait()时,没有持有适当的锁,则抛出IllegalMonitorStateException;
    如果调用notify()时没有持有适当的锁,也会抛出IllegalMonitorStateException

    如果线程调用了对象的wait方法,那么线程便会处于对该对象的等待池中,等待池中的线程不会去竞争该对象的锁;
    当线程调用了对象的notiAll方法或者notify方法,被唤醒的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁;
    优先级高的线程竞争到对象锁的概率大,如果没有竞争到对象锁那么他会留在锁池中只有线程再次调用wait方法的时候,它才会再次进入等待池中。竞争到对象锁的线程则会继续向下执行,直到执行完了synchronized代码块,才会释放掉对象锁,此时锁池内线程会继续竞争该对象锁。

Error和Exception的区别

  • Error是Throwable的子类用于标记严重错误,合理的应用程序不可以用try/catch这种错误。绝大多数错误都是非正常的,不应该出现的。

    Exception是Throwable的子类,用于指示合理的程序想去catch的条件,即它仅仅是一种程序运行的条件,而非严重错误。
    checked exceptions: 通常是从一个可以恢复的程序中抛出来的,并且最好能够从这种异常中使用程序恢复。比如FileNotFoundException, ParseException等。
    unchecked exceptions:就是程序中的bug

Java中的异常处理机制的简单原理和应用

  • Java中通过面向对象的方式,把各种异常进行了分类,并且提供了良好的接口,在Java中每一个异常就是一个的对象。当一个方法抛出异常的时候便抛出了一个异常对象,该对象内包含异常的信息,调用这个对象的方法就可以捕获到这个异常并进行处理。

try/catch的执行过程

  • 一般情况由try来执行一段程序,如果出现异常,系统则会抛出一个异常,此时程序员可以通过他的类型进行捕捉(catch操作),或在最后(finally)由缺省处理器来进行处理。

    用try来指定一块预防所有“异常”的程序。紧跟在try程序后面,应包含一个catch子句来指定你想要捕捉的“异常”的类型。throw语句用来明确地抛出一个“异常”。throws用来标明一个成员函数可能抛出的各种“异常”。Finally为确保一段代码不管发生什么“异常”都被执行一段代码。

try{ return} catch{} finally{}; return 还是 finally 先执行?

  • 任何执行try或者catch内的return语句之前,都会先执行finally语句,如果finally存在的话。
    如果finally中有return语句,那么程序就return了,所以finally中的return是一定会被return的

    在try语句中,在执行return语句时,要返回的结果已经准备好了,就在此时,程序转到finally执行了。在转去之前,try中先把要返回的结果存放到不同于x的局部变量中去,执行完finally之后,在从中取出返回结果,因此,即使finally中对变量x进行了改变,但是不会影响返回结果。

猜你喜欢

转载自blog.csdn.net/b13646141775/article/details/79978206