Java基础知识

一、Java相关概念

1、Java特点

  • 面向对象,语法简单;
  • 在语言定义阶段、字节码检查阶段、程序执行阶段进行三级代码安全检查机制;
  • 与平台无关:跨平台;
  • 解释、和编译两种运行方式;
  • 多线程:
    • Java内置了语言级多线程功能,支持多线程;
  • 动态执行:
    • Java执行代码是在运行时动态载入的;

2、Java 虚拟机

Java虚拟机(Java virtual machine,JVM),编译后的Java程序指令不直接在硬件系统的CPU中执行,而是有JVM执行。
JVM的具体实现包括:

  • 指令集(等价于CPU的指令集)

  • 寄存器组

  • 类文件格式

  • 垃圾收集堆

  • 内存区。

    JVM的代码格式为压缩的字节码,Java主要的类型检查是在编译时由字节码校验器完成的。
    JVM的实现叫做Java运行时系统或者运行时环境(runtime environment),即运行时。

3、Java虚拟机的性能

在程序执行时,Hotspot对每个字节码执行进行分析,根据其执行次数,动态决定它的执行方式。多次重复执行的指令。则立即编译为可执行代码,否则,使用解释执行的方式。

4、垃圾收集

内存漏洞:内存得不到释放,系统中没有内存可用时程序就会崩溃。
Java中的垃圾收集是自动进行的。

5、Java的组成部分

  1. Java解释器的三项主要工作:

    • 下载代码:由类下载器完成;
    • 校验代码:由字节码校验器完成;
    • 运行代码:由运行时解释器完成;
  2. 类下载器:
    Java运行时系统区别对待来自不同源的类文件。类下载器把本地文件系统的类名空间和网络源输入的类名空间区分开来,以增加安全性。

  3. 字节码校验器:
    通过网络传送的所有类文件都要经过字节码校验器的检验。校验器主要检查;

    • 类遵从JVM的类文件格式;
    • 不出现访问违例情况;
    • 代码不会引起运算栈溢出;
    • 所有运算代码的参数类型正确;
    • 不会发生非法数据转换;
    • 对象域访问是合法的;

6、Jdk安装后的目录结构

  • bin:Java开发工具,包括Java编译器、解释器等;
    • javac:Java编译器,用来将Java程序编译成字节码;
    • java:Java解释器,执行已经转换成字节码的Java应用程序;
    • jdb:Java调试器,用来调试Java程序;
    • javap:反编译,将类文件还原回方法和变量;
    • javadoc:文档生成器,创建HTML文件;
    • appletviewer:Applet解释器。
  • demo:示例程序;
  • lib:Java开发类库;
  • jre:Java运行环境,包括Java虚拟机、运行类库等;

二、Java 基本数据类型

  • 数据类型:
    • 基本数据类型
      • 数值类型
        • 整数类型:byte、short、int、long
        • 浮点数类型:float、double
      • 字符类型:char
      • 布尔类型:boolean
    • 复合数据类型:
      • 类类型:class
      • 数组:
      • 接口类型:interface

数据类型-201972012037

三、面向对象(OOP)

面向对象的概念:对象、抽象数据类型、类、类型层次(子类)、继承性、多态性。
面向对象的方法学包括:

  • 面向对象的分析(object-oriented analysis,OOA);
  • 面向对象的设计(object-oriented design,OOD);
  • 面向对象的程序设计(object-oriented program,OOP);

OOP把问题看成是相互作用的事物的集合,用属性来描述事物,对事物的操作定义为方法,事物被称为对象,把属性称为数据,即对象=数据+方法。对象在程序中通过抽象数据来描述,这个抽象数据类型就是类。

OOP的三大技术:

  • 封装:将数据和对数据的操作捆绑在一起成为类,即封装;
  • 继承:将一个已有类中的数据和方法保留,并加上自己特殊的数据和方法,从而构建成一个新类,即继承;
    • C++具有多重继承能力,而Java只允许单重继承;
    • Java的多重继承能力可以通过接口来实现。
  • 多态:在一个类或多个类中,可以让多个方法使用同一个名字,从而具有多态性。

1、Object 类

Object类是Java中所有类的父类,Object是唯一没有父类的类。

2、Java中的限定修饰符

  • public:可以被其他任何对象访问;
  • private:类中的private成员只能被这个类本身访问,类外不可见;
  • protected:只可以被同一包及其子类的实例对象访问;
  • friendly:限定符如果不写的话,默认就是friendly,可以被其所在包中的各类访问;
类型 无修饰符 private protected public
同一类
同一包中的子类
同一类中的非子类
不同包中的子类
不同包中的非子类

3、与OOP有关的关键字

  • static:可修饰数据成员,也可修饰成员方法,表明其索命的对象是静态的。
    静态成员与类相对应,被类的所有对象共享,定义了类之后即已存在;类中定义的公有静态变量相当于全局变量。
  • final:用final修饰的类不能再派生子类。
  • abstract:用abstract可以修饰类或成员方法,表明被修饰的成分是抽象的;
  • this:指代本类;
  • super:指代父类;

对象引用:Java系统在内存中为实例分配相应的空间后会将存储地址返回,称此存储地址为对象的引用,也称为引用变量。

四、运算符

  • 算数运算符:加(+)、减(-)、乘(*)、除(/)、取模(%);
  • 关系运算符:大于(>)、大于等于(>=)、小于(<)、小于等于(<=)、等于(==)、不等于(!=);
  • 逻辑运算符:逻辑与(&)、逻辑或(||)、逻辑非(!)
  • 位运算符: 只能操作整型和字符型数据进行操作。
    • 按位取反(~):
    • 按位与(&):
    • 按位或(|):
    • 异或(^):
    • 右移(>>):右每移一位,相当于被2除一次。128>>1得到64
    • 左移(<<):
    • 无符号右移(>>>):逻辑右移,左侧空位用0填充;
      算数右移不改变原数的符号,逻辑右移不能保证这一点。
  • 扩展赋值运算符(+=、-=、*=、/=、%=、&=、|=、^=、>>=、<<=、>>>=)
  • 条件运算符:(?:)
  • 点运算符:(.)
  • 实例运算符:(instanceof)
  • new 运算符
  • 数组下标运算符:([])

五、数组、向量和字符串

1、数组初始化

静态初始化(定义时初始化)和动态初始化;

  • int[] ages = {12,13};
  • String names[] = {"John","June"}

2、多维数组

  • int intArray[][];
  • int intArray[][] = {{2,3},{1,2}};

数组复制:System.arraycopy

六、向量类Vector

Vector:允许不同类型的元素共存于一个变长数组中,是可变大小的数组。
使用场景:

  • 需要处理的对象数目不定;
  • 需要将不同类的对象组合成一个数据序列;
  • 需要做频繁的对象序列中元素的插入和删除;
  • 经常需要定位序列中的对象和其他查找操作;
  • 在不同的类之间传递大量的数据。
    局限性:
    其中的对象不能是简单数据类型;

1、==和equals方法的区别

“==”判定两字符串对象是否是同一实例,即它们在内存中的存储空间是否相同。

2、StringBuffer

StringBuffer用来处理可变字符串。

七、对象和类

1、按值传递

Java只“按值”传送自变量,即方法调用不会改变自变量的值。 当对象实例作为自变量传送给方法时,自变量的值是对对象的引用,也就是说,传递给方法的是引用值。在方法内,这个引用值是不会变化的,但可以修改该引用指向的对象内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class PassTest {
float ptValue;

public static void main(String[] args) {
String str;
int val;

PassTest pt = new PassTest();

val = 11;

pt.changeInt(val);

System.out.println("Int Value is:" + val);

str = new String("hello");

pt.changeStr(str);

System.out.println("Str Value is:" + str);

pt.ptValue = 101f;

// 通过对象引用改值
pt.changeObjValue(pt);

System.out.println("Current ptValue is:" + pt.ptValue);
}

public void changeInt(int value) {
value = 55;
}

public void changeStr(String value) {
value = new String("different");
}

public void changeObjValue(PassTest ref) {
ref.ptValue = 99f;
}
}

运行结果:

1
2
3
4
5
Int Value is:11
Str Value is:hello
Current ptValue is:99.0

Process finished with exit code 0

2、重载

重载:允许对多个方法使用同一个方法名。方法之间区别于:

  • 方法名;
  • 参数列表;
  • 返回值;

3、对象的构造和初始化

调用构造方法时,步骤如下:

  1. 分配新对象的空间,并进行默认的初始化;
  2. 执行显式的成员初始化;
  3. 执行构造方法。
    构造方法不能说明为native、abstract、synchronized或final,也不能从父类继承构造方法。

构造方法的特性总结如下:

  • 构造方法的名字与类名相同;
  • 没有返回值类型;
  • 必须为所有的变量赋初值;
  • 通常要说明为public类型的,即共有的;
  • 可以按需包含参数列表。

每个类必须有一个构造方法,默认构造方法的参数列表及方法体均为空。

4、finalize()方法

finalize() 方法属于Object类,它可被所有类使用。在对对象实例进行垃圾收集之前,Java自动调用对象的finalize()方法,它相当于C++中的析构方法,用来释放对象所占用的系统资源。

5、单重继承

一个类如果有父类,则其父类只能有一个。

6、异类集合

异类集合是由不同质内容组成的集合,即集合内所含元素的类型可以不完全一致。

7、instanceof运算符

由于类的多态性,类的变量既可以指向本类实例,又可以指向其子类的实例。需要判明某个引用到底执行哪个实例,通过instanceof运算符来实现。

8、转换对象

如果用instanceof运算符已判明父类的引用指向的是子类实例,就可以转换该引用。

  • 沿类层次向上转换总是合法的;
  • 对于向下转换,只能是父类到子类转换,其他类之间是不允许的。

9、方法重写

子类中定义方法所用的名字、返回类型及参数表和父类中方法使用的完全一样,称子类方法重写了父类中的方法,逻辑上来看,也就是子类中的成员方法将隐藏父类中的同名方法。

重写方法的目的:既可以是取代或修改原有的方法,也可以是要在某些方面进行扩展,或者加以改进。

重写的同名方法中,子类方法不能比父类方法的访问权限更严格。

面向对象语言的一个重要特性:要执行的是与对象真正类型(运行时类型)相关的方法,而不是与引用类型(编译时类型)相关的方法。这也是多态的另一个重要性质,称作虚方法调用。

示例:

Employee.java

1
2
3
4
5
6
7
8
public class Employee {
String name;
int salary;

public String getDetails() {
return "Name:" + name + "\n" + "Salary:" + salary;
}
}

Manager.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Manager extends Employee {
String department;

@Override
public String getDetails() {
return "Name:" + name + "\n" + "Manager of " + department;
}

public static void main(String[] args) {
Employee e = new Manager();
String details = e.getDetails();
System.out.println(details);
}
}

运行结果:可以看到执行的是Manager类中的方法,因为实例的真正类型是Manager。

1
2
Name:null
Manager of null

注:如果子类已经重写了父类中的方法,但在子类中想使用父类中被隐藏的方法,可以使用super关键字。

10、重载和重写的区别。

方法名相同,参数表不同,则是对方法的重载。重载的方法属于同一个类,而重写的方法分属于父、子类中。

  • 重写方法的允许访问范围不能小于原方法;
  • 重写方法所抛出的异常不能比原方法更多。

11、父类构造方法调用

注意点:

  • 一个父类的对象要在子类运行前完全初始化。
  • 子类不能从父类继承构造方法;
  • 如果在子类的构造方法中没有明确调用父类的构造方法,则系统在执行子类的构造方法时会自动调用父类默认的构造方法(即无参构造方法);
  • 如果在子类的构造方法的定义中调用了父类的构造方法,则调用语句必须出现在子类构造方法的第一行。

八、Java包

一个Java源代码文件称为一个编译单元,一个编译单元中只能有一个public类,且该类名与文件名相同。

包是类的容器,用于分隔类名空间,避免类名冲突。

1、classpath环境变量

在文件系统的目录中,环境变量classpath将指示着javac编译器如何查找所需要的对象,classpath是设置的环境变量。

2、封装

  • 指对象的全部属性数据和对数据的全部操作结合在一起,形成一个统一体(即对象);
  • 尽可能地隐藏对象的内部细节,只保留有限的对外接口,对数据的操作都通过这些接口实现。

九、类成员

  • 类变量:static修饰的变量,类中所有对象共享的变量;
  • 类方法:静态方法,static修饰的方法;

十、关键字final

1、 终极类

终极类:使用final修饰的,不能被继承的类;

2、终极方法

终极方法:使用final修饰的,不能被重写的方法;

3、终极变量

终极变量:标记为final的变量就变成一个常量,企图改变终极变量的取值将引起编译错误;

如果将一个引用类型的变量标记为final,那么这个变量将不能再指向其他对象,但它所指对象的取值仍然是可以改变的。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Car {
int number = 1234;

public static void main(String[] args) {
final Car myCar = new Car();

myCar.number = 8888;// 可以

// myCar = new Car();// 错误

System.out.println(myCar.number);// 8888
}
}

十一、抽象类

abstract修饰的类。
抽象类的子类所继承的抽象方法同样还是抽象方法,因此必须提供其父类中所有抽象方法的实现代码,否则它还是抽象类。

注意:一个抽象类中可以包含非抽象方法和成员变量。即:包含抽象方法的类一定是抽象类,但抽象类中的方法不一定都是抽象方法。

抽象方法,用abstract修饰的方法。

十二、接口

  • 接口(interface)是抽象类功能的另一种实现方法;
  • 接口中的多有方法都是抽象方法,都没有方法体,因此可以把接口看成特殊的抽象类;
  • Java中的多重继承的变种实现方法,Java中允许一个类实现(implements)多个接口,从而实现了比多重继承更加强大的能力,并具有更加清晰的结构;

1、接口的实现与类的继承区别

实现接口的类不从该接口的定义中继承任何行为,在实现该接口的类的任何对象中都能够调用这个接口中定义的方法。在实现的过程中,这个类还可以同时实现其他接口。
要实现接口,可在一个类的声明中用关键字implements表示该类已经实现的接口。实现接口的类必须实现接口中的所有抽象方法。

Java中可以通过在implements后面声明多个接口名来同时实现多个接口,也就是一个类可以实现多个接口。

十三、内部类

内部类,也被称为嵌套类。

1、 内部类的属性

  • 类名只能在定义的范围内被使用,内部类的名称必须区别于外部类;
  • 内部类可以使用外部类的类变量和实例变量,也可使用外部类的局部变量;
  • 内部类可以定义为abstract类型;
  • 内部类也可以是一个接口,这个接口必须由另一个内部类来实现;
  • 内部类可以被定义为private或者protected类型。当一个类中嵌套另一个类时,访问保护并不妨碍内部类使用外部类的成员;
  • 被定义为static型的内部类将自动转换为顶层类,它们不能再使用局部范围中或其他内部类中的数据和变量;
  • 内部类不能定义static型成员,而只有顶层类才能定义static型成员。如果内部类需要使用static型成员,这个成员必须在外部类中加以定义。

十四、匿名类

在定义一个内部类时,也可以将整个类的描述包含在一个表达式的范围里,使用这种方法是在定义了一个匿名类的同事创建了一个对象。

十五、包装类

Java使用基本类型及类和对象来表示数据,管理的数据只有两类,即基本类型值及对象引用。
java.lang包中的包装类Byte、Short、Integer、Long、Float、Double、Character、Boolean、Void。

十六、异常

异常

Java把程序运行中可能遇到的错误分为两类,

  • 非致命性的,通过修正后程序还能继续执行,这类错误称为异常,也称为例外;
  • 致命性的,即程序遇到了非常严重的不正常状态,不能简单地恢复执行,这就是错误。

异常的使用场景:

  1. 当方法因为自身无法控制的原因而不能完成其任务;
  2. 文件不存在,网络连接无法建立等;
  3. 处理在方法、类库、类中抛出的异常,如FileInputStream.read产生IOException;
  4. 在大的项目中采用统一的方式处理异常时;
  5. 编写文字处理器一类的程序等;
  6. 不经常发生但却可能发生的故障。

异常处理

  • 第一种方式:
    捕获到所发生的异常类,并进行相应的处理。
1
2
3
4
5
6
7
try {

} catch (ExceptionType1 e) {
// 抛出ExceptionType1异常时要执行的异常
} finally {
// 必须执行的代码
}
  • 第二种方式:
    不在当前方法内处理异常,而是把异常抛出到调用方法中。

公共异常

  1. ArithmeticException
    整数除法中,如果除数为0,则发生该类异常。
  2. NullPointerException
    如果一个对象还没有实例化,那么访问该对象或调用它的方法将导致NullPointerException异常。
  3. NegativeArraySizeException
    创建数组时,如果元素个数是个负数,则会引发此类异常。
  4. SecurityException
    该类异常一般在浏览器内抛出。
  5. ArrayStoreException
    程序试图存取数组中错误的数据类型。
  6. ArrayIndexOutOfBoundsException
    访问数据时,如果数组下标超出长度,则导致数组下标越界异常。
  7. FileNotFoundException
    试图存取一个并不存在的文件时发生该异常。
  8. IOException
    通常的I/O错误。

异常分类

类java.lang.Throwable是使用异常处理机制可被抛出并捕获的所有异常对象的父类。它有三个基本子类。
Throwable-2019724221012

  • Error:表示很难恢复的错误,如内存越界。
  • RuntimeException:用来表示设计或实现方面的问题,如数组越界等。
  • 其他异常表示运行时因环境的影响可能发生并可被处理的问题。

用户自定义的所有异常都必须是Exception子类。

十七、Java数据流

数据流是指一组有顺序的、有起点和终点的字节集合。

输入数据流

Java.io包中所有的输入数据流都是由抽象类InputStream继承而来,并且实现了其中所有方法,包括读取数据、标记位置、重置读写指针、获取数据量等。
输入数据流(input stream)是指只能读不能写的数据流,用于向计算机内输入信息而用。

主要数据操作方法有:

  1. int read() 从输入流中读一个字节的二进制数据,然后以此数据为低位数据,配上一个全零字节,形成一个0~255之间的整数返回。
  2. int read(byte[] b):将多个字节读到数组中,填满整个数组;
  3. int read(byte[] b, int off, int len):从输入流中读取长度为len的数据,从数组b中索引为off的位置开始放置读入的数据,读毕返回读取的字节数;
  4. void close():结束对一个数据流的操作时应该将其关闭,同时释放与该数据流相关的资源。
  5. int available():返回目前可以从数据流中读取的字节数(但实际的读操作所读得的字节数可能大于该返回值)。
  6. long skip(long l):跳过数据流中指定数量的字节不读。返回值表示实际跳过的字节数。
  7. boolean markSupported()
  8. void mark(int markarea)
  9. void reset()

输出数据流

output stream 只能写不能读的流,用于从计算机中输出数据。

  1. void write(int i):将字节i写入到数据流中,它只输出低位字节。抽象方法。
  2. void write(byte b[] ):将数组b[]中的全部b.length个字节写入数据流。
  3. void write(byte b[], int off, int len):将数组b[]中从第off个字节开始的len个字节写入数据流。
  4. void close():当结束对输出数据流的操作时应该将其关闭;
  5. void flush():将不够一个缓冲区单位而留在缓冲区的用flush方法将这部分数据强制提交。

为加快数据传输速度,提高数据输出效率,有时输出数据流会在提交数据之前把所要输出的数据先锁定在内存缓冲区中,然后成批地进行输出,每次传输过程都以某特定数据长度为单位进行传输。

文件数据流

文件数据流包括:FileInputStream和FileOutputStream,这两个类用来进行文件的I/O处理,其数据源或者数据终点都应当是文件。

过滤流

一个过滤器数据流在创建时与一个已经存在的数据流相连,这样在从这样的数据流中读取数据时,它提供的是对一个原始输入数据流的内容进行了特定处理的数据。

  • 缓冲区数据流: BufferedInputStream和BufferedOutputStream,都属于过滤器数据流,都是在数据流上增加了一个缓冲区。
  • 数据数据流:前面的数据流中,处理的数据都是指字节或字节数组,这是进行数据传输时系统默认的数据类型。DataInputStream和DataOutputStream,允许通过数据流来读写Java原始类型,包括布尔型(boolean)、浮点型(float)等。
  • 管道数据流:管道数据流主要用于线程间的通信,一个线程中的PipedInputStream对象从另一个线程中互补的PipedOutputStream一起使用,来建立一个通信信道。管道数据流必须同时具备可用的输入端和输出端。 创建一个通信信道的步骤如下:
    • 建立一个输入数据流: PipedInputStream pis = new PipedInputStream();
    • 建立输出数据流:PipedOutputStream pos = new PipedOutputStream();
    • 将输入数据流和输出数据流连接起来:pis.connect(pos);
  • 对象流:能够输入输出对象的流称为对象流。ObjectOutputStream、ObjectInputStream。

可持久化

记录自己的状态以便将来再生的能力,叫对象的持久性(persistence)。对象通过写出描述自己状态的数值来记录自己的过程叫持久化(或串行化,serialization)。持久化的主要任务是写出对象实例变量的数值,如果变量是另一个对象的引用,则引用的对象也要串行化。
当一个类声明实现Serializable接口时,表明该类加入了对象串行化协议。在Java中,允许可串行化的对象通过对象流进行传输。

对象结构表

串行化只能保存对象的非静态成员变量,而不能保存任何成员方法和静态成员变量,而且保存的只是变量的值,对于变量的任何修饰符都不能保存,访问权限对于数据域的持久化没有影响。

读者和写者

读者(Reader)和写者(Writer)是Java提供的对不同平台之间数据流中数据进行转换的功能。其他程序设计语言使用ASCII字符集,ASCII字符集是以一个字节(byte)(8位)来表示一个字符,所以可以认为一个字符就是一个字节,但Java使用的Unicode是一种大字符集,要用两个字节(16位)来表示一个字符,这是字节与字符就不再一样了。为了实现与其他程序语言及不同平台进行交互,Java必须提供一种16位的数据流处理方案,使得数据流中的数据可以进行与以往16位平台时的相同处理。这种16位方案称作读者和写者,用来在字节流和字符流之间作为中介。

缓冲区读者和缓冲区写者

BufferedReader、BufferedWriter。

十八、文件的处理

File类

随机访问文件 RandomAccessFile

访问随机位置的记录。

十九、线程

线程的结构

  • 虚拟CPU:封装在java.lang.Thread类中,它控制着整个线程的执行;
  • 执行的代码:传递给Thread类,由Thread类控制顺序执行;
  • 处理的数据:传递给Thread类,是在代码执行过程中所要处理的数据。

多线程的优势

  • 多线程编程简单,效率高。使用多线程可以在线程间直接共享数据和资源,而多进程之间不能做到这一点。
  • 适合于开发服务程序,如Web服务、聊天服务等。
  • 适合于开发有多种交互接口的程序,如聊天程序的客户端,网络下载工具。
  • 适合于有人机交互又有计算量的程序,如字处理程序Word、Excel等。

线程的状态

  • 新建(new):线程对象刚刚创建,还没有启动,此时还处于不可运行状态;
  • 可运行状态(runnable):线程已经启动,处于线程的run()方法之中。这种情况下线程可能正在运行,也可能没有运行,只要CPU一空闲,马上就会运行。可以运行但并没在运行的线程都排在一个队列中,这个队列称为就绪队列。
    • 调用线程的start()方法可使线程处于“可运行”状态。
  • 死亡(dead):线程死亡的原因有两个:一是run()方法中最后一个语句执行完毕;二是当线程处于可运行状态时,调用了stop()方法结束了线程的运行。使其进入了死亡状态。
  • 阻塞(blocked):一个正在执行的线程因特殊原因被暂停执行,就进入阻塞状态。阻塞时线程不能进入就绪队列排队,必须等到引起阻塞的原因消除,才可重新进入队列排队。sleep()和wait()就是两个常用的引起阻塞的方法。
  • 中断线程:当run()执行结束返回时,线程自动终止。使用stop()也可以终止线程的执行。interrupt()不仅可中断正在运行的线程,而且也能中断处于blocked状态的线程,此时interrupt()会抛出一个InterruptedException异常。Java提供的几个用于测试线程是否被中断的方法:
    • void interrupt():向一个线程发送一个中断请求,同时把这个线程的“interrupted”状态置为true。若该线程处于“blocked”状态,会抛出InterruptedException异常。
    • static boolean interrupted():检测当前线程是否已被中断,并重置状态“interrupted”值。即如果连续两次调用该方法,则第二次调用将返回false。
    • boolean isInterrupted():检测当前线程是否已被中断,不改变状态“interrupted”值。

创建线程的方法一:继承Thread类

定义一个线程类,继承Thread类并重写run方法。

  1. 从Thread类派生出一个子类,在类中一定要实现run();
  2. 用该类创建一个对象;
  3. 用start()方法启动该线程。

创建线程的方法二:实现Runnable接口

Runnable是Java中用以实现线程的接口,从根本上讲,任何实现线程功能的类都必须实现该接口。前面的Thread类实际上就是因为实现了Runnable接口,所以它的子类才相应具有线程功能的。

Runnable接口中只定义了一个方法就是run()方法,也就是线程体。用Runnable()接口实现多线程时,也必须实现run()方法,也需用start()启动线程,但此时常用Thread类的构造方法来创建线程对象。

两种创建线程方法的使用场景

1. 适用于采用实现Runnable接口方法的情况

因为Java只允许单继承,如果一个类已经继承了Thread,就不能再继承其他类,在一些情况下,这就被迫采用实现Runnable的方法。

2. 适用于采用继承Thread方法的情况

当一个run()方法置于Thread类的子类中时,this实际上引用的是控制当前运行系统的Thread实例。

线程的相关操作方法

  • start():启动线程对象;
  • run():用来定义线程对象被调度之后所执行的操作,必须重写run()方法;
  • yield():强制终止线程的执行;
  • isAlive():测试当前线程是否在活动;
  • sleep(int millsecond):使线程休眠一段时间,时间长短由参数决定;
  • void Wait():使线程处于等待状态。

线程的调度

Java中,线程调度通常是抢占式,而不是时间片式。
Java的线程调度采用如下的优先级策略:

  • 优先级高的先执行,优先级低的后执行;
  • 多线程系统会自动为每个线程分配一个优先级,默认时,继承其父类的优先级;
  • 任务紧急的线程,其优先级较高;
  • 同优先级的线程按“先进先出”的原则。

Thread类有三个与线程优先级有关的静态量:

  • MAX_PRIORITY:最大优先权,值为10;
  • MIN_PRIORITY:最小优先权,值为1;
  • NORM_PRIORITY:默认优先权,值为5。

线程的基本控制

  1. 结束线程:使用stop()方法强制停止,即强迫死亡。
  2. 检查线程:isAlive();
  3. 挂起线程:sleep(),用于暂时停止一个线程的执行;suspend()强制挂起线程和resume()唤醒其继续执行;join()引起现行线程等待,知道方法join()所调用的线程结束。

线程间的通信

管道流可以连接两个线程间的通信。

线程间的资源互斥共享

同时运行的线程需要共享数据时,必须保证共享数据的一致性。保证对共享数据操作的完整性,这种完整性称为共享数据操作的同步,共享数据叫做条件变量。

对象互斥锁

对象互斥锁阻止多个线程同时访问同一个条件变量。Java可以为每一个对象的实例配有一个“对象互斥锁”。
实现方法:

  • 用关键字volatile来声明一个共享数据(变量);
  • 用关键字synchronized来声明一个操作共享数据的方法或一段代码。

一般情况下,多使用syncharonized关键字在方法的层次上实现对共享资源操作的同步,很少使用volatile关键字声明共享变量。

死锁

如果一个线程持有一个锁并试图获取另一个锁时,就有死锁的危险。

产生背景

死锁情况发生在第一个线程等待第二个线程所持有的锁,而第二个线程又在等待第一个线程持有的锁的时候,每个线程都不能继续运行,除非有一个线程运行完同步程序块。而恰恰因为哪个线程都不能继续运行,所以哪个线程都无法运行完同步程序快。

产生原因

死锁是资源的无序使用而带来的,解决死锁问题的方法就是给资源施加排序。

避免死锁的办法

一个避免死锁发生的较麻烦的办法是:如果有多个对象要被同步,那就制定一个规则来决定以何种顺序来获得这些锁,并在整个程序中遵循这个顺序。

线程交互

生产者-消费者问题,可能出现的问题:

  • 生产者比消费者快时,消费者会漏掉一些数据取不到;
  • 消费者比生产者快时,消费者取的数据相同。
    使用以下方法来协调线程间的运行速度(读取)关系。
  • wait():作用是让当前线程释放其所持有的“对象互斥锁”,进入wait队列(等待队列);
  • notify()/ notifiAll():作用是唤醒一个或所有正在等待队列中等待的线程,并将他们移入等待同一个“对象互斥锁”的队列。
    需要说明的:
  • 上面三个方法都只能在被声明为syncharonized的方法或代码段中调用。
  • 当一个线程被notify()后,它并不立即变为可执行状态,而仅仅是从等待队列中移入锁定标志队列中,这样的话,在重新获得锁定标志之前它依旧不能继续运行。
  • 线程执行被同步的语句时必须要拥有对象的锁定标志
    说明:
    在实际实现中,方法wait()既可以被notify()终止,也可以通过调用线程的interrupt()方法来终止。后一种情况下,wait()会抛出一个InterruptedException异常,所以需要把它放在try/catch结构中。

守护线程

守护线程是为其他线程提供服务的线程,它一般应该是一个独立的线程,它的run()方法是一个无限循环。可以通过方法public boolean isDaemon()来确定一个线程是否是守护线程,也可以用方法public void setDaemon(boolean)来设定一个线程为守护线程。
一般当最后一个线程结束时,Java程序才退出,而如果最后一个线程是守护线程,它不影响Java程序的退出。即如果守护线程是唯一运行着的线程,程序会自动退出。

说明

守护线程一般不能用于执行关键任务,因为有可能任务还未执行完,但它已经成为最后一个运行中的线程了,系统强制结束它,导致人物不能顺利完成。一般地,守护线程用来做辅助性工作,如用于提示、帮助等。

二十、Java的网络功能

Java提供的网络功能:

  • InetAddress:面向的是IP层,用于标识网络上的硬件资源;
  • URL面向应用层:通过URL的网络资源表达形式确定数据在网络中的位置,利用URL对象中提供的相关方法,直接读入网络中的数据,或者将本地数据传送到网络的另一端;
  • Socket面向传输层:使用的是TCP协议;
  • Datagram也面向传输层:使用的是UDP协议;
    URL和Socket两种方法都是面向连接方式的通信。

使用InetAddress

类InetAddress可以用于标识网络上的资源,他提供了一系列的方法,用来描述、获取及使用网络资源。

  • public static InetAddress getByName(String host);
  • public static InetAddress getLocalHost();
  • public static InetAddress[] getAllByName(String host);

InetAddress类的主要方法

  • public byte[] getAddress()——获得本对象的IP地址(存放在字节数组中);
  • public String getHostAddress()——获得本对象的IP地址“%d.%d.%d.%d”;
  • public String getHostName()——获得本对象的机器名。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class InetAddressUtil {

private static final Logger LOGGER = LoggerFactory.getLogger(InetAddressUtil.class);

private static InetAddress ADDRESS;

public static InetAddress getInetAddress(String host) {
InetAddress inetAddress = null;
try {
inetAddress = InetAddress.getByName(host);
} catch (UnknownHostException e) {
LOGGER.error("UnknownHostException", e);
}
return inetAddress;
}

public static void main(String[] args) {
String host = "www.baidu.com";
InetAddress address = InetAddressUtil.getInetAddress(host);
LOGGER.info("address {}" , address);
System.out.println(address);
}
}

执行结果:

1
www.baidu.com/220.181.38.150

统一资源定位器

URL是uniform resource locator(统一资源定位器)的缩写,它表示Internet上某一资源的地址。
URL包括两部分内容:协议名称和资源名称,中间用冒号隔开。
Protocol:resourceName
URL的具体结构为:
protocol://host_name:port_number/file_name/reference

  • protocol:用来指示所要获取资源的传输协议,如:http、ftp、gopher、file等;
  • host_name:用来指示资源所在的主机;
  • port_number:用来指示连接时所使用的通信端口号;
  • file_name:用来指示该资源在主机的完整文件名;
  • reference:指示资源中的某个特定位置;

URL的构造方法

  • public URL(String spec);
  • public URL(URL context, String spec);
  • public URL(String protocol, String host, String file);
  • public URL(String protocol, String host, int port, String file);

获取URL对象属性

  • String getProtocol()——获取传输协议;
  • String getHost()——获取机器名称;
  • String getPort()——获取通信端口号;
  • String getFile()——获取资源文件名称;
  • String getRef()——获取参考点;

读入URL数据

URL类中定义了openStream()方法,通过这个方法可以读取一个URL对象所指定的资源。方法openStream()与指定的URL建立连接并返回一个InputStream对象,即这个方法的返回值是一个InputStream数据流。

网络通信
通过URL的方法openStream(),只能从网络上读取资源中的数据。通过URLConnection类,可以在应用程序和URL资源之间进行交互,既可以从URL中读取数据,也可以向URL中发送数据。URLConnection类表示了应用程序和URL资源之间的通信连接。

public URLConnection openConnection

URLConnection 中最常用的两个方法是:

  • public InputStream getInputStream();
  • public OutputStream getOutputStream();
    通过getInputStream()方法,应用程序就可以读取资源中的数据。

二十一、Socket接口

在Java中,基于TCP协议实现网络通信的类有两个:在客户端的Socket类和在服务器端的ServerSocket类。ServerSocket类的功能是建立一个Server,并通过accept()方法随时监听客户端的连接请求。

构造方法:

  • public Socket(String host, int port);
  • public Socket(InetAddress address, int port);
  • public Socket(String host, int port, InetAddress localAddr, int localPort);
  • public Socket(InetAddress address, int port, InetAddress localAddr, int localPort);

Socket类的输入/输出流管理包括以下方法:

  • public InputStream getInputStream();
  • public void shutdownInput();
  • public OutputStream getOutputStream();
  • public void shutdownOutput();
    以上方法都会抛出IOException异常,程序中需要捕获处理。

关闭Socket的方法为:

  • public void close() throws IOException;

设置/获取Socket数据的方法为:

  • public InetAddress getInetAddress();
  • public int getPort();
  • public void setSoTimeOut(int timeout);

ServerSocket类的构造方法:

  • public ServerSocket(int port);
  • public ServerSocket(int port, int backlog),支持指定数目的连接;
  • public ServerSocket(int port, int backlog, InetAddress bindAddr);

  • public Socket accept():等待客户端的连接;
  • public void close():关闭Socket;

Socket的基本概念

  1. 建立连接,建立连接的过程为:

    1. 先在服务端生成一个ServerSocket实例对象,随时监听客户端的连接请求;
    2. 当客户端需要连接时,相应地要生成一个Socket实例对象,并发出连接请求,其中host参数指明该主机名,port#参数指明该主机端口号;
    3. 服务端通过accept()方法接收到客户端的请求后,开辟一个接口与之进行连接,并生成所需的I/O数据流;
    4. 客户端和服务器端的通信都是通过一对InputStream和OutputStream进行的。通信结束后,两端分别关闭对应的Socket接口。
  2. 连接地址

  3. 端口号

  4. 网络连接模式
    每个Server端都拥有一个端口号,一台机器上如果运行多个服务,则可能对应多个端口号。通信结束后,两端分别关闭对应的Socket接口,而不影响其他的端口。

Socket通信的基本步骤

1). 在服务器端制定一个用来等待连接的端口号,在客户端规定一个主机和端口号,从而在客户端和服务器端创建Socket/ServerSocket实例;
2). 打开连接到Socket的输入输出流;
3). 利用输入输出流,按照一定的协议对Socket进行读写操作;
4). 关闭输入输出流和Socket;

Java实例