链接一下目录:
菜鸡的2021春招实习之旅(面经+自己总结的笔记)
面向对象的设计思想是将需要解决的问题分解成一个个对象,这一个个对象可以用来描述在完成这件事中扮演的具体角色和行为。
面向过程的设计思想是将需要解决的问题拆分成一个个具体的步骤,然后设置这些步骤的实现方法,然后按照顺序一步步实现即可
优缺点:
面向对象相较于面向过程而言,有着易维护、易复用和易扩展的特点,由于面向对象有封装、继承和多态的特点,所以能够设计出低耦合的系统,易于去维护;而相对面向过程,面向对象性能较低
封装是将对象属性进行私有化,对外提供一个公共的访问方法
封装
封装是将一个对象的属性进行私有化,给外界访问提供一些方法来访问这些私有属性,如果不想外界访问也可以不提供这些方法,但这个类存在也没什么意义了。
继承
继承是使用已存在的类来建立新的类的技术,新的类可以定义新的属性和新的方法,能够使用父类的技术,但是必须完全继承自存父类,不能选择性的继承。通过使用继承我们可以很方便的复用以前的代码
多态
指在程序定义中引用变量所指向的具体类型和通过该引用变量发出的方法具体调用对象在编译的时候并不确定,即引用变量指向哪个类的实例对象,引用对象调用的方法到底是哪个类发出的是并不确定,只有在程序运行时才能确定。
Java实现多态可以是继承和实现
String的不可变性:
String类是已经被声明为final的, 不可被继承
String代表不可变的字符序列(简称不可变性)
StringBuilder 与 StringBuffer 的区别
StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,都有相同的一些方法比如append,indexof等
从线程安全性上来说,String对象是不可变的,可以理解成常量,是线程安全的。StringBuffer对内部方法调用了同步锁,是线程安全的;而StringBuilder没有加锁,是线程不安全的。
从性能上来说,当然是加锁了StringBuffer性能比较慢,而没有加锁的StringBuilder比较快
对于三者使用的总结:
装箱:将基本类型用它们对应的引用类型包装起来;比如Integer i = 20;//将20包装成Integer
拆箱:将包装类型转换为基本数据类型;比如int a = i;//将上面的包装类型进行拆箱
使用integer时,数值在[-128,127]之间,便返回指向IntegerCache.cache中已经存在的对象的引用;否则创建一个新的Integer对象。
public class Main {
public static void main(String[] args) {
Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;
System.out.println(i1==i2);//true,指向同一个对象
System.out.println(i3==i4);//false指向不同对象
}
}
public class Test1 {
public static void main(String[] args) {
Double i1 = 100.0;
Double i2 = 100.0;
Double i3 = 200.0;
Double i4 = 200.0;
System.out.println(i1==i2);//false,直接创建两个不同对象
System.out.println(i3==i4);//false,直接创建两个不同对象
}
}
注意,Integer、Short、Byte、Character、Long这几个类的valueOf方法的实现是类似的。Double、Float的valueOf方法的实现是类似的。
由于静态方法可以不通过对象进行调用,因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态变量成员。
一个非静态成员在未实例化前是不存在的,静态方法是不用实例化就可以调用的,所以调用不到非静态变量、成员和方法。
如果有该类有子类,就会在调用子类实例过程,在构造方法中默认调用super()。如果父类没有无参数构造方法,只有有参数构造方法,父类就不会默认创建一个无参构造方法,导致super()调用失败。
java和javax都是Java的API包,java为baijava语言的核du心包,javax为java语言的扩展包。
java包是java基础zhi核心类库,也就是Java Development kit ,提供java语言编程核dao心包,如io、awt、集合库(如Collection、List、Map)等;
javax是java基础上的扩展包,如swing、servlet、jsp、xml等类库。
抽象类中不可以定义静态的抽象方法可以定义静态的实例方法
总结一下 jdk7~jdk9 Java 中接口概念的变化:
1.成员变量属于类,局部变量属于方法。成员变量可以使用访问权限符和static来修饰,而成员变量不行。他们都可以用final来修饰
2.存储方式也有区别。如果成员变量用static修饰,这个成员变量是属于类的,是类成员变量;如果没有用static,这个成员变量是属于实例的,只有实例对象可以使用。局部变量为基本数据类型,则存储在栈内存内,如果是引用数据类型,则存储堆中引用对象的地址或者是指向常量池的地址
3.从生命周期来看,成员变量和实例对象是一样的;局部变量随着方法的生命周期一致。
4.成员变量会被默认赋值,而局部变量不会默认赋值,必须手动赋值
new运算法。new的引用放在栈中,new的实体放在堆内存中。一个对象引用可以指向0个或者1个对象实例,一个对象实例可以被0-n个对象引用指向
方法的返回值是指我们获取到的某个方法体中的代码执行后产生的结果。返回值就是一个值,可以用于操作运算和赋值
对一个类进行初始化。能。默认会创建一个无参构造方法
1.构造方法名字和类相同
2.没有返回值,但是不能用void
3.产生实例的时候自动调用
1.调用方式来说,实例方法的调用必须通过实例后的对象.方法进行调用,而静态方法无序实例化,直接类名.方法就可以调用,也可以通过对象.方法进行调用
2.静态方法只允许调用本类的静态方法和静态变量,因为执行前就已经初始化;而实例方法无限制
对象的相等是内存中的内容是否相等,而引用相等是地址是否相等
帮助子类做初始化工作
==:
如果==比较的是基本数据类型,那就是比较他们的值是否相等。
如果==比较是的引用数据类型,就是比较他们的地址是否相等。
equals():
比较两个对象是否相等。如果没有重写equals,作用和==类似。如果重写了equals,一般重写都是比较内容了
拿String来说:
面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写 equals 时必须重写 hashCode 方法?”
hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个 int 整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在 JDK 的 Object.java 中,这就意味着 Java 中的任何类都包含有 hashCode() 函数。
散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)
我们先以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode: 当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与该位置其他已经加入的对象的 hashcode 值作比较,如果没有相符的 hashcode,HashSet 会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()
方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的 Java 启蒙书《Head first java》第二版)。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。
通过我们可以看出:hashCode()
的作用就是获取哈希码,也称为散列码;它实际上是返回一个 int 整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode()
在散列表中才有用,在其它情况下没用。在散列表中 hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。
public class Student {
int age;
String name ;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(age, name);
}
}
方法参数是基本数据类型的话,就是将数据进行拷贝后执行方法体;如果是引用数据类型,就可以改变指向的具体内存中的数据。
值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量.
引用传递一般是对于对象型变量而言的,传递的是该对象地址的一个副本, 并不是原对象本身 。 所以对引用对象进行操作会同时改变原对象.一般认为java内的传递都是值传递.
线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
程序是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。
进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如 CPU 时间,内存空间,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。
1.初始状态:线程被构建,未调用start()方法
2.运行状态:java将运行和就绪都成为运行状态
3.阻塞状态:表示线程被阻塞
4.等待状态:表示该线程进入等待,需要线程通知或中断
5.超时等待状态:不同于等待,可以在指定的时间自行返回
6.终止状态:表示当前线程已被执行完毕
当线程执行 wait()
方法之后,线程进入 **WAITING(等待)**状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而 TIME_WAITING(超时等待) 状态相当于在等待状态的基础上增加了超时限制,比如通过 sleep(long millis)
方法或 wait(long millis)
方法可以将 Java 线程置于 TIMED WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 BLOCKED(阻塞) 状态。线程在执行 Runnable 的run()
方法之后将会进入到 TERMINATED(终止) 状态。
final主要用于变量、方法和类中
1.变量中:被final修饰的基本数据类型变量必须被显式地初始化,且初始化以后不能被改变;被final修饰的引用变量在初始化以后不能指向别的对象。
2.方法中:被final修饰的方法无法被其子类重写;同时能够在一定程度上提升方法执行效率。类中所有的private方法被隐式定义为了final方法
3.类中:被final修饰的类无法被继承,类中的所有成员方法都默认修饰final
在 Java 中,所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类。Throwable: 有两个重要的子类:Exception(异常) 和 Error(错误) ,二者都是 Java 异常处理的重要子类,各自都包含大量子类。
Error(错误):是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java 虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。
这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如 Java 虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java 中,错误通过 Error 的子类描述。
Exception(异常):是程序本身可以处理的异常。Exception 类有一个重要的子类 RuntimeException。RuntimeException 异常由 Java 虚拟机抛出。NullPointerException(要访问的变量没有引用任何对象时,抛出该异常)、ArithmeticException(算术运算异常,一个整数除以 0 时,抛出该异常)和 ArrayIndexOutOfBoundsException (下标越界异常)。
注意:异常和错误的区别:异常能被程序本身处理,错误是无法处理。
在以下 4 种特殊情况下,finally 块不会被执行:
序列化:将java对象转换成字节序列;反序列化:将字节序列恢复成java对象
对于不想进行序列化的变量,使用 transient 关键字修饰。
transient 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。transient 只能修饰变量,不能修饰类和方法。
Scanner和BufferedReader都可以读入键盘输入
方法 1:通过 Scanner
Scanner input = new Scanner(System.in);
String s = input.nextLine();
input.close();Copy to clipboardErrorCopied
方法 2:通过 BufferedReader
BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
String s = input.readLine();
IO流分类
基类是InputStream、Reader、OutputStream、Writer
既然有了字节流,为什么还要有字符流?
问题本质想问:不管是文件读写还是网络发送接收,信息的最小存储单元都是字节,那为什么 I/O 流操作要分为字节流操作和字符流操作呢?
回答:字符流是由 Java 虚拟机将字节转换得到,需要->字节流->另一端接收->转换成字符流读取。所以如果仅涉及文本内容的I/O流,直接使用字符流会更加方便,也不会产生乱码。
同步与异步
同步和异步的区别最大在于异步的话调用者不需要等待处理结果,被调用者会通过回调等机制来通知调用者其返回结果。
阻塞和非阻塞
BIO、NIO、AIO区别
Socket
和 ServerSocket
相对应的 SocketChannel
和 ServerSocketChannel
两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞 I/O 来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发简单来说,如果是对于基本数据类型而言,深拷贝和浅拷贝都仅仅是对值进行复制;而对于引用类型,深拷贝是拷贝一个内容完全一样的对象,而浅拷贝仅仅是拷贝地址。比如一个Int类型数据,无论深浅拷贝,都是拷贝这个数具体值,而例如一个int数组{1,2,3},深拷贝拷贝一个{1,2,3}的数组,地址不一致,浅拷贝则是拷贝数组地址
这里其实有一个概念,就是引用拷贝,比如我直接用一个指针指向一个对象,那么其实他是引用拷贝
浅拷贝就已经是两个不同的指针了,但是其实指向的是同一个内存区域,但是指针是不同的
深拷贝就是具体将里面所有的东西都进行clone
拷贝是需要继承cloneable接口的
java序列化就是将对象转换成字节序列。实现方法就是让对应对象实现serialization接口,是一个标记性接口,不需要重写方法,然后构造一个ObjectOutPutStream对象输出流,使用writeObject方法将Obj写成字节序列
Lambda 表达式 :Lambda允许把函数作为一个方法的参数
方法引用: 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
默认方法:默认方法就是一个在接口里面有了一个实现的方法。
新工具:新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。
优点:
缺点:
java序列化就是把对象改成二进制的过程,可以保存到磁盘或者网络发送
1.实现java.io.serializable
2.java.io.externalizable
3.ObjectInputStream
4.ObjectOutputStream
1.变量声明为静态变量 static
2.变量声明为瞬态变量 transient
静态变量反序列化是直接从内存中拿值
修改属性,会报异常InvalidClassException无效类,因为每次修改会使得serialVersionUID改变,这时的办法是自己定义
==比较的是值,如果是基本数据类型,就是比较具体的数值.
如果比较的是引用数据类型,那么比较的是地址值
equals是Object中的方法,默认也是比较地址,效果和==是一样的,需要进行重写
public static void main(String[] args) {
String s1 = new String("zs");
String s2 = new String("zs");
System.out.println(s1 == s2);
String s3 = "zs";
String s4 = "zs";
System.out.println(s3 == s4);
System.out.println(s3 == s1);
String s5 = "zszs";
String s6 = s3 + s4;
System.out.println(s5 == s6);
final String s7 = "zs";
final String s8 = "zs";
String s9 = s7 + s8;
System.out.println(s5 == s9);
final String s10 = s3 + s4;
System.out.println(s5 == s10);
}
@Test
public void test2(){
String s1 = "javaEE";
String s2 = "hadoop";
String s3 = "javaEEhadoop";
String s4 = "javaEE" + "hadoop";//编译期优化
//如果拼接符号的前后出现了变量,则相当于在堆空间中new String(),具体的内容为拼接的结果:javaEEhadoop
String s5 = s1 + "hadoop";
String s6 = "javaEE" + s2;
String s7 = s1 + s2;
System.out.println(s3 == s4);//true
System.out.println(s3 == s5);//false
System.out.println(s3 == s6);//false
System.out.println(s3 == s7);//false
System.out.println(s5 == s6);//false
System.out.println(s5 == s7);//false
System.out.println(s6 == s7);//false
//intern():判断字符串常量池中是否存在javaEEhadoop值,如果存在,则返回常量池中javaEEhadoop的地址;
//如果字符串常量池中不存在javaEEhadoop,则在常量池中加载一份javaEEhadoop,并返回次对象的地址。
String s8 = s6.intern();
System.out.println(s3 == s8);//true
}
@Test
public void test3(){
String s1 = "a";
String s2 = "b";
String s3 = "ab";
/*
如下的s1 + s2 的执行细节:(变量s是我临时定义的)
① StringBuilder s = new StringBuilder();
② s.append("a")
③ s.append("b")
④ s.toString() --> 约等于 new String("ab")
补充:在jdk5.0之后使用的是StringBuilder,在jdk5.0之前使用的是StringBuffer
*/
String s4 = s1 + s2;//
System.out.println(s3 == s4);//false
}
/*
1. 字符串拼接操作不一定使用的是StringBuilder!
如果拼接符号左右两边都是字符串常量或常量引用,则仍然使用编译期优化,即非StringBuilder的方式。
2. 针对于final修饰类、方法、基本数据类型、引用数据类型的量的结构时,能使用上final的时候建议使用上。
*/
@Test
public void test4(){
final String s1 = "a";
final String s2 = "b";
String s3 = "ab";
String s4 = s1 + s2;
System.out.println(s3 == s4);//true
}
final用来修饰类表示类不可变,不可以继承
final修饰方法,表示该方法不可以重写
final修饰变量,表示这个变量是常量
如果修饰的是基本数据类型,这个值本身不能改变;如果修饰的是引用类型,引用的指向不能修改
String 跟其他两个类的区别是
String是final类型,每次声明的都是不可变的对象,
所以每次操作都会产生新的String对象,然后将指针指向新的String对象。
StringBuffer,StringBuilder都是在原有对象上进行操作
所以,如果需要经常改变字符串内容,则建议采用这两者。
StringBuffer vs StringBuilder
前者是线程安全的,后者是线程不安全的。
线程不安全性能更高,所以在开发中,优先采用StringBuilder.
StringBuilder > StringBuffer > String
什么叫线程安全和不安全?
如果一个字符串,在多线程环境下对这个对象的访问不需要加入额外的同步控制,操作结果依然正确,说明是线程安全的.StringBuffer里面的方法都选了synchronized修饰
如果是在方法内使用字符拼接,如果这个字符串不需要做参数传递,也就是在之后不会被用到,就可以用builder加快速度.
语法:
JDK1.8以前
JDK1.8以后
接口页可以有实现的方法,不过要在方法的声明上加上default或者是static
设计:
抽象类:同一类事物的抽取,比如针对Dao层操作的封装,如,BaseDao,BaseServiceImpl
接口:通常更像是一种标准的制定,定制系统之间对接的标准
多继承,多重继承,多实现
总结:
- 抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象。
- 抽象类要被子类继承,接口要被类实现。
- 接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。
- 抽象类里可以没有抽象方法。
- 接口可以被类多实现(被其他接口多继承),抽象类只能被单继承。
- 接口中没有
this
指针,没有构造函数,不能拥有实例字段(实例变量)或实例方法。- 抽象类不能在Java 8 的 lambda 表达式中使用
Integer i1 = new Integer(12);
Integer i2 = new Integer(12);
System.out.println(i1 == i2);
Integer i3 = 126;
Integer i4 = 126;
int i5 = 126;
System.out.println(i3 == i4);
System.out.println(i3 == i5);
Integer i6 = 128;
Integer i7 = 128;
int i8 = 128;
System.out.println(i6 == i7);
System.out.println(i6 == i8);
new:一旦new,就是开辟一块新内存,结果肯定是false
不new:
看范围
Integer做了缓存,-128至127,当你取值在这个范围的时候,会采用缓存的对象,所以会相等,具体可以看下面的源码
当不在这个范围,内部创建新的对象,此时不相等
实际比较的是数值,Integer会做拆箱的动作,来跟基本数据类型做比较
此时跟是否在缓存范围内或是否new都没关系
源码分析:
当我们写Integer i = 126,实际上做了自动装箱:Integer i = Integer.valueOf(126);
分析这段源码
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
//IntegerCache是Integer的内部类
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
重载:发生在一个类中,方法名相同,参数列表不同.(注意和返回类型是没有关系的)如果方法名相同,参数列表相同,返回值类型不同,则直接报错,因为会认为是统一方法.
重写:发生在父类与子类之间,方法名相同,参数列表相同.
当执行序列化时,我们写对象到磁盘中,会根据当前这个类的结构生成一个版本号ID,当反序列化时,程序会比较磁盘中的序列化版本号ID跟当前的类结构生成的版本号ID是否一致,如果一致则反序列化成功,否则,反序列化失败.
也就是说在版本升级的时候使用,能够用来确定类的唯一性,如果当前版本号不同,说明类发生改变,反序列化失败.
异常体系分为Error和Exception
Error一般是JVM异常,比如OOM和StackOverflow
exception分为运行时异常和非运行时异常
运行时异常就是我们通常说的logic异常
就是在编写程序的时候会出现的异常,比如空指针异常,索引越界异常,这种不能进行trycatch处理,需要去修改程序.
算数异常,
空指针,
类型转换异常,
数组越界,
NumberFormateException(数字格式异常,转换失败,比如“a12”就会转换失败)
而非运行时异常是客观因素产生的,如果FileNotFound或者是一些别的客观异常,提前做好处理也是java程序健壮性的一种表现.
IOException,
SQLException,
FileNotFoundException,
NoSuchFileException,
NoSuchMethodException
throw,作用于方法内,用于主动抛出异常
throws, 作用于方法声明上,声明该方法有可能会抛些某些异常
1.继承Thread
2.实现Runable接口
3.实现Callable接口(可以获取线程执行后的返回值)
run()并不是开启一个线程,而是一个方法
只有new Thread(? extend Runable) .start()才是建立一个线程
本质上都是继承Thread来new一个线程
在开发中使用线程池比较多
Runable接口和Callable最大区别是Callable可以有返回值,在Callable的泛型加上返回值类型
new,runnable,blocked,waiting,timed waiting,terminated
**六大状态:**NEW、RUNNABLE、BLOCKED、WAITIING、TIME_WAITING、TERMINAED
1,当进入synchronized同步代码块或同步方法时,且没有获取到锁,线程就进入了blocked状态,直到锁被释放,重新进入runnable状态
2,当线程调用wait()或者join时,线程都会进入到waiting状态,当调用notify或notifyAll时,或者join的线程执行结束后,会进入runnable状态
3,当线程调用sleep(time),或者wait(time)时,进入timed waiting状态,
当休眠时间结束后,或者调用notify或notifyAll时会重新runnable状态。
4,程序执行结束,线程进入terminated状态
blocked,waiting,timed waiting 我们都称为阻塞状态
上述的就绪状态和运行状态,都表现为runnable状态
1,定义在不同的类上
sleep方法定义在Thread类中
wait方法定义在Object类中
为什么wait要定义在Object中,而不定义在Thread中?
在同步代码块中,我们说需要一个对象锁来实现多线程的互斥效果,也就是说,Java的锁是对象级别的,而不是线程级别的。
2.对锁资源的处理方式不同
sleep不会释放锁
wait会释放锁
3.写在不同的代码域
sleep可以使用在任何代码块
wait必须在同步方法或者同步代码块中执行
4.方法使用区别
sleep会进入time waiting状态,到时间解除sleep
wait会进入waiting状态,可以用时间,或者使用notify或者notifyAll方法进行唤醒
为什么wait必须写在同步代码块中?
原因是避免CPU切换到其他线程,而其他线程又提前执行了notify方法,那这样就达不到我们的预期(先wait再由其他线程来唤醒),所以需要一个同步锁来保护
类加载机制:
优点:
沙箱安全机制
自定义String类是先使用引导类加载器进行加载,先加载自带的String,进而保护核心源代码,这就是沙箱安全机制。
并发:
并行: