这是我当初学习java时的一些笔记,这几天翻出来看了一下,感觉可以作为一些参考,由于当时我有C/C++的基础,所以学习的速度非常快,有些地方直接和C/C++进行了对比,所以在观看的时候最好有一些编程基础。

语言只是算法的载体,会得再多,也仅仅是获得了一个又一个躯壳,真正称得上灵魂的是算法,一个没有灵魂的躯壳,只能算一个行尸走肉。

开始前的一些准备

Java的三大版本

  • JavaSE:标准版(桌面应用程序,控制台开发)
  • JavaME:嵌入式开发(手机app)
  • JavaEE:企业开发

JDK、JRE、JVM

  • JDK:Java开发者工具(用来开发Java程序)
  • JRE:Java运行环境(可以运行Java的程序)
  • JVM:Java虚拟机(核心部分)

虚拟机机制

.java文件编译成.class文件,在虚拟机(JVM)上运行,实现“一次编译处处运行”。

垃圾回收机制

C/C++是程序员手动回收内存,高效准确,但容易失误。

java自动垃圾回收,可以自动检测内存,虽然规避了失误,但不会非常高效。

环境搭建

官网下载JDK,尽量使用稳定版本,推荐使用压缩包进行解压,之后再进行环境变量配置,在版本上可以更好的更换。

配置环境变量包括:JDK\binJDK\jre\bin

检测方法,输入java -version会出现:

1
2
3
java version "1.8.0_101"
Java(TM) SE Runtime Environment (build 1.8.0_101-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.101-b13, mixed mode)

第一个程序

  1. 建立Test.java文件,用来写代码。

  2. 写下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    public class Test{
    public static void main(String[] args){
    // 打印后不会换行
    System.out.print("hello world");
    // 打印后自动换行
    System.out.println("hello world");
    }
    }

    注意,类的名字和文件名字要一致。

  3. 打开命令窗口,cd到目标文件的路径,输入以下指令进行编译:

    1
    javac Test.java

    编译完之后就会出现一个名字相同的.class文件。

  4. 运行Test.class文件,需要输入以下程序:

    1
    java Test

    基本语法

这一部分的特点是java的基础部分,大部分非常和C/C++类似,所以有点基础的话简单看看就基本了解。

注释

  • 单行注释:同C/C++,//注释内容

  • 多行注释:同C/C++,/*注释内容*/

    文档注释:java特有

    1
    2
    3
    4
    5
    6
    7
    /**
    *类名
    *描述
    *@author 作者名
    *@version 版本
    *@since jdk版本
    */

    命名规范

不是强制,但更具有规范性。

  • 包名:所有字母小写。
  • 类名、接口名:大驼峰命名。
  • 变量名、方法名:小驼峰命名。
  • 常量名:所有字母大写,多单词时用下划线分割。

数据类型

注:八种基本数据类型之外全是引用数据类型

基本数据类型

  • 整型
    • byte:1字节
    • short:2字节
    • int:4字节
    • long:8字节
  • 浮点型
    • float:单精度(7位有效数字),4字节
    • double:双精度(16位有效数字),8字节
  • 字符型:char
  • 布尔型:boolean

引用数据类型

  • 接口
  • 数组

String类型

引用类型的一种,当出现两个相同的内容时,会分配相同的内存。

可以使用“+”来进行拼接,例如:

1
String str = "hello " + "world";

注:只要有一个String类型进行相加时,就会合并成字符串,但前面会进行直接计算。

数据类型转换

基本转换

(char、byte、short) —> int —> long —> float —> double

强制转换

举个例子:

1
2
int number = 30;
byte temp = (byte)number;

虽然强制转换很不错,这种操作会出现溢出现象,而且只针对基本数据类型。

注:布尔类型不能转换成其他类型。

运算符

数学运算符、赋值运算符、逻辑运算符、比较运算符、位运算符、三元运算符,这些操作和C/C++类似,不再赘述。

执行顺序

条件语句、循环语句、特殊语句(break、continue、return),这些操作和C/C++类似,不再赘述。

数组

声明和初始化

变量声明完就要初始化,这涉及到一些java运行机制,所以必须这样操作。但在声明数组的时候可以不用初始化,值得注意的是,java数组声明和C/C++有点不同:

1
2
3
// 声明一个int数组
int[] num;
int num[];

和C/C++不同点主要是可以把中括号直接放前面。

虽然可以不用初始化,但一般还是直接初始化,这里需要使用new关键字,new作用就是分配空间。

以下为动态初始化:

1
2
// 声明一个int数组,并初始化
int[] num = new int[10];

动态初始化不会进行赋值,所以初始化完后会是默认值,初始化同时需要说明数据类型和分配大小。

还有一种静态初始化:

1
2
// 声明一个int数组,并初始化
int[] num = new int[]{0,1,2,3,4,5,6,7,8,9};

这种直接初始化并赋值,不用说明空间申请大小。

内部方法和属性

每个数组提供了一个length属性,可以利用这个属性直接查看这个数组的大小:

1
int len = num.length;

多维数组

其实就是每个数组中的每个元素就是一个数组,这样可以进行不断的数组嵌套。

书写方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 动态初始化
int num1[][] = new int[3][4];

// 静态初始化
int num2[][] = new int[][]{
{1,2,3},
{4,5,6},
{7,8,9}
};

// 只定义第一个维度
int num3[][] = new int[3][];

// 可以不相同
int num2[][] = new int[][]{
{1,2},
{3,4,5,6},
{7,8,9}
};

核心部分

java是一个面向对象的语言,比C++和Python还要强调对象的概念,这部分主要去讲面向对象的一些操作。

写一个类

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Persion{
// 属性(成员变量)
String name;
int age = 18;
boolean sex;

// 方法(成员函数)
public void showName(){
System.out.println("姓名:" + name);
}
public int getAge(){
return age;
}
}

成员变量是可以不用初始化的,因为有默认值。

新建文件,类和文件名相同。

实例化一个类

示例:

1
2
3
4
5
6
// 声明一个Persion对象,并实例化
Persion persion;
persion = new Persion();

//与上面相同
Persion persion = new Persion();

匿名对象

不定义对象句柄,直接实例化,接着使用内部的方法。

1
new Persion().showName();

特点就是只使用一次就可以,或者将其作为实参直接传递。

类的属性

语法格式:修饰符 类型 属性名 = 初值

修饰符

  • public:公有,外部可以直接访问。
  • private:私有,只能通过类的内部访问。

成员变量的两种方式

  • 实例变量:最普通的变量,类实例化之后才能使用

  • 类变量:在中间加上static进行修饰,类不用实例化,直接通过类名.属性就可以使用,存在于静态域。

    static的方法只能访问static的变量,但普通方法可以访问static变量。

类的方法

完成某些功能的函数。

参数传递

java的参数传递方式很有意思,准确来讲是值传递

首先讲一下虚拟机的内存模型:

虚拟机中有栈(stack)、堆(heap)和方法区(method):

  • 栈(stack):保存了所有的基本数据类型和引用数据类型的地址。
  • 堆(heap):所有对象,引用数据类型的地址指向这里。
  • 方法区(method):所有class和static变量

当使用基本数据类型的时候,由于值传递,直接是拷贝了数据放在栈中,函数结束的时候直接销毁。

当使用引用数据类型的时候,由于值传递,拷贝了这个引用类型的地址,所以放在栈中的是该对象的地址(也就是指针),那么接下来使用的时候该地址指向了原对象,这就很有意思了,直接变成了引用传递。

方法重载

一个类中可以存在多个同名方法,前提是参数的个数不同或者类型不同。

多参数传递

使用数组传递可变参数
1
2
3
4
5
6
7
8
// 定义
public void show(String[] args){
// 函数的内容
}

// 使用
String str = new String[]{"a","b","c"}
persion.show(str);
使用java特有方式

其实和数组的使用方式相同。

1
2
3
4
5
6
7
8
9
10
// 定义
public void show(String... args){
// 函数的内容
}

// 使用
String str = new String[]{"a","b","c"}
persion.show(str);
// 也可以这样用
persion.show("a","b","c");

总结:

  • 第二种可以不写参数,但使用第一种的时候需要将null(空数组)传递进去。
  • 必须放在最后,前面部分放其他类型。

java没有命名空间的概念,但包的管理解决了命名冲突的问题,同时方便管理了其他的程序。

本质就是一个文件夹,文件夹中的每一个程序文件里最开始都有一句语句:

1
package 顶层包名.子包名;

这句指明该类所属的包,如果同一个包下调用文件可以不用说明。

比较常用的命名方式:

  • 小写,这样可以区分类文件
  • 使用公司倒置命名,例如百度的域名:www.baidu.com,可以命名成com.baidu.www

调用的方式也很简单(上面的例子):import com.baidu.www.Demo

封装

修饰符的区别

修饰符 类内部调用 同一包内调用 子类调用 其他地方
private Yes
(缺省) Yes Yes
protect Yes Yes Yes
public Yes Yes Yes Yes

构造方法

构造函数与类名相同,没有返回值,当实例化这个对象的时候就会调用这个方法。

1
2
3
4
5
6
7
public class Persion{
public Persion(int age, String name){

}
}

Persion persion = new Persion(18,"wang");

如果定义了构造函数,就不会使用默认的构造函数。

同样道理,构造函数也存在重载,可以提供多种初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Persion{
public Persion(int age, String name){
// 构造函数1
}
public Persion(int age){
// 构造函数2
}
public Persion(String name){
// 构造函数3
}
public Persion(){
// 构造函数4
}
}

Persion persion = new Persion(18,"wang");
Persion persion = new Persion(18);
Persion persion = new Persion("wang");
Persion persion = new Persion();

this关键字

表示当前对象,可以调用自己成员,主要是区分形参和类的成员变量的重名。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Persion{
public int age;
public String name;

public Persion(int age, String name){
this.name = name;
this.age = age;
}

public show(){
System.out.println("姓名:" + this.name);
}
}

可以让this()为自己调用自己的构造函数,但必须有一个不用this()的构造函数。

继承

示例:

1
2
3
public class Student extends Persion{
String school;
}

使用extends继承了Persion类,拥有Persion类的所有成员。

注:不要仅为了一个功能去继承。

java只支持单继承,不允许多继承,允许多层继承。

方法重写

方法重写可以更改从父类继承过来的函数,方便更好的使用。

要求:

  • 必须拥有相同的函数名、形参和返回值。
  • 不能使用更严格的访问权限。
  • 需要同时为static或非static
  • 子类抛出异常不能大于父类重写异常。

理论上直接去重写就可以,加上@Override会更直观一点。

我使用的是idea,快捷键是Ctrl + o可以快速重写。

super关键字

作用:

  • 可以访问父类的属性。
  • 可以调用父类的方法。
  • 可以调用父类的构造方法。

多态

具体体现在重写(针对子类重写)和重载(重名)上。

关键字static

只有通过new关键字才能实例化对象,关键字static修饰的变量或方法是类所有,实例化的对象可以共享此变量或方法。不想频繁修改或调用,就可以用static修饰的变量或方法,这样可以节省内存。

其他操作

instanceof操作符

检查一个对象是否是一个类型,如果是就返回true

1
2
3
4
// 检查e是否是Persion类型
if(e instanceof Persion){

}

Object

是所有类的根父类(基类),即使没有继承,默认也继承基类。

如果想用一个对象作为形参,但不知道需要用什么数据类型时,可以使用Object类型,这样可以接受各种参数:

1
2
3
public void test(Object obj){

}

类型转换

子类可以直接转换父类:

1
2
Student s = new Student();
Persion p = s;

父类需要强制转换成子类:

1
2
Persion p = new Persion();
Student s = (Student) p;

注:无继承关系不能转换。

类比较大小

  • ==运算符:比较的只是值,基本类型就是直接比较大小,引用类型比较的是引用指针,指向同一个对象的时候会返回true

  • equals()方法:继承了Object的类都具有这个方法,和==相同,也是指向对象的是否相同。

    1
    obj1.equals(obj2);

    对File、String、Data以及包装类来说比较的是内容是否相等。

字面量创建对象的时候,只会在常量池中引用其地址,而用new来分配内存创建对象的时候,会创建对象再考虑常量池。简单来讲字面量创建对象会节省内存。

默认状态下,equals()方法比较的是地址,当然也可以自己定义比较的规则:

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
public class MyDate{
public int year;
public int month;
public int day;

// 构造函数赋值
public MyDate(int year, int month, int day){
this.year = year;
this.month = month;
this.day = day;
}

// 重写函数
public boolen equals(Object obj){
int flag = 1;
// 检查是不是MyDate类型
if(obj instanceof MyDate){
MyDate md = (MyDate) obj;
flag = 0
if(this.year != md.year)
flag++;
if(this.month != md.month)
flag++;
if(this.day != md.day)
flag++;
}
if(flag == 0)
return true;
else
return false;
}
}

包装类

这是对基本数据类型的封装,这样可以使用一些方法:

基本数据类型 包装类
boolean Boolean
byte Byte
short Short
int Integer
long Long
char Character
float Float
double Double
1
2
3
4
5
int num = 10;
// 装箱
Integer num2 = new Integer(num);
// 拆箱
int num3 = num2.intValue();

注:1.5版本后支持自动装箱和拆箱。

常用操作:类型转换

1
2
3
4
// 字符串转数字
int num1 = Integer.parseInt("123");
// 数字转字符串
String str = String.valueof(num1);

.toString()的重写

默认情况下,会返回内存地址,重写可以修改返回内容。并可以直接用变量去返回。

1
2
3
4
Persion p = new Persion();
// 以下两种情况一致
System.out.print(p.toString());
System.out.print(p);

初始化代码块

下面的类包含了一个初始化代码块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Persion{
// 属性(成员变量)
String name;
int age = 18;
boolean sex;

{
// 非静态初始化代码块
}

// 方法(成员函数)
public void showName(){
System.out.println("姓名:" + name);
}
public int getAge(){
return age;
}
}

作用就是对java对象进行初始化,执行顺序就是:声明成员变量的默认值,多个初始化块共同执行,执行构造函数。

初始化代码块可以拥有多个,执行顺序从上到下。

注:静态代码块在类被声明就开始执行,只执行一次

final关键字

这个关键字可以让程序更加安全。

  • final标记的类不能被继承
  • final标记的方法不能被重写
  • final标记的变量只能赋值一次(常量,一般写成大写)

抽象类

针对于父类,不要去写具体方法,在接下来继承中主要有针对地去写。

abstract修饰,可以修饰类和方法,含有抽象方法的类必须声明成抽象类。

抽象类不能实例化,只能作为父类被继承。

不能去修饰属性、私有方法、构造函数、静态方法、final标记的方法。

接口

java不支持多继承,所以可以使用接口来实现多继承的效果。

接口是抽象方法和常量值的集合,本质上是一种特殊的抽象类,只包含常量定义和方法定义,没有实现过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 定义接口
public interface InterfaceA{
int NUM;
String STR;

void fun1();
void fun2();
}

// 类去继承
public class SubClass implements InterfaceA{
public void fun1(){

}
public void fun2(){

}
}

特点:

  • interface来定义
  • 默认成员变量全是public static final修饰
  • 默认方法全是public abstract修饰
  • 没有构造函数
  • 采用多继承机制

注:生成文件最好是接口文件。

类可以继承多个接口:

1
2
3
public class SubClass implements InterfaceA,InterfaceB,InterfaceC{

}

接口可以继承接口:

1
2
public interface InterfaceA extends InterfaceB{
}

注:必须实现接口中所有的方法(重写),否则就是一个抽象类。

接口好处是,修改接口内容,抽象类会不稳定,如果我们新建一个接口,让类去多继承,可以避免这些。

1
2
3
4
public class SubClass extends ClassA implements InterfaceA,InterfaceB,InterfaceC{
// 继承了ClassA类
// 实现了InterfaceA,InterfaceB,InterfaceC接口
}

内部类

顾名思义,类内定义一个类。

其他部分

这部分就比较零散,主要涉及到一些其他操作。

main函数

1
2
3
public static void main(String[] args){

}
  • public:可以外部调用
  • static:类方法
  • void:无返回值
  • String[] args:可以传递字符串数组

在使用命令行运行的时候,可以在后面添加其他参数:

1
java Test abc 123 jjj999

后面部分会进入到args数组中,并且可以为接下来提供参数。

异常处理

程序运行难免会出现错误,一般出现异常就会直接停止程序。

异常分类:

  • Error:JVM内部错误,例如资源消耗严重。
  • Exception:偶尔存在的外部错误,例如读取不存在的文件。

异常捕获

示例:

1
2
3
4
5
6
7
try{
// 有可能发生异常部分
}catch(Exception e){
// 遇到这种异常时的处理异常内容
}finally{
// 最终会执行的内容
}

不知道捕获异常类型时,直接Exception e进行处理。

一些操作:

1
2
3
4
// 直接打印异常
e.printStackTrace();
// 返回异常内容
e.getMessage();

抛出异常

示例:

1
2
3
4
5
6
7
public class ClassA{
int i;
// 主动抛出异常
public void test() throws Exception{

}
}

一般是一个函数去主动抛出异常,如果遇到继承关系,子类重写时也需要抛出异常,但不能写范围小异常。

集合

存放于java.util包中,是存放变量的容器。

  • 集合只能存放对象,基本数据类型会转化为对应的封装类。
  • 存放的方式为引用类型。
  • 可以存放不同类型。
  • JDK5之后增加了泛型,可以记住对象中的数据类型。

分类:

  • Set:无序,不可重复集合。
  • List:有序,可重复集合。
  • Map:具有映射关系集合。

HashSet

用Hash算法来存储集合中的元素,因此不能保证存储顺序,而且不能重复,不是线程安全。

使用方法:

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
public class Test{
public static void main(String[] args){
Set s = new HashSet();
// 等价于 Set<Object> set = new HashSet<Object>();

s.add(obj); // 添加对象
s.remove(obj); // 移除对象
s.contains(obj); // 判断是否有此对象
s.clear(); // 清空集合
s.size(); // 返回集合大小

//迭代器遍历
Iterator it = s.iterator();
while(it.hasNext()){
// 每次循环判断是否有下一个
System.out.println(it.hasNext());
}

// for遍历
for(Object obj : set){
// 把每个集合之中的元素遍历
// 和C++类似
System.out.println(obj);
}

// 使用泛型
// 这样只能存储单一的类型
Set<String> set = new HashSet<String>();
}
}

TreeSet

确保集合排序状态,默认情况下采用自然排序。

使用方法:

1
2
3
4
5
6
public class Test{
public static void main(String[] args){
Set<Integer> set = new TreeSet<Integer>();
// 大部分操作和HashSet一致
}
}

ArrayList

有序的可重复的集合,每个元素都有对应的索引,默认顺序为添加顺序。

使用方法:

1
2
3
4
5
6
public class Test{
public static void main(String[] args){
List<Integer> list = new ArrayList<Integer>();
// 大部分操作和HashSet一致
}
}

HashMap

就是键值对,键不允许重复。

使用方法:

1
2
3
4
5
6
7
8
9
10
public class Test{
public static void main(String[] args){
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("a",1); // 添加一个键值对
map.get("a"); // 通过键提取
map.remove("a"); // 通过键移除
map.size(); // 返回大小
map.containsKey("a"); // 判断是否有这个键
}
}

工具类:Collections

主要提供一些有用的函数,这里不再赘述。

泛型

java泛型只在编译过程有效,编译后直接擦除对应的信息,不会到运行阶段。

泛型类

具体使用和定义方法和C++类似。

定义方法:

1
2
3
4
5
6
7
8
// T代表的是类型
class A<T>{
private T obj;

public void set(T obj){
this.obj = obj;
}
}

泛型接口

定义方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// T代表的是类型
interface B<T>{
private T obj;

public void set(T obj);
}

// 继承接口
class C<T> implements B<T>{
private T obj;

public void set(T obj){
this.obj = obj;
}
}

泛型方法

注:只能在函数内部使用。

静态方法不能使用类定义泛型,如果想使用,只能使用静态方法自己定义的泛型。

定义方法:

1
2
3
4
5
6
7
8
9
10
11
12
class A{

// T代表的是类型
public <T> void set(T obj){
T t = obj;
}

public <T> T set(T obj){
return obj;
}

}

泛型通配符

使用一个List集合充当参数的时候,可以使用?充当通配符。

1
2
3
4
5
public class Test{
public void fun(List<?> list){

}
}

枚举类

如果一个对象有限且固定,可以使用枚举来限制,例如:四季。

定义方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
enum Season{
// 构造函数
SPRING("春天","1"),
SUMMER("夏天","2"),
AUTUMN("秋天","3"),
WINTER("冬天","4");

private final String name;
private final String desc;

private season(String name, String desc){
this.name = name;
this.desc = desc;
}

}

规定了构造函数只能使用自己定义的几个。

使用方法:

1
Season season = new Season.SPRING;

这样就可以单独返回规定的对象,而且获得的是相同对象(地址一样)。

注解

程序员可以在程序中嵌入信息,不会改变逻辑,会保存在name=value对中。

  • @Override:限定重写父方法,只能用于方法。
  • @Deprecated:表示某个类或方法已过时。
  • @SupperWarning:抑制编译器的警告。

IO流

  • 文件流:基于文件的操作。
  • 缓冲流:数据基于内存的操作。

其他内容流懒得写了,看这网站吧。

扩展部分

线程

  • 程序:为了完成某个任务。
  • 进程:正在运行的过程。
  • 线程:进程细分成线程,一个程序的一条执行路径。

Thread实现多线程

通过java.lang.Thread类来实现,每个线程通过Thread对象的run()来实现,通常把run()方法称作线程体。通过start()方法调用这个线程。

构造方法:
  • Thread():创建新的Thread对象。
  • Thread(String threadname):创建新的Thread对象,并指定名。
  • Thread(Runnable target):实现Runnable接口来创建线程目标对象。
  • Thread(Runnable target, String threadname):实现Runnable接口来创建线程目标对象,并指定名。

方法一:

继承Thread类,重写run(),创建子对象的时候就表示创造了线程对象,调用start()方法的时候就可以启动线程。

1
2
3
4
5
6
7
8
9
// 继承Thread
public class MyThread extends Thread{
@Override
public void run(){
System.out.println("线程代码");
for(int i = 0;i < 5;i++)
System.out.println(i);
}
}
1
2
3
4
5
6
7
public class Test{
public static void main(String[] args){
MyThread t0 = new MyThread();
// 启动线程
t0.start();
}
}

方法二:

定义子类,实现Runnable接口,重写run(),使用Thread的构造方法来实现,此方法局限性更小。

1
2
3
4
5
6
7
8
9
// 继承Runnable
public class MyRun implements Runnable{
@Override
public void run(){
System.out.println("线程代码");
for(int i = 0;i < 5;i++)
System.out.println(i);
}
}
1
2
3
4
5
6
7
public class Test{
public static void main(String[] args){
Thread t0 = new Thread(new MyRun());
// 启动线程
t0.start();
}
}
优先级

优先级为1~10,数字越大,优先级越高。默认设置优先级为5,

常用方法
  • start():启动线程
  • run():线程内容
  • getName():返回线程名称
  • setName():设置线程名称
  • static currentThread():返回当前线程
  • getPriority():获取优先级
  • setPriority():设置优先级
  • static yield():线程让步
  • join():线程强行加入(阻塞),一般用try
  • sleep():延时,单位毫秒
  • stop():强制线程结束

线程的生命周期

过程:

  • 新建:实例化一个Thread对象。
  • 就绪:运行.start()之后,等待CPU时间片。
  • 运行:线程就绪之后,进行运行状态。
  • 阻塞:特殊情况时终止自己执行,进入阻塞。
  • 死亡:完成全部工作或强制终止。

线程通信

  • wait():当前线程挂起并放弃CPU、同步资源,让别的线程可以进行访问并修改。当前线程状态为排队等候,等待被唤醒。
  • notify():唤醒排队等候的线程。
  • notifyAll():唤醒排队等候的所有线程。