java基础

1.配置开发环境

2.java 三个体系

3.第一个 java 程序

4.编译与运行

5.注释

6.标识符

7.基本数据类型

8.基本数据类型转换

9.常用运算符

10.位运算符

11.分支结构

12.循环结构

13.跳转语句

14.三大流程整合

15.Java 内存模型

16.综合练习

17.类和对象

18.如何定义类

19.成员方法(函数)

20.访问控制修饰符

21.构造方法

22.静态变量(类变量)

23.静态方法(类方法)

24.方法重载

25.方法覆盖

26.抽象

27.封装

28.继承

29.多态

30.抽象类

31.接口

32.new 运算符背后

33.程序进程与线程

34.继承 Thread

35.实现 Runnable 接口

36.线程的同步

37.死锁

38.IO

39.File

40.字节输入流 FileInputStream

41.字节输出流 FileOutputStream

42.字符流 FileReader 和 FileWriter

43.缓冲字符流 BufferedReader 和 BufferedWriter

44.properties 配置文件的读取

45.对象序列化和反序列化

46.线程的几种可用状态

1.配置开发环境

1.下载 jdk1.8 安装包或从已经安装过的电脑上拷贝 jdk 安装后的文件夹直接使用。下载解压版地址:下载 jdk1.8

2.安装完 JDK 后配置环境变量:计算机→属性→高级系统设置→高级→环境变量。

3.系统变量→新建 JAVA_HOME 变量。变量值填写 jdk 的安装或解压目录如 E:\jdk1.8。

4.系统变量→寻找 Path 变量→编辑。

如果是 win7 系统,Path 中的每个变量都是通过分号隔开,新的变量值需要追加在原变量的后面。如果原来 Path 的变量值末尾有没有;号。需要先输入分号后再加入下面的两段配置。

%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;

如果是 win10 系统,Path 显示的方式有所不同,需要单独配置每个变量。通常使用下面的配置方式,将两个值分别进行配置。

5.系统变量→新建 CLASSPATH 变量。变量值填写如下:

%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar

系统变量配置完毕。检验是否配置成功运行 cmd 输入 java -version 命令进行校验。

C:\Users\Administrator>java -version
java version "1.8.0_131"
Java(TM) SE Runtime Environment (build 1.8.0_131-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode)

注意:

JDK(Java Development Kit)称为 Java 开发包或 Java 开发工具,是一个编写 Java 的 Applet 小程序和应用程序的程序开发环境。JDK 是整个 Java 的核心,包括了 Java 运行环境(Java Runtime Envirnment),一些 Java 工具和 Java 的核心类库(Java API)。不论什么 Java 应用服务器实质都是内置了某个版本的 JDK。主流的 JDK 是 Sun 公司发布的 JDK,除了 Sun 之外,还有很多公司和组织都开发了自己的 JDK,例如,IBM 公司开发的 JDK,BEA 公司的 Jrocket,还有 GNU 组织开发的 JDK。

另外,可以把 Java API 类库中的 Java SE API 子集和 Java 虚拟机这两部分统称为 JRE(JAVA Runtime Environment),JRE 是支持 Java 程序运行的标准环境。

JRE 是个运行环境,JDK 是个开发环境。因此写 Java 程序的时候需要 JDK,而运行 Java 程序的时候就需要 JRE。而 JDK 里面已经包含了 JRE,因此只要安装了JDK,就可以开发 Java 程序,也可以正常运行 Java 程序。但由于 JDK 包含了许多与运行无关的内容,占用的空间较大,因此运行普通的 Java 程序无须安装 JDK,而只需要安装 JRE 即可。

JVM 是 Java Virtual Machine(Java虚拟机)的缩写,JVM 是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。

引入 Java 语言虚拟机后,Java 语言在不同平台上运行时不需要重新编译。Java 语言使用 Java 虚拟机屏蔽了与具体平台相关的信息,使得 Java 语言编译程序只需生成在 Java 虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。

2.java 三个体系

Java2 平台企业版(Java 2 Platform,Enterprise Edition)包括:标准版(J2SE)、企业版(J2EE)和微缩版(J2ME)三个版本。J2SE,J2ME 和 J2EE 这也就是 SunONE(OpenNetEnvironment)体系。J2SE 就是 Java2 的标准版,主要用于桌面应用软件的编程;J2ME 主要应用于嵌入式系统开发,如手机和 PDA 的编程;J2EE 是 Java2 的企业版,主要用于分布式的网络程序的开发,如电子商务网站和 ERP 系统。

Standard Edition(标准版)J2SE 包含那些构成 Java 语言核心的类。比如:数据库连接、接口定义、输入/输出、网络编程。

Enterprise Edition(企业版)J2EE 包含 J2SE 中的类,并且还包含用于开发企业级应用的类。比如:servlet、JSP、XML、事务控制。

Micro Edition(微缩版)J2ME 包含 J2SE 中一部分类,用于消费类电子产品的软件开发。比如:呼机、智能卡、手机、PDA、机顶盒。

简单讲就是:

J2SE:java2Standard edition(java2 标准版)

J2EE:java2 enterprise edition(Java2 企业版)

J2ME:java2 micro edition(Java2 微缩版)

他们的范围是:J2SE 包含于 J2EE 中,J2ME 包含了 J2SE 的核心类,但新添加了一些专有类。

应用场合,API 的覆盖范围各不相同。

笼统的讲,可以这样理解:J2SE 是基础;压缩一点,再增加一些硬件控制方面的特性就是 J2ME;扩充一点,再增加一些企业应用方面的特性就是 J2EE。

J2EE 更恰当的说,应该是 JAVA2 企业开发的技术规范,不仅仅是比标准版多了一些类。J2EE 又包括许多组件,如 Jsp、Servlet、JavaBean、JDBC、JavaMail 等。

J2SE 商业版本,标准版本(Java2 Standard Edition)定位在客户端,主要用于桌面应用软件的编程。

J2SE 包含那些构成 Java 语言核心的类。

比如:数据库连接、接口定义、输入/输出、网络编程,J2SE 是 J2EE 的基础,他大量的 JDK 代码库是每个要学习 J2EE 的编程人员必须掌握的。

从 JDK5 开始,不再叫 J2SE 而是改名为 Java SE 了,因为那个 2 已经失去了其应该有的意义。

3.第一个 java 程序

java 有很多开发工具如:记事本、Notepad++、eclipse、idea。本课程使用 idea ,官网地址:IntelliJ IDEA: The Capable & Ergonomic Java IDE by JetBrains,ideaIU-2020.1.win.zip 下载地址:链接
注意:不建议使用记事本直接创建 java 文件,会出现编译报错问题。
使用 idea 创建 java 项目,项目名为 hello,在项目中创建名为 HelloWorld 的 java 类,内容如下,找到该类的具体位置,可以看到该类是一 HelloWorld.java 文件。


public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}

HelloWorld 程序中的第一行的内容是 public class HelloWorld,其中 HelloWorld 是一个类名,class 是判断 HelloWorld 为一个类名的关键字,而 public 是用来修饰类的修饰符,表示公开的。每一个基础类都有一个类体,使用大括号包括起来,类主要是用来存放方法的。

程序中的第二行为 public static void main(String args[]),简打(main 回车)它是一个特殊方法,主体是 main,其他的都是修饰内容。这条代码语句是一个 Java 类固定的用法,其中 main 定义一个 Java 程序的入口。和类具有类体,方法具有方法体一样,其同样也要使用大括号括起来。

程序的第三行为 System.out.println("Hello World"),简打(sout 回车)该语句的功能是向输出台输出内容。在该程序中输入的是 Hello World 信息。

4.编译与运行

1、java 源文件(*.java)是不能直接运行的,需要由编译器 javac.exe 编译生成 java 字节码文件(*.class)

2、字节码文件(*.class)由执行器 java.exe 将字节码文件加载到 java 虚拟器(jvm)后执行。

C:\Users\Administrator>cd Desktop

C:\Users\Administrator\Desktop>javac HelloWorld.java

C:\Users\Administrator\Desktop>java HelloWorld
hello world

在 cmd 命令提示窗口中通过 cd Desktop 进入 windows 桌面,因为 java 文件在桌面上。注意,使用 cmd 编译时 java 文件中不能包含中文否则会编译不通过。如果 Java 文件中必须包含中文可以使用 idea 进行编译。

5.注释

注释添加在代码中,是给程序员看的,当系统运行程序,读取注释时会越过不执行。随着技术的发展,现在具有百万行代码的程序已经很常见了,在这样一个大型的代码中,如果没有注释,可想而知对于后面的修改和维护会产生多大的麻烦。在 Java 语言中提供了完善的注释机制,具有三种注释方式。

1.单行注释

// 注释内容

2.多行注释

/*
    1
    2
    3
*/

3.文档注释,常用在方法上表明方法的作用,调用参数和返回值,如下方法用于简单的求和运算,使用注释可以清楚的展示方法的参数与返回值。

public class HelloWorld {
    public static void main(String[] args) {
        int sum = sum(10, 20);
        System.out.println(sum);
    }

    /**
     * 这是一个求和的方法
     * @param i1 这是加数1
     * @param i2 这是加数2
     * @return 他们的和
     */
    public static int sum(int i1, int i2) {
        return i1 + i2;
    }
}

int :表示整数。
void:表示方法执行后,不返回内容。
return:表示立即返回对应类型的内容。
sum:方法的名字,英文意思为求和。
param:英文意思为参数。

6.标识符

标识符是程序员为自己定义的类、方法或者变量等起的名称。也就是程序员起的类名、方法名、属性名、变量名。

在 Java 中是区分大小写的,而且还要求首位不能是数字和其中不能包含运算符(加(+)减(-)乘(*)除(/)与(&)或(|)非(!)等)。最重要的是,Java 关键字不能当作 Java 标识符。

合法标识符的示例:age,$salary,_value,__1_value。

非法标识符的示例:123abc,-salary,public。

命名规则:

  1. 类和接口名:每个字的首字母大写,含有大小写。例如:MyClass,HelloWorld,Time 等。
  2. 方法名和变量:首字符小写,其余的首字母大写,含大小写。尽量少用下划线。例如:myName,setTime 等。这种命名方法叫做驼峰式命名。
  3. 常量名:基本数据类型的常量名使用全部大写字母,字与字之间用下划线分隔。对象常量可大小混写。例如:SIZE_NAME,MAX_VALUE。

java 关键字:

访问控制

private[ˈpraɪvət] 私有的、protected[prəˈtektɪd] 受保护的、public[ˈpʌblɪk] 公开的

类、方法、变量和修饰符

abstract[ˈæbstrækt] 声明抽象、class[klɑːs] 类、extends[ɪkˈstendz] 扩允,继承、final 终极,不可改变的、implements 实现、interface 接口、native 本地、new 新,创建、static 静态、synchronized 线程同步、transient 短暂、assert 断言

程序控制语句

break 跳出循环、continue 继续、return 返回、do 运行、while 循环、if 如果、else 反之、for 循环、instanceof 实例判断、switch 开关、case 返回开关里的结果、default 默认

错误处理

catch 处理异常、finally 有没有异常都执行、throw 抛出一个异常对象、throws 声明一个异常可能被抛出、try 捕获异常

包相关

import 引入、package 包

基本类型

boolean 布尔型、byte 字节型、char 字符型、double 双精度、float 浮点、int 整型、long 长整型、short 短整型

变量引用

super 父类,超类、this 本类、void 无返回值、enum 枚举类型

注意:

const、goto 是关键字,虽然没用,但不能拿来当变量名。

true、false、null 不是关键字,但也不能用来当变量名。

7.基本数据类型

Java 是一门强数据类型语言,Java 程序中定义的所有数据都有一个固定的数据类型。Java 中的数据类型基本可以分为两类:基本数据类型(也称原始数据类型)和复合数据类型(引用数据类型)。在本节中主要讲解基本数据类型,复合数据类型在后面的章节中将会讲到。学习数据类型的重点是了解每一种数据类型的取值范围。

整数类型、小数(浮点)类型、布尔类型、字符类型

1.整数类型

可以表示一个整数,常用的整数类型有

byte  占用内存 一个字节 范围:-128至127(-2^7 ~2^7 -1)
short 占用内存 两个字节 范围:-32768至32767(-2^15 ~2^15 -1)
int   占用内存 四个字节 范围:-2147483648至2147483647(-2^31 ~2^31 -1)
long  占用内存 八个字节 范围:-?至? (long 赋值时要在值后加L )

在 byte 下二进制与十进制,根据下面的提示,从 0 到 10 的二进制列出来。

0000 0000           0
0000 0001           1
0000 0010           2
0000 0011           3
0000 0100           4

为什么 byte 只能装 127 到 -128?
基本上所有编程语言都规定,第一位表示符号:如果第一位为 1 则表示负数,如果为 0 则表示正数。
由于第一位表示符号,必然出现 1000 0000 这个数,通过字面理解 -0,只能往负数加一个。

byte b=127;
//0000 0000   0
//0000 0001   1
//0000 0010   2
//0000 0011   3
//0000 0100   4
//0000 0101   5
//0000 0110   6
//_111 1111   127

2.小数(浮点)类型

可以表示一个小数,常用的小数(浮点)类型有 float (单精度)和 double (双精度),java 中的小数默认是 double 的

float  占用内存 四个字节 范围:3.4E-38至3.4E+38   只能提供 8 位有效数字 (float 赋值时要在值后加 f )
double 占用内存 八个字节 范围:1.7E-308至1.7E+308 可提供 16 位有效数字

3.布尔类型

可以表示"真"或者"假",类型是 boolean,值只能是 true(真) 和 false(假) 。

boolean spBool = true; //给变量spBool定义为boolean型并赋值为真

4.字符类型

可以表示单个字符,字符类型是 char,可以存放一个字母、一个符号或一个汉字,而且使用单引号包裹。

char c = 'A';

由于字符存储是以 ASCII 码的数字存储,故字符的加减运算也是基于 ASCII 码的加减运算。

案例:

char c = 'a';
System.out.println(c + 1 - 1);
//97

注意:多个字符连在一起我们称为字符串,在 java 中用 String 这种数据类型表示

String(字符串)不是基本数据类型,而是复合数据类型(引用类型)。String 声明的变量使用双引号。字符串进行加法运算时是简单的拼接,不能进行减法运算。

char c = '1';
//字符必须用单引
String s1 = "大家好";
//字符串必须用双引

8.基本数据类型转换

1.自动转换

数据类型可以自动的从低精度向高精度转换,换而言之就是高精度的类型可以存放低精度的变量。而高精度不能自动转为低精度。

精度大小顺序表:byte < short < int < long < float < double

double b = 3;

2.强制转换

当需要让高精度变量转为低精度时需要使用强制类型转换,需要注意强制类型转换会丢失小数部分或发生数据溢出。强制类型转换需要在变量前面使用括号声明转换后的类型。

int a = (int) 1.2; 
byte b = (byte) 255;

3.计算过程中的转换

当一个整数类型和一个 double 类型运算的时,运算结果会向高精度的 double 转换。

int a = 3;
double d = a + 3.4;

byte b = 1;
b = b + 1;//编译报错

9.常用运算符

1.算术运算符:+加、-减、*乘、/除、%取余

在整数运算中:

  • / 运算可以得到两个整数的商
  • % 运算可以得到两个整数的余数
int quotient = 5 / 3;//1  商
int remainder = 5 % 3;//2  余

在小数运算中:

/ 就是正常运算

double out = 5 / 3.0;
System.out.println(out);

2.算术运算符:++ 自加、-- 自减。

++ 表示变量自动增加 1

-- 表示变量自动减少 1

案例:

int a = 90;
a++;
System.out.println("a:" + a);
int b = 89;
b--;
System.out.println("b:" + b);

变量 ++ 与 ++ 变量的主要区别在于自增的时机,通常有先用后加和先加后用的区别。如:i++ 表示先用后加,++i 表示先加后用。

案例:

int a = 56;
int b = a++;
System.out.println("b:" + b);
System.out.println("a:" + a);

案例:

int a = 21;
System.out.println(a++);
System.out.println(a);
System.out.println(++a);
if (a++ == 23) {
    System.out.println("进入if,a:" + a);
}

3.算术运算符:+=左加、-=左减、/=左除、%=左取模

这是开发中的一种简化操作,如:i += 5  等效于 i = i + 5,+= 操作能够自动转换类型如:byte i = 12; 执行 i=i+10;时会编译报错,而 i += 10;是合法的。

案例:

int a = 90;
a += 9;
System.out.println(a);
float b = 89.7f;
b += a;
System.out.println(b);

案例:

int a = 8;
int b = 9;
a -= 3;
b %= a;//b=b%a
System.out.println("a:" + a);
System.out.println("b:" + b);

4.关系运算符:== 等于、> 大于、< 小于、>= 大于等于、<= 小于等于、!= 不等于。

大于等于的真实含义表示既可以大于又可以等于,两个条件满足一个即可,它的要求要比大于底一些。

案例:

int a = 90;
int b = 90;
if (a == b) {
    System.out.println("ok1");
}
b--;
if (a &gt; b) {
    System.out.println("ok2");
}
//90 &gt;= 89
if (a &gt;= b) {
    System.out.println("ok3");
}
a--;
if (a != b) {
    System.out.println("ok4");
}

5.逻辑运算符:&& 与、|| 或、! 非

&& (与)要求运算符两边的两个条件都要为真,结果才为真。简记:真真为真,其它都为假。

|| (或)要求运算符两边的两个条件只要有一个条件为真,结果就为真。简记:假假为假,其它都为真。

! (非)对运算符后面的条件取反。简记:真取假,假取真。

案例:

int a = 90;
int b = 90;
if(a == b || a &gt; 8){System.out.println("ok1");}
b--;
if(a &gt; b &amp;&amp; a &gt; 45){System.out.println("ok2");}
if(!(a &lt;= b)){System.out.println("ok3");}

扩展:短路与与短路或

|| 或运算符前面如果为 true,则后面的不执行

&& 与运算前面如果为 false,则后面的也不执行

案例:

int a = 90;
if (1 != 1 || a++ == 1) {
}
System.out.println("a:" + a);//91
if (1 != 1 &amp;&amp; a++ == 1) {
}
System.out.println("a:" + a);//91

10.位运算符 (自学
1.位运算符通常有,按位与 &、按位或 |、按位异或 ^、按位取反 ~ 等一系列基于计算机二进制的运算操作符。

它们的运算规则是:

按位与 &:两位全为 1,结果为 1 

按位或 |:两位有一个为 1,结果为 1 

按位异或 ^:两位一个为 0,一个为 1,结果为 1 

按位取反 ~:0->1,1->0

案例:

int i = 3;//0000 0011
int j = 7;//0000 0111
//0000 0011
System.out.println(i &amp;amp; j);//3
System.out.println(~i);//-4

2.(了解)移位运算符:算术右移>>、算术左移<<、>>>无符右移

byte a = 5;//0000 0101
System.out.println(a&lt;&lt;2);//  0001 0100

使用 String s = Integer.toBinaryString(6) 输出数字的二进制。

11.分支结构

迄今为止,我们写的代码都是一条一条语句顺序执行,这种代码结构通常称之为顺序结构。然而仅有顺序结构并不能解决所有的问题,比如我们设计一个游戏,游戏第一关的通关条件是玩家获得 1000 分,那么在完成本局游戏后,我们要根据玩家得到分数来决定究竟是进入第二关,还是告诉玩家 “Game Over”,这里就会产生两个分支,而且这两个分支只有一个会被执行。类似的场景还有很多,我们将这种结构称之为“分支结构”或“选择结构”。分支结构分为单分支、双分支和多分支。

语法如下:

1、单分支语法:
if(条件表达式){
    语句;
}

2、双分支语法:
if(条件表达式){
    语句;
}else{
    语句;
}

3、多分支语法:
if(条件表达式){
    语句;
}else if(条件表达式){
    语句;
}else if(条件表达式){
    语句;
}else{
    语句;
}

案例:通过运行参数获取两个数并做一个判断,如果两个数相等,则输出 "两个数为:123 他们相等",否则输出 : "两个数分别为123,456 他们的差为-333",运行参数添加如上:

public static void main(String[] args) {
    String s1 = args[0];
    String s2 = args[1];
    int i1 = Integer.parseInt(s1);
    int i2 = Integer.parseInt(s2);
    if (i1 == i2) {
        System.out.println("两个数为:" + i1 + " 他们相等");
    } else {
        System.out.println("两个数分别为" + i1 + "," + i2 + " 他们的差为" + (i1 - i2));
    }
}

三元(目)运算符:
Java 中有一个特殊的三元运算符,它支持条件表达式,当需要进行条件判断时可用它来替代 if-else 语句。它的简洁高效地完成 if else 的功能。其一般格式如下。
 

条件 ? 表达式1 : 表达式2
 

案例:

int i = 6;
int j = 1;
System.out.println(i == j ? "相等" : "不相等");
System.out.println(i > j ? "大于" : i < j ? "小于" : "等于");

补充:switch case 一样可以做多分支逻辑,条件表达式数据类型,就和 case 常量表达式一致,否则就会报错。

switch(变量名){
    case 常量1:
        语句1;
        break;  //break,表示跳出switch语句
    case 常量2:
        语句2;
        break;
    ...
    case 常量n:
        语句n;
        break;
    default:
        语句;
        break;
}

Java5 以前 switch(expr)中,expr 只能是 byte、short、char、int。从 Java 5 开始,Java 中引入了枚举类型, expr 也可以是 enum 类型。 从 Java 7 开始,expr 还可以是字符串(String),但是长整型(long)在目前所有的版本中都是不可以的。switch 分支语句不支持判断类型即 boolean 类型。
案例:

int i = 6;
switch (i) {
    case 6:
        System.out.println("周末");
        break;
    case 7:
        System.out.println("周末");
        break;
    default:
        System.out.println("工作日");
        break;
}

12.循环结构

如果在程序中我们需要重复的执行某条或某些指令,比如在我们的程序中要实现每隔 1 秒中在屏幕上打印一个"hello,world"这样的字符串并持续一个小时,我们肯定不能够将打印输出这句代码写上 3600 遍,如果真的需要这样做那么编程的工作就太无聊了。

因此,我们需要了解一下循环结构,有了循环结构我们就可以轻松的控制某件事或者某些事重复、重复、再重复的发生。循环控制有通常有如下三种,for、while 与 do-while,这三种循环各自有自己的特点。它们的语法如下:

1、for循环 语法:
    for(循环初值;循环条件;步长){
        语句;  //循环体
    }

2、while循环 语法:
    while(循环条件){
        语句;  //循环体
    }
特别说明:while循环是先判断在执行语句。

3、do while循环 语法:
    do{
        语句;  //循环体
    }while(循环条件);
特别说明:do while循环是先执行,再判断。

案例:三种方式的输出 0 到 5

for (int i = 0; i < 6; i++) {
    System.out.println(i);
}
System.out.println("*********************");
int i = 0;
while (i < 6) {
    System.out.println(i);
    i++;
}
System.out.println("*********************");
i = 0;
do {
    System.out.println(i);
    i++;
} while (i < 6);

如上便是使用 3 种循环完成从 0 到 5 的输出。
案例:求和 0+1+2+...+100=?

public static void main(String[] args) {
    int sum = 0;
    for (int i = 0; i <= 100; i++) {
        sum += i;
    }
    System.out.println(sum);
}

13.跳转语句

跳转语句是指打破程序的正常运行,跳转到其他部分的语句。在 Java 中支持 3 种跳转语句:break 语句、continue 语句和 return 语句。这些语句将程序从一部分跳到程序的另一部分,这对于程序的整个流程是十分重要的。

1.break 跳出语句

break 语句主要有三种用途。第一,它可以用于跳出 switch 语句,前面的 switch 语句已经使用了该语句。第二,break 语句可以用于跳出离它最近的循环。第三,可以用于大语句块的跳出。

案例:跳出循环

for (int i = 0; i &lt; 10; i++) {
    if (i == 5){
        break;
    }
    System.out.println(i);
}

案例:跳出指定循环,如下案例跳出外层循环。

block: for (int i = 0; i &lt; 10; i++) {
    for (int i1 = 0; i1 &lt; 10; i1++) {
        if (i1 == 5) {
            break block;
        }
        System.out.print(i1);
    }
     System.out.println();
}

2.continue 继续语句

停止一次循环剩余的部分,同时还要继续执行下次循环。

案例:continue 用法

for (int i = 0; i &lt; 10; i++) {
    if (i == 5) {
        continue;
    }
    System.out.println(i);
}

3.return 返回语句

return 语句用于一个方法显示的返回,它把程序的控制权交给方法的调用者。如果是在主函数中 return 则结束整个主函数。

案例:return 用法

public class Hello {
    public static void main(String[] args) {
        int sum = sum(12, 34);
        System.out.println(sum);
    }

    public static int sum(int i1, int i2) {
        return i1 + i2;
    }
}

案例:

private static String t41(int i) {
    if (i >= 90) {
        return "A";
    }
    if (i >= 70) {
        return "B";
    }
    if (i >= 60) {
        return "C";
    }
    return "D";
}

这就是 break 语句、continue 语句和 return 语句的用法,在后面的项目中我们将继续练习。

14.三大流程整合

三大流程控制分为:顺序(从上往下)、分支(根据条件选择执行的代码块)和循环(重复执行)

在开发过程中,经常会遇到比较复杂的业务逻辑,通常大部分可以由循环加判断完成。如下案例可以完成一个递增数组的样式输出。

最终效果如下:

[ 1, 2, 3, 4, 5, 6, 7, 8, 9,10]
[ 2, 3, 4, 5, 6, 7, 8, 9,10,11]
[ 3, 4, 5, 6, 7, 8, 9,10,11,12]
[ 4, 5, 6, 7, 8, 9,10,11,12,13]
[ 5, 6, 7, 8, 9,10,11,12,13,14]
[ 6, 7, 8, 9,10,11,12,13,14,15]
[ 7, 8, 9,10,11,12,13,14,15,16]
[ 8, 9,10,11,12,13,14,15,16,17]
[ 9,10,11,12,13,14,15,16,17,18]
[10,11,12,13,14,15,16,17,18,19]

案例:递增数组

for (int i = 0; i &lt; 10; i++) {
    System.out.print("[");
    for (int i1 = 1; i1 &lt;= 10; i1++) {
        System.out.print(i1 + ",");
    }
    System.out.println("]");
}

输出结果,如下

[1,2,3,4,5,6,7,8,9,10,]
[1,2,3,4,5,6,7,8,9,10,]
[1,2,3,4,5,6,7,8,9,10,]
[1,2,3,4,5,6,7,8,9,10,]
[1,2,3,4,5,6,7,8,9,10,]
[1,2,3,4,5,6,7,8,9,10,]
[1,2,3,4,5,6,7,8,9,10,]
[1,2,3,4,5,6,7,8,9,10,]
[1,2,3,4,5,6,7,8,9,10,]
[1,2,3,4,5,6,7,8,9,10,]

1.bug解决最后多出的逗号

System.out.print(i1 + (i1 == 10 ? "" : ","));

2.每一排依次递增,效果如下

[1,2,3,4,5,6,7,8,9,10]
[2,3,4,5,6,7,8,9,10,11]
[3,4,5,6,7,8,9,10,11,12]
[4,5,6,7,8,9,10,11,12,13]
[5,6,7,8,9,10,11,12,13,14]
[6,7,8,9,10,11,12,13,14,15]
[7,8,9,10,11,12,13,14,15,16]
[8,9,10,11,12,13,14,15,16,17]
[9,10,11,12,13,14,15,16,17,18]
[10,11,12,13,14,15,16,17,18,19]

3.对齐上面的数字,即位数等于1时后面添加空格。

代码如下:

int out = i1 + i;
System.out.print((out &lt; 10 ? " " + out : out) + (i1 == 10 ? "" : ","));

通过上面的代码,我们便完成递增数组的输出。

15.Java 内存模型 

java 内存模型(Java Memory Model,JMM)是 java 虚拟机规范定义的,用来屏蔽掉 java 程序在各种不同的硬件和操作系统对内存的访问的差异,这样就可以实现 java 程序在各种不同的平台上都能达到内存访问的一致性。Java 内存模型的主要目标是定义程序中变量的访问规则。即在虚拟机中将变量存储到主内存或者将变量从主内存取出这样的底层细节。

需要注意的是这里的变量跟我们写 java 程序中的变量不是完全等同的。这里的变量是指实例字段,静态字段,构成数组对象的元素,但是不包括局部变量和方法参数(因为这是线程私有的)。这里可以简单的认为主内存是 java 虚拟机内存区域中的堆,局部变量和方法参数是在虚拟机栈中定义的。

1、程序计数器(线程私有)一块较小的内存空间,是当前线程所执行的字节码的行号指示器,每条线程都要有一个独立的程序计数器,这类内存也称为“线程私有”的内存。正在执行 java 方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址)。如果还是 Native 方法,则为空。这个内存区域是唯一一个在虚拟机中没有规定任何 OutOfMemoryError 情况的区域。

2、虚拟机栈(线程私有) 是描述 java 方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。栈帧( Frame)是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接(Dynamic Linking)、方法返回值和异常分派(Dispatch Exception)。栈帧随着方法调用而创建,随着方法结束而销毁——无论方法是正常完成还是异常完成(抛出了在方法内未被捕获的异常)都算作方法结束。

3、本地方法区(线程私有)本地方法区和 Java Stack 作用类似,区别是虚拟机栈为执行 Java 方法服务,而本地方法栈则为 Native 方法服务,如果一个 VM 实现使用 C-linkage 模型来支持 Native 调用,那么该栈将会是一个 C 栈,但 HotSpot VM 直接就把本地方法栈和虚拟机栈合二为一。 

4、堆(Heap-线程共享)-运行时数据区是被线程共享的一块内存区域,创建的对象和数组都保存在 Java 堆内存中,也是垃圾收集器进行垃圾收集的最重要的内存区域。由于现代 VM 采用分代收集算法,因此 Java 堆从 GC 的角度还可以细分为:新生代(Eden 区、From Survivor 区和 To Survivor 区)和老年代。

5、方法区/永久代(线程共享)即我们常说的永久代(Permanent Generation),用于存储被 JVM 加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。HotSpot VM把GC分代收集扩展至方法区,即使用Java 堆的永久代来实现方法区,这样 HotSpot 的垃圾收集器就可以像管理 Java 堆一样管理这部分内存, 而不必为方法区开发专门的内存管理器(永久带的内存回收的主要目标是针对常量池的回收和类型的卸载,因此收益一般很小)。 运行时常量池(Runtime Constant Pool)是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。Java 虚拟机对 Class 文件的每一部分(自然也包括常量池)的格式都有严格的规定,每一个字节用于存储哪种数据都必须符合规范上的要求,这样才会被虚拟机认可、装载和执行。

17.类和对象

Java 是一门面向对象的编程语言,其重要的一个思想就是“万物皆对象”。而类是 Java 的核心内容,它是一种逻辑结构,定义了对象的结构,可以由一个类得到众多相似的对象。从某种意义上说,类是 Java 面向对象性的基础。Java 与 C++不同,它是一门完全的面向对象的编程语言,它的任何工作都要在类中进行。 

类(class)实际上是一个模板,而对象是由这个模板产生的多个实例(instance [ˈɪnstəns])。

实际上前面的程序中也是在类中实现的,不过全在类中的 main 方法中演示程序的使用,没有体现面向对象编程的思想。这一节里主要讲解 Java 类的相关知识,包括类的形式、类包含的内容,即属性和方法。

18.如何定义类

Java 的重要思想是万物皆对象,也就是说在 Java 中可以把所有现实中的一切人和物都看做对象,而类就是它们的一般形式。编写程序就是抽象出这些事物的共同点,用程序语言的形式表达出来。

语法如下:

class 类名{
类型 实例变量名;
类型 实例变量名;
}

案例:可以把某某人看做一个对象,那么就可以把人作为一个类抽象出来,这个人就可以作为人这个类的一个对象。

public class Person {
    int age;
    String name;
}

注意:在类名面前可以不加上修饰符 public,在 Java 中是允许把许多类放在一个 Java 文件中,但是这些类只能有一个类被声明为 public,而且这个类的名字必须和 Java 文件名相同。

案例:通过 Person 类创建两个对象小明和小张

Person person1 = new Person();
person1.age = 12;
person1.name = "小明";
Person person2 = new Person();
person2.age = 13;
person2.name = "小张";
System.out.println(person1);
System.out.println(person2);

说明:输出到控制台为 Person@1540e19d,原因是 toString 方法没有重写,idea 生成 toString 方法的快捷键是 alt + insert,toString 方法主要用来控制对象的输出格式。

19.成员方法(函数)

成员方法也叫成员函数、实例方法。如人类:除了有一些属性(Field [fiːld])如:年龄、姓名...外,我们人类还有一些行为比如:可以说话、跑步、学习、还可以做算术题。这时就要用成员方法(Method [ˈmeθəd])才能完成。

案例:

  1. 添加 speak 成员方法,输入出:我是谁,我是一个好人
  2. 添加 calculate 成员方法,可以计算从 1+...+1000 的结果
  3. 添加 calculate 成员方法,该方法可以接收一个数 n,计算返回从 1+...+n 的结果
  4. 添加 add 成员方法,可以计算返回两个数的和
public class Person {
    int age;
    String name;

    public void speak() {
        System.out.println("我是" + name + ",我是好人!");
    }

    public int calculate() {
        int sum = 0;
        for (int i = 1; i <= 1000; i++) {
            sum += i;
        }
        return sum;
    }

    public int calculate(int n) {
        int sum = 0;
        for (int i = 1; i <= n; i++) {
            sum += i;
        }
        return sum;
    }

    public int add(int x, int y) {
        return x + y;
    }

    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

class MainTest {
    public static void main(String[] args) {
        Person person1 = new Person();
        person1.age = 12;
        person1.name = "张三";
        Person person2 = new Person();
        person2.age = 18;
        person2.name = "李四";
        System.out.println(person1);
        System.out.println(person2);
        person2.speak();
        int calculate = person2.calculate(5);
        System.out.println(calculate);
        int add = person1.add(2, 8);
        System.out.println(add);
    }
}

按照要求完成后两个方法,然后看看和课件上区别在哪里。

语法如下:

public 返回数据类型 方法名(参数列表){
    语句;//方法(函数)主体
}
1、参数列表:表示成员函数输入
2、返回数据类型:表示成员函数输出
3、函数主体:表示为了实现某一功能代码块

注意方法有如下特性:

  1. 方法名在有不同参数的情况下可以使用同一个方法名,即有参数和没参数的方法可以同名
  2. 返回类型和返回结果的类型要一致
  3. 在调用某个成员方法的时候,给出的具体数值的个数和类型要相匹配

20.访问控制修饰符

java 提供四种访问控制修饰符号控制类、方法和属性的访问权限:

1、公开级别:用 public [ˈpʌblɪk]修饰,对外公开

2、受保护级别:用 protected [prəˈtektɪd]修饰,对子类和同一个包中的类公开

3、默认级别:没有修饰符号,向同一个包的类公开

4、私有级别:用 private [ˈpraɪvət]修饰,只有类本身可以访问,不对外公开

具体如下:

访问级别	修饰符    	同类	同包	子类	不同包
公  开    	public    	√	√	√	√
受保护    	protected	√	√	√	╳
默  认    	没有修饰符	√	√	╳	╳
私  有    	private    	√	╳	╳	╳

package:包,用来区分相同类名的类,在类前面声明用于标识当前类所在的包。

import:用于表明当前类中所用到的类。如果是导入同一个包内的其他类,可省略。

21.构造方法

构造方法(Constructor [kənˈstrʌktə(r)])是类的一种特殊的方法(Method),它的主要作用是完成对新对象的初始化(initialize [ɪˈnɪʃəlaɪz])。

public class User {
    public int id;
    public String name;

    public User() {//无参构造
    }

    public User(int id, String name) {//有参构造
        this.id = id;
        this.name = name;
    }

}

class MainTest {
    public static void main(String[] args) {
        User user = new User(1, "张三");//调用有参构造初始化对象
        User user1 = new User();//调用无参构造初始化对象(需要再设置对象属性)
        user1.id = 2;
        user1.name = "李四";
    }
}

构造方法有几个特点:

  1. 方法名和类名相同,且没有返回值
  2. 在创建一个类的新对象时,系统会自动的调用该类的构造方法完成对新对象的初始化
  3. 一个类可以定义多个不同的构造方法
  4. 如果程序员没有定义构造方法,系统会自动生成一个默认的无参构造方法
  5. 如果创建了有参数的构造方法,则系统不自动生成无参数的构造方法,程序员一般需要手动创建

注意:在类中的 this 是自身的一个对象,代表对象本身,可以理解为:指向对象本身的一个指针。

22.静态变量(类变量)

类变量(static field)是该类的所有对象共享的变量。任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。
语法如下:
访问修饰符 static 数据类型 变量名;
案例:完成对象创建个数统计

public class Person {
    public static int count;
    public int age;
    public String name;

    public Person() {
        count++;
    }

    public static void main(String[] args) {
        Person person = new Person();
        Person person1 = new Person();
        System.out.println(person1.count);//2
        person.age = 12;
        person.name = "张三";
        new Person();
        System.out.println(Person.count);//3
    }
}

思考一下,类变量还可以用在哪些地方。

23.静态方法(类方法)

类方法(static method)是属于所有对象实例共享的方法。

语法如下:

访问修饰符 static 数据返回类型 方法名(参数列表){}

注意:

  1. 类方法中不能访问普通属性(实例属性),而普通方法既可以访问普通属性也可以访问静态属性。
  2. 一般使用 类名.类方法名() 或者 对象名.类方法名() 调用类方法(int i = Integer.parseInt("12");)

案例:

public class Person {
    public static int count;
    public int age;
    public String name;

    public Person() {
        count++;
    }

    public void say() {//普通方法既可以访问类属性,也可以普通属性
        System.out.println("我是" + name + ",我是好人,共" + count + "个对象");
    }

    public static void staticSay() {//类方法不能访问对象属性(实例属性,普通属性)
        System.out.println("创建了" + count);
    }

    public static void main(String[] args) {
        Person person = new Person();
        person.staticSay();//对象.方法名() 方式调用静态方法
        Person.staticSay();//类名.方法名() 方式调用静态方法
        new Person();
        System.out.println(Person.count);
    }
}

思考一下类方法在实际开发中的主要作用。

24.方法重载

方法重载(overload [əʊvəˈləʊd])就是在类的同一种功能的多种实现方法,方法的参数列表不同,到底采用哪个方法调用(Invoke [ɪnˈvəʊk]),取决于调用者给出的参数(Parameter [pəˈræmɪtə(r)])。

注意事项:

  1. 方法名必须相同。
  2. 方法的参数类型、个数、顺序至少有一项不同,简称参数列表不同(Parameter list)。
  3. 方法的返回类型,访问级别和 throws 子句对使其成为重载方法没有任何影响。

案例

public void aa(){
}
public void aa(int i1){
}

如上就是方法的重载,重载在我们后面开发中是非常常见的。

25.方法覆盖

方法覆盖也叫方法重写(override)就是子类有一个方法,和父类的某个方法的名称、返回类型、参数一样,那么我们就说子类的这个方法覆盖了父类的那个方法。

java 里面的所有类都是 Object 的子类。

注意事项:

  1. 子类的方法的返回类型,参数,方法名称,要和父类的返回类型,参数,方法名称完全一样,否则不能构成方法覆盖
  2. 一般在覆盖的方法上添加注解(@Override),用于检查和方法的返回类型,参数,方法名称是否完全一样
  3. 子类方法不能缩小父类方法的访问权限

案例

Object 的 toString
public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

自己的 toString
@Override
public String toString() {
    return "Person{" +
        "age=" + age +
        ", name='" + name + '\'' +
        '}';
}

方法覆盖主要是子类覆盖父类的方法。

26.抽象

我们在前面去定义一个类时候,实际上就是把一类事物的共有的属性(Field)和行为(Method)抽取出来,形成一个物理模型,叫做模版类(class)。这种研究问题的方法称为抽象(abstract)。

27.封装

封装就是把抽象出来的属性(Field)数据和对属性数据的操作封装在一起,属性数据被保护在内部,程序的其它部分只有通过被授权的成员方法(Method)操作数据,没有对应的授权方法则该属性数据不可操作。


常见的属性数据操作有:设置(setter)和获取(getter)。

public class Person {
    private String wxh;
    private int age;
    private String name;

    public Person(String wxh) {
        this.wxh = wxh;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

class MainTest {
    public static void main(String[] args) {
        Person person = new Person("wpf_123");
        person.setName("张三");
        person.setAge(18);
        System.out.println(person.getAge());
    }
}

在开发过程中,经常把一些常用的代码块放入一个方法,其他类或对象在使用时直接调用该方法,而不用再重新写一遍,从而增加代码的复用率,这种操作也叫做封装。
 

28.继承

继承(extend)可以解决代码复用问题,让我们的编程更加靠近人类思维。当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性(Field)和方法(Method),所有的子类不需要重新定义这些属性和方法,只需要通过 extends 语句来声明继承父类。这样,子类就会自动拥有父类定义的某些属性和方法。

语法:
class 子类 extends 父类

父类的 public、protected 和默认修饰符修饰的属性和方法被子类继承了,而父类的 private 修饰符修饰的属性和方法不能被子类继承。

public class Student extends Person {
    private String classRoom;
}

注意:super 可以理解为是指向自己超(父)类对象的一个指针,而这个超类指的是离自己最近的一个父类。
案例:当我们在 Person 里面添加一个全参构造时,在子类 Student 里面通过 super 调用父类构造方法。

public Student(int age, String name, String classRoom) {
    super(age, name);
    //super.setAge(age);
    setName(name);
    this.classRoom = classRoom;
}

java 的继承是单继承,也就是一个类最多只能有一个父类,这种单继承的机制可保证类的纯洁性,比 C++ 中的多继承机制简洁。

29.多态

多态就是指一个引用类型在不同情况下的多种状态。也可以理解成:

多态是指父类的类型可以存放子类的实例;
通过调用父类的方法自动调用子类实现了的或重写了的方法;

Person person = new Student();

这种转换时自动完成的,在调用 Person 对象的方法时自动调用 student 重写或实现的方法。
缺点:使用多态会使创建出来的对象丢失子类的方法。
优点:大多在设计模式中使用,使程序更加通用。

class MainTest {
    public static void main(String[] args) {
        Person person1 = new Student(1, "张三", "java2004");
        Student person2 = new Student(1, "张三", "java2004");
        Worker person3 = new Worker(1, "张三", "华信智原");
        changeName(person2, "历史");
    }

    private static void changeName(Person person, String name) {
        person.setName(name);
    }
}

30.抽象类

当父类的一些方法不能确定时,可以用 abstract [ˈæbstrækt] 关键字来修饰该方法,这种方法叫做抽象方法(abstract method)。拥有抽象方法的类必须添加 abstract 关键字修饰,用 abstract 来修饰的类叫抽象类(abstract class)。

public abstract class Animal {
    private String name;

    abstract public void cry();

    public void sx() {
        System.out.println("实现方法");
    }

    public static void main(String[] args) {
        Animal dog = new Dog();
        dog.cry();
    }
}

class Dog extends Animal {
    @Override
    public void cry() {
        System.out.println("汪汪汪汪!!!!");
    }
}
  1. 用 abstract 关键字来修饰一个类时,这个类就是抽象类,抽象类不能被实例化(new Animal())
  2. 用 abstract 关键字来修饰一个方法时,这个方法就是抽象方法。
  3. 抽象方法是不允许在该抽象类中实现的(void cry() { })也就是方法不能有方法体,一旦实现(有方法体)就编译报错。
  4. 抽象方法只能在子类中实现。
  5. 抽象类中可以拥有实现过的方法。

注意:

抽象类不一定要包含抽象方法。也就是说,抽象类可以没有抽象方法。一旦类包含了抽象方法,则这个类必须声明为抽象类。

31.接口

接口(interface [ˈɪntəfeɪs])就是给出一些没有方法体的方法,封装到一起,到某个类要使用的时候,再根据具体情况把这些方法写出来。接口是更加抽象的抽象类,抽象类里的方法可以有方法体,接口里的所有方法都没有方法体。接口体现了程序设计的多态和高内聚低偶合的设计思想。接口和类之间一般使用 implements [ˈɪmplɪments]表示父子关系。

接口的建立语法:
interface 接口名{
    方法;
}
实现类语法:
class 类名 implements 接口{
    方法;
    变量;
}

高内聚,低耦合:

每个模块之间相互联系的紧密程度,模块之间联系越紧密,则耦合性越高,模块的独立性就越差!反之同理;

一个模块中各个元素之间的联系的紧密程度,如果各个元素(语句、程序段)之间的联系程度越高,则内聚性越高,即高内聚!

如:一个项目中有 20 个方法调用良好,但是要修改了其中一个,另外的 19 个都要进行修改,这就是高耦合!独立性太差!

现在的软件结构设计,都会要求“高内聚,低耦合”,来保证软件的高质量!

public interface Person {
    void say();
}

class PersonMain implements Person {
    @Override
    public void say() {
        System.out.println("1212");
    }

    public static void main(String[] args) {
       Person person = new PersonMain();
       person.say();
    }
}

注意:

  1. 接口不能被实例化(new Person()),接口中的方法默认都是 public 修饰的。
  2. 接口中所有的方法都不能有方法体(void aaa(){}),故接口可以看作更加抽象的抽象类。
  3. 一个类可以实现多个接口(class PersonMain implements Person, Serializable{})
  4. 接口中可以有变量,但变量不能用 private 和 protected 修饰。接口中的变量,本质上都是 static 的而且是 final (最终的)类型的,不管你加不加 static 修饰,通常称为常量。
  5. 一个接口不能继承其它的类,但是可以继承别的接口
public interface Person {
    int aa = 0;
    void say();
}

interface PersonMain extends Person {  
    public void say1();
}

class Main implements PersonMain {

    @Override
    public void say() {
    }

    @Override
    public void say1() {
    }
}

案例

public class Demo {
    public static void main(String[] args) {
        LittleMonkey li = new LittleMonkey();
        li.setName("孙悟空");
        li.swimming();
        li.fly();
    }
}

interface Fish {
    void swimming();
}

interface Bird {
    void fly();
}

class Monkey {
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public void jump() {
        System.out.println("猴子会跳!");
    }
}

class LittleMonkey extends Monkey implements Fish, Bird {
    public void swimming() {
        System.out.println("学到了鱼的游泳!");
    }

    public void fly() {
        System.out.println("学到了鸟飞翔!");
    }
}

注意:

实现接口可以看作是对继承的一种补充。

实现接口可在不打破继承关系的前提下,对某个类功能扩展,非常灵活。

32.new 运算符背后 (自学)

new 运算符,new 创建对象实例(new User("aa")),对象引用(s)指向对象实例。 

 对象实例在堆内存中, 对象引用存放在栈内存中。 

 一个对象引用可以指向 0 个或 1 个对象 (一根绳子可以不系气球,也可以系一个气球); 

一个对象可以有 n 个引用指向它(可以用 n 条绳子系住一个气球)。 

String s = new String("aa");
String s1= s;
33.程序进程与线程

程序只是一组指令的有序集合,它本身没有任何运行的含义,它只是一个静态的实体,由一堆可执行脚本、配置文件组成。

进程是指运行中的应用程序,每个进程都有自己独立的地址空间(内存空间),比如用户点击桌面的 IE 浏览器,就启动了一个进程,操作系统就会为该进程分配独立的地址空间。当用户再次点击左面的 IE 浏览器,又启动了一个进程,操作系统将为新的进程分配新的独立的地址空间。目前操作系统都支持多进程。

线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。线程有就绪、阻塞和运行三种基本状态。

要点:用户每启动一个进程,操作系统就会为该进程分配一个独立的内存空间。

线程与进程的区别与联系

  1. 线程是轻量级的进程
  2. 线程没有独立的地址空间(内存空间)
  3. 线程是由进程创建的(寄生在进程)
  4. 一个进程可以拥有多个线程(这就是我们常说的多线程编程)
34.继承 Thread

多线程编程是指一个程序可以同时运行多个任务,每个任务由一个单独的线程来完成。也就是说,多个线程可以同时在一个程序中运行,并且每一个线程完成不同的任务。程序可以通过控制线程来控制程序的运行,例如线程的等待、休眠、唤起线程等。
Java 中有两种方法创建线程: 一种是对 Thread [θred] 类进行派生并覆盖 run 方法;另一种是通过实现 Runnable 接口创建。
继承 Thread:
创建一个线程的第一种方法是创建一个新的类,该类继承 Thread 类,然后创建一个该类的实例。继承类必须重写 run() 方法,该方法是新线程的入口点。它也必须调用 start() 方法才能执行。该方法尽管被列为一种多线程实现方式,但是本质上也是实现了 Runnable 接口的一个实例。

public class Cat extends Thread {
    int times = 0;

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(1000);//线程睡眠 1000 毫秒(一秒钟)
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            times++;
            System.out.println("hello,world!" + times);
            if (times == 10) {
                break;
            }
        }
    }
}
class ThreadTest {
    public static void main(String[] args) throws InterruptedException {
        Cat cat = new Cat();
        cat.start();
        for (int i = 0; i < 10; i++) {
            Thread.sleep(1000);
            System.out.println(i);
        }
    }
}

如上,Cat 继承 Thread 类创建线程,主线程每秒输出一个数字。

35.实现 Runnable 接口

创建一个线程,另一个方法是创建一个实现 Runnable 接口的类,重写 run() 方法。使用实现 Runnable 接口的方式创建多线程需要创建 Thread 类辅助它运行,Thread 定义了几个构造方法,我们经常使用的 Thread(Runnable runnalbe) 来启动一个 Runnable 子类的线程。

public class Dog implements Runnable {
    @Override
    public void run() {
        int times = 0;
        while (true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            times++;
            System.out.println("hello,world!" + times);
            if (times == 10) {
                break;
            }
        }
    }
}
class RunnableTest{
    public static void main(String[] args) throws InterruptedException {
        Dog dog = new Dog();
        Thread thread = new Thread(dog);
        thread.start();
        for (int i = 0; i < 10; i++) {
            Thread.sleep(1000);
            System.out.println(i);
        }
    }
}

案例:

class Pig implements Runnable {
    int n = 0;
    int times = 0;

    public Pig(int n) {
        this.n = n;
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            times++;
            System.out.println("我是一个 Pig,正在输出第" + times + "个 hello world!");
            if (times == n) {
                break;
            }
        }
    }
}

class Bird implements Runnable {
    int n = 0;
    int res = 0;
    int times = 0;

    public Bird(int n) {
        this.n = n;
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            res += (++times);
            System.out.println("当前结果是:" + res);
            if (times == n) {
                System.out.println("最后的结果是:" + res);
                break;
            }
        }
    }
}
public class Test{
    public static void main(String[] args) {
        Pig pig = new Pig(10);
        Bird bird = new Bird(12);
        Thread thread1 = new Thread(pig);
        Thread thread2 = new Thread(bird);
        thread1.start();
        thread2.start();
    }

}

从 java 的设计来看,通过继承 Thread 或者实现 Runnable 接口来创建线程本质上没有区别,从 jdk 帮助文档我们可以看到 Thread 类本身就实现了 Runnable 接口。由于 java 的单继承机制,所以尽可能使用实现 Runnable 接口的方式来创建线程。
注意:结束线程方式
System.exit(0);//结束主线程和主线程创建的子线程
return;//在主线程中只能结束主线程

36.线程的同步

因为当我们有多个线程要同时访问一个变量或对象时,如果这些线程中既有读又有写操作时,就会导致变量值或对象的状态出现混乱,从而导致程序异常。这时就需要让进程排队进入某些代码块 java 中庸 synchronized ['sɪŋkrənaɪzd]关键字控制线程同步也叫同步锁。

java 任意类型(不包括基本数据类型)的对象都有一个标志位,该标志位具有 0、1 两种状态,其开始状态为 1

当某个线程执行了 synchronized(Object) 语句后,object 对象的标志位变为 0 的状态,直到执行完整个 synchronized 语句中的代码块后,该对象的标志位又回到 1 状态。

当一个线程执行到 synchronized(Object) 语句的时候,先检查 Object 对象的标志位,如果为 0 状态,表明已经有另外的线程正在执行 synchronized 包括的代码,那么这个线程将暂时阻塞,让出 CPU 资源,直到另外的线程执行完相关的同步代码,并将 Object 对象的标志位变为 1 状态,这个线程的阻塞就被取消,线程能继续运行,该线程又将 Object 的标志位变为 0 状态,防止其它的线程再进入相关的同步代码块中。 

如果有多个线程因等待同一个对象的标志位面而处于阻塞状态时,当该对象的标志位恢复到 1 状态时,只会有一个线程能够进入同步代码执行,其它的线程仍处于阻塞的状态。 

案例:买票系统

class TicketWindow implements Runnable {
    private int nums = 2000;

    public void run() {
        while (true) {
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            //synchronized (this) {//同步代码块
                if (nums > 0) {
                    System.out.println("当前线程:" + Thread.currentThread().getName() + "正在售出第:" + nums + "张票");
                    nums--;
                } else {
                    //售票结束
                    break;
                }
            //}
        }
    }
}

public class Thread05 {
    public static void main(String[] args) {
        //定义一个售票窗口
        TicketWindow tw1 = new TicketWindow();
        //使用三个线程同时启动
        Thread t1 = new Thread(tw1);
        Thread t2 = new Thread(tw1);
        Thread t3 = new Thread(tw1);
        t1.start();
        t2.start();
        t3.start();
    }
}

一般同步的实现方式有两种,同步方法和同步块,这两种方式都要用到 synchronized 关键字。
1.同步方法:即有 synchronized 关键字修饰的方法。 由于 java 的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。代码如:

public synchronized void save(){} 

注:synchronized 关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类
2.同步代码块:即有 synchronized 关键字修饰的语句块。被该关键字修饰的语句块会自动被加上内置锁,从而实现同步代码如:

synchronized(object){ }

注:同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用 synchronized 代码块同步关键代码即可。
 

37.死锁

为了保证数据安全使用 synchronized 同步机制,当线程进入堵塞状态(不可运行状态和等待状态)时,其他线程无法访问那个加锁对象(除非同步锁被解除),所以一个线程会一直处于等待另一个对象的状态,而另一个对象又会处于等待下一个对象的状态,以此类推,这个线程“等待”状态链会发生很糟糕的情形,即封闭环状态(也就是说最后那个对象在等待第一个对象的锁)。此时,所有的线程都陷入毫无止境的等待状态中,无法继续运行,这种情况就称为“死锁”。

虽然这种情况发生的概率很小,一旦出现,程序的调试变得困难而且查错也是一件很麻烦的事情。下面举一个死锁的例子。

public class ThreadLocked implements Runnable {
    public static boolean flag = true;  //起一个标志作用
    private static Object a = new Object();  //声明,并初始化静态 Object 数据域 A
    private static Object b = new Object();  //声明,并初始化静态 Object 数据域 B

    @Override
    public void run() {
        try {
            if (flag) {//当 flag 为 true,执行下面语句
                accessA();  //调用 AccessA 方法
            } else {
                accessB();  //调用 AccessB 方法
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    public static void main(String[] args) throws InterruptedException {
        Runnable r1 = new ThreadLocked(); //创建,并初始化 ThreadLocked 对象 r1
        Thread t1 = new Thread(r1);  //创建线程 t1
        Runnable r2 = new ThreadLocked(); //创建,并初始化 ThreadLocked 对象 r2
        Thread t2 = new Thread(r2); //创建线程 t2
        t1.start(); //启动线程 t1
        t2.start(); //启动线程 t2
    }

    public void accessA() throws InterruptedException {
        flag = false;  //初始化域 flag
        //同步代码快
        synchronized (a) {  //声明同步块,给对象 A 加锁
            System.out.println("线程 t1 : 我得到了 A 的锁"); //输出字符串信息
            //让当前线程睡眠,从而让另外一个线程可以先得到对象 B 的锁
            Thread.sleep(1000); //休眠
            System.out.println("线程 t1 : 我还想要得到 B 的锁");
            //在得到 A 锁之后,又想得到 B 的锁
            //同步块内部嵌套同步块
            synchronized (b) {  //声明内部嵌套同步块,指定对象 B 的锁
                System.out.println("线程 t1 : 我得到了 B 的锁"); //输出字符串信息
            }
        }
    }

    public void accessB() throws InterruptedException {
        flag = true;  //修改 flag 的值
        //同步代码块
        synchronized (b) {  //指定同步块,给 B 加锁
            System.out.println("线程 t2 : 我得到了 B 的锁"); //输出字符串信息
            //让当前线程睡眠,从而让另外一个线程可以先得到对象 A 的锁
            Thread.sleep(1000); //休眠
            System.out.println("线程 t2 : 我还想要得到 A 的锁"); //字符串信息输出
            //在得到 B 锁之后,又想得到 A 的锁
            //同步块内部嵌套内部快
            synchronized (a) {  //指定同步块,给 A 加锁
                System.out.println("线程 t2 : 我得到了 A 的锁"); //输出字符串信息
            }
        }
    }
}

运行后如下:

线程 t1 : 我得到了 A 的锁
线程 t2 : 我得到了 B 的锁
线程 t2 : 我还想要得到 A 的锁
线程 t1 : 我还想要得到 B 的锁

分析:创建了两个线程 t1 和 t2,并且声明两个方法:accessA 和 accessB。在运行过程中,线程 t1 先获得了 A 的锁,然后又要求获得 B 的锁;而 t2 先获得 B 的锁,然后又要求获得 A 的锁,此时便进入了无休止的相互等待状态,即死锁。

Java 语言本身并没有提供防止死锁的具体方法,但是在具体程序设计时必须要谨慎,以防止出现死锁现象。通常在程序设计中应注意,不要使用 stop()、suspend()、resume()以及 destroy()方法。

stop()方法不安全,它会解除由该线程获得的所有对象锁,而且可能使对象处于不连贯状态,如果其他线程此时访问对象,而导致的错误很难检查出来。suspend()/resume()方法也极不安全,调用 suspend()方法时,线程会停下来,但是该线程并没有放弃对象的锁,导致其他线程并不能获得对象锁。调用 destroy()会强制终止线程,但是该线程也不会释放对象锁。

死锁产生的 4 个必要条件

1、互斥: 某种资源一次只允许一个进程访问,即该资源一旦分配给某个进程,其他进程就不能再访问,直到该进程访问结束。

2、占有且等待: 一个进程本身占有资源(一种或多种),同时还有资源未得到满足,正在等待其他进程释放该资源。

3、不可抢占: 别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来。

4、循环等待: 存在一个进程链,使得每个进程都占有下一个进程所需的至少一种资源。

当以上四个条件均满足,必然会造成死锁,发生死锁的进程无法进行下去,它们所持有的资源也无法释放。这样会导致 CPU 的吞吐量下降。所以死锁情况是会浪费系统资源和影响计算机的使用性能的。那么,解决死锁问题就是相当有必要的了。

38.IO

I/O输入/输出(Input/Output)在 java 中表示内存与硬盘的内容输入输出,在整个 java.io 包中最重要的就是 5 个类和 1 个接口。5 个类指的是 File、OutputStream、InputStream、Writer、Reader;一个接口指的是 Serializable [ˈsɪərɪəlaɪzəbl] 掌握了这些 IO 的核心操作那么对于 Java 中的 IO 体系也就有了一个基本的认识。

如何判断是输入流、输出流?

以内存为参照,如果数据流向内存流动,则是输入流;如果数据向内存外流动则是输出流。换句话说,文件保存到硬盘就是输出流。从硬盘打开文件就是输入流。

流分为两种 

  1. 字节流:可以用于读写二进制文件及任何类型文件 byte ,比如 图片、pdf 文件等。
  2. 字符流:可以用于读写文本文件,不能操作二进制文件,比如 java文件、记事本文件。

        字节流                 字符流 

输入 InputStream        Reader 

输出 OutputStream     Writer

注意:为了简化代码,该章节的全部异常都直接往上抛出。

按顺序给出数据存储单位:bit、Byte、KB、MB、GB、TB、PB、EB、ZB、YB、BB、NB、DB。 

1Byte = 8bit;1K = 1024Byte;1MB = 1024K;1G = 1024M;1T = 1024G;1P = 1024T;

39.File

File(文件特征与管理类):用于文件或者目录的描述信息,例如生成新目录,修改文件名,删除文件,判断文件所在路径等。

import java.io.File;
import java.io.IOException;

public class FileTest {
    public static void main(String[] args) throws IOException {
        //操作文件
        File f1 = new File("C://aa.txt");
        System.out.println("绝对路径:" + f1.getAbsolutePath());
        if (!f1.exists()) {//文件是否存在
            f1.createNewFile();//创建新文件
        } else {
            System.out.println("文件已存在");
        }

        //操作文件夹
        File f2 = new File("C:/file/aa/bb");
        if (f2.isDirectory()) {//文件夹是否存在
            System.out.println("文件夹已存在");
        } else {
            f2.mkdir();//创建文件夹(单层)
            //f2.mkdirs();//创建文件夹(多层)
        }

        //列出文件夹下面的所有文件
        File f3 = new File("c:/file");
        if (f3.isDirectory()) {
            File[] files = f3.listFiles();
            for (int i = 0; i < files.length; i++) {
                File file = files[i];
                System.out.println(file.getName());
            }
        }
    }
}

如上,便是 File 对文件和文件夹的一般操作。
 

40.字节输入流 FileInputStream

InputStream(二进制格式操作):抽象类,基于字节的输入操作,是所有输入流的父类。定义了所有输入流都具有的共同特征。

文件字节输入流 FileInputStream 就是其中最常用的子类,该类最常用的方法是 read 方法,该方法每次调用都需要传入一个 byte[] 并返回该次读取到的字节数量,如果读取到达文件末尾则返回 -1。

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class FileInputStreamTest {
    public static void main(String[] args) throws IOException {
        File file = new File("C:/file/log.txt");
        InputStream inputStream = new FileInputStream(file);
        byte[] bytes = new byte[1024];
        int n = 0;
        while ((n = inputStream.read(bytes)) != -1) {
            System.out.println(n);
            String s = new String(bytes, 0, n);
            System.out.println(s);
        }
        inputStream.close();
    }
}

字节输入流一般针对文本文件和二进制文件的读取操作。但是需要注意以下几点:

  1. 字节输入流读取文件时,一般前几次返回的读取的字节数量都为字节数组的长度,只有最后一次可能到达不了数组的长度。
  2. 字节输入流可以获取文件的内容,针对字符文件可以将字节数组转为字符串输出 new String(bytes,0,n),但是有时可能会将中文字符切成两半而出现乱码。
  3. UFT-8 编码中,大部分中文占用三个字节,可以使用字符串的 getBytes 方法获取任意字符串在 UTF-8 编码时的字节序列(数组)。
  4. 字节输入流读取二进制文件后转换为字符串后输出为乱码。
  5. 如果文件编码是 ANSI 编码,则输出时使用字符编码 GB2312,否则输出乱码。new String(bytes,0,n,"GB2312")。win7 系统文本文件默认为 ANSI 编码。

注意:如果出现文件找不到报错,一般是因为文件后缀没有开启,从而文件名部分隐藏,无法匹配导致,建议开启文件后缀名。

41.字节输出流 FileOutputStream

OutputStream(二进制格式操作):抽象类。基于字节的输出操作。是所有输出流的父类。定义了所有输出流都具有的共同特征。

文件字节输出流 FileOutputStream 就是其中最常用的子类,该类有一个常用的方法 write 用于将字节数组写入文件。如果在写入过程中需要换行可以使用转义字符 \n 表示。在不关闭流的情况下每次 write 的内容都会追加在文件中。如果输出流指定的文件不存在,则文件创建输出流时会自动创建该文件,但是前提是父路径必须存在,不可以将输出流的路径直接指向文件或盘符。

public class FileOutputStreamTest {
    public static void main(String[] args) throws Exception {
        File f = new File("C:\\file\\ss.txt");//直接覆盖写同一个文件
        //字节输出流
        OutputStream outputStream = new FileOutputStream(f);
        String s = "hello,world!\r\n";
        String s1 = "中国人";
        outputStream.write(s.getBytes());
        outputStream.write(s1.getBytes());
        outputStream.close();
    }
}

注意:在程序运行期间如果不手动关闭流,则该文件会一直占用,无法删除。

42.字符流 FileReader 和 FileWriter

字符文件输入 FileReader、输出 FileWriter 与 InputStream、OutputStream 操作基本相同,只是它们操作文件时使用的是字符数组。FileReader 和 FileWriter 只能用来操作文本文件,不能用来操作二进制文件。而 FileOutputStream 与 FileInputStream 既可以操作二进制文件,又可以操作文本文件(根本原因就是一个字符通常占用一个或多个字节)。
案例:使用字符流拷贝文本文件

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class CopyTxtFile {
    public static void main(String[] args) throws IOException {
        //文件取出字符流对象(输入流)
        FileReader reader = new FileReader("C:/file/log.txt");
        //写入到文件(输出流)
        FileWriter writer = new FileWriter("C:/log.txt");
        //创建字符数组
        char[] chars = new char[1024];
        int n = 0;
        while ((n = reader.read(chars)) != -1) {
            String string = new String(chars, 0, n);
            System.out.println(string);
            writer.write(chars, 0, n);
        }
        writer.close();
        reader.close();
    }
}
43.缓冲字符流 BufferedReader 和 BufferedWriter

在字符文件操作方面为了提高效率引入了缓冲字符流 BufferedReader 和 BufferedWriter。它们操作文件的基本单位可以升级为一行,无论该文件一行有多少字符都能通过 readLine 读取,同时写入时可以直接指定任意的字符串。

import java.io.*;

public class BufferedCopyTxtFile {
    public static void main(String[] args) throws Exception {
        FileReader reader = new FileReader("C:\\ff\\hsp.txt");
        BufferedReader bufferedReader = new BufferedReader(reader);
        FileWriter writer = new FileWriter("C:\\hsp1.txt");
        BufferedWriter bufferedWriter = new BufferedWriter(writer);
        String s = "";
        while ((s = bufferedReader.readLine()) != null) {
            bufferedWriter.write(s + "\n");
        }
        bufferedReader.close();
        bufferedWriter.close();
        reader.close();
        writer.close();
    }
}

缓冲流在原来基础上增加自己的一些方法如:readLine 读一行等一系列升级操作,在字符文件操作上有很好的性能和扩展。
关于输入流与输出流关闭问题需要注意以下几点:
1.输入流如果不关闭。在进程运行期间,该文件一直被占用,无法删除。
2.输出流不关闭文件内容将不能正常保存到该文件中。
3.流的关闭顺序一般要求与打开顺序相反,即:后开先关。

44.properties 配置文件的读取

在实际开发中经常会把一些易发生改变的配置信息放到配置文件中(如:数据库的连接信息,文件上传的位置信息等),一般在 java 中配置文件常用properties 格式,该文件需要在 src 下创建。如下,在 src 下创建 application.properties 文件。

id=12
name=wpf

2.读取配置文件里面值

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public class PropertiesTest {
    public static void main(String[] args) throws IOException {
        InputStream inputStream = PropertiesTest.class.getClassLoader().getResourceAsStream( "application.properties" );
        //读取src下名字叫thisFile的文件
        Properties properties = new Properties();
        //作用是从文件流里面获取键值对
        properties.load( inputStream );//加载文件流
        System.out.println( properties.getProperty( "name" ));
        stream.close();
    }
}

轻松使用 properties.getProperty( "key" ) 方法就可以通过健获取文件流里面的值。
注意:关于中文乱码问题,创建的文件 properties 文件默认编码为 gbk,包含中文时会乱码,需要调整项目的默认编码为 UTF-8,同时要勾选 Transparent native-to-ascii conversion,再重新创建 properties 文件即可正常读取中文。

45.对象序列化和反序列化

Java 序列化是指把 Java 对象转换为字节序列 byte[] 的过程;而 Java 反序列化是指把字节序列恢复为 Java 对象的过程。

在程序运行过程中,经常会把对象序列化到到磁盘文件中,以保存对象数据。在需要的时候再从文件中反序列化出来,完成对象的持久化。要想类的对象能够序列化和反序列化,该类必须实现 Serializable 接口,否则会报 NotSerializableException。

案例:

import java.io.*;
import java.util.Scanner;

public class SerializableTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        // writeObject();
        readObject();
    }

    private static void readObject() throws IOException, ClassNotFoundException {
        FileInputStream inputStream = new FileInputStream("C:/person.txt");
        ObjectInputStream stream = new ObjectInputStream(inputStream);
        //获取对象,由于无法判断对象的类型,故采用 Object 类型接收。
        Object o = stream.readObject();
        //确定是 Person 类型后可以将之强转为 Person 类型
        Person person = (Person) o;
        System.out.println(person);
        stream.close();
        inputStream.close();
    }

    private static void writeObject() throws IOException {
        //Scanner 专门用于接收控制台输入的数据
        Scanner scanner = new Scanner(System.in);
        FileOutputStream outputStream = new FileOutputStream("C:/person.txt");
        ObjectOutputStream stream = new ObjectOutputStream(outputStream);

        System.out.println("请输入 id");
        //通过 scanner 获取一个整数,程序运行到该位置会自动卡住,直到用户输入内容后回车程序才能继续。
        int id = scanner.nextInt();
        System.out.println("请输入名字");
        String name = scanner.next();
        System.out.println("请输入密码");
        String password = scanner.next();

        Person person = new Person(id, name, password);
        //序列化对象到文件
        stream.writeObject(person);
        //刷新
        stream.flush();
        //关闭资源
        stream.close();
        outputStream.close();
        scanner.close();

    }
}

class Person implements Serializable {
    private int id;
    private String name;
    private String password;

    ...省略 构造方法、get、set 与 toString 方法。
}

注意:对象写入和读出之间不能修改类的结构,否则会报:InvalidClassException。

java.io.InvalidClassException: cn.hx.Person; local class incompatible: 
stream classdesc serialVersionUID = 2431545036432879007, local class serialVersionUID = -2205805426406937374
46.线程的几种可用状态 (自学)

1. 新建( new ):新创建了一个线程对象。

2. 可运行( runnable ):线程对象创建后,其他线程(比如 main 线程)调用了该对象 的 start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取 cpu 的使用权 。 

3. 运行( running ):可运行状态( runnable )的线程获得了 cpu 时间片( timeslice ) ,执行程序代码。 

4. 阻塞( block ):阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice ,暂时停止运行。直到线程进入可运行( runnable )状态,才有 机会再次获得 cpu timeslice 转到运行( running )状态。

阻塞的情况分三种:

 (一). 等待阻塞:运行( running )的线程执行 wait() 方法, JVM 会把该线程放入等待队列( waitting queue )中。

 (二). 同步阻塞:运行( running )的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池( lock pool )中。

 (三). 其他阻塞: 运行( running )的线程执行 Thread.sleep( long ms) 或 t.join()方法,或者发出了 I/O 请求时, JVM 会把该线程置为阻塞状态。

当 sleep ()状态超时、join ()等待线程终止或者超时、或者 I / O 处理完毕时,线程重新转入可运行( runnable )状态。 

5. 死亡( dead ):线程 run ()、 main () 方法执行结束,或者因异常退出了 run ()方法,则该线程结束生命周期。死亡的线程不可再次复生。 

举例:

早上打车去上班 新建(准备叫一辆嘀嘀打车) 

可运行(找到一辆可以带你去上班的车) 

运行(司机接到你,带你去上班)

阻塞(路上堵车了) 

死亡(到公司了,付钱下车)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值