Java基础
1 java简介
Java三大版本
JavaSE:标准版(桌面程序、控制台开发等)
JavaME:嵌入式开发(手机、小家电等)
JavaEE:E企业级开发(web端、服务器开发等)
JDK、JRE、JVM三者简介

- jdk:java development kit (java开发工具包)
JDK是Java开发工具包,是Sun Microsystems针对Java开发员的产品。
JDK中包含JRE,在JDK的安装目录下有一个名为jre的目录,jre里面有两个文件夹bin和lib,在这里可以认为bin里的就是jvm,lib中则是jvm工作所需要的类库,而jvm和 lib和起来就称为jre。
JDK是整个JAVA的核心,包括了Java运行环境JRE(Java Runtime Envirnment)、一堆Java工具(javac/java/jdb等)和Java基础的类库(即Java API 包括rt.jar)。
- jre:java runtime environment(java运行环境)
jre是运行基于Java语言编写的程序所不可缺少的运行环境。也是通过它,Java的开发者才得以将自己开发的程序发布到用户手中,让用户使用。
JRE中包含了Java virtual machine(JVM),runtime class libraries和Java application launcher,这些是运行Java程序的必要组件。
JRE是Java运行环境,并不是一个开发环境,所以没有包含任何开发工具(如编译器和调试器),只是针对于使用Java程序的用户。
- jvm:java virtual machine(java虚拟机)
就是我们常说的java虚拟机,它是整个java实现跨平台的最核心的部分,所有的java程序会首先被编译为.class的类文件,这种类文件可以在虚拟机上执行。
也就是说class并不直接与机器的操作系统相对应,而是经过虚拟机(相当于中间层)间接与操作系统交互,由虚拟机将程序解释给本地系统执行。
JVM屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。
所有的程序只要有java虚拟机的支持,那么就可以实现程序的执行,并且不同的操作系统上会有不同版本的jvm。
jvm相当于一个容器,放到不同的操作系统中,因为编写的Java程序经过编译后生成的字节码可以被JVM识别,JVM为程序运行屏蔽了底层操作系统的差异
小结:
三者的关系,简单来说就是JDK包含JRE,JRE又包含JVM的关系
2 java环境搭建
JDK下载与安装
- 下载
- 安装
直接安装即可-------路径接自行选择------当出现下一步,直接关闭即可,因为JDK中包含了JRE

配置环境变量
首先找到自己安装JDK的目录-----接着开始屏幕搜索环境变量----点击环境变量,在系统变量进行如下操作
- JAVA_HOME

作用:JDK路径改变后,不需要每个PATH进行更改,只需改变JAVA_HOME中的路径
- CLASS_PATH

另一种普遍配置:.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar
作用:
在cmd中运行java程序时,JRE通过这个CLASSPATH去搜索执行的class文件
注意:
再顺便科普一下,其实这个CLASSPATH环境变量已经没有必要去配置它了,只有早期的JDK版本需要设置CLASSPATH变量。
在JDK9后,它的lib目录中都已经不包含dt.jar和tool.jar这两个包了,而‘.’的搜索当前路径下的class文件这个功能,不去设置CLASSPATH的话,这就是它的默认搜索路径。
jdk1.4以后(不包括1.4)的jdk版本用户可以尝试着删除CLASSPAHT然后进行java命令的使用测试,其实是没有收到影响的。
- path

- 测试安装是否成功
1、检查版本,java -version

2、java

3、javac

3 java入门
注释(增强可阅读性)
// 我是单行注释
/* 我是多行注释 */
/**
我是文档注释
*/
数据类型
- 基本数据类型
整数类型:byte(1字节=8bit)、short(2字节)、int(4字节)、long(8字节)
浮点类型:float(4字节)、double(8字节)
字符型:char(1字符=2字节)
布尔型:boolean
注意:
byte范围:-128 ~ 127,声明long型变量,必须以"l"或"L"结尾,通常,定义整型变量时,使用int型。
float表示数值的范围比long还大,定义float类型变量时,变量要以"f"或"F"结尾,通常,定义浮点型变量时,使用double型(默认)。
定义char型变量,通常使用一对’’,内部只能写一个字符
- 引用数据类型
class(String)、interface、数组
运算符
- 算数运算符
+ - + - * / % (前)++ (后)++ (前)-- (后)-- +
demo:
int a=5;
int b=++a; // ++在前 先运算再赋值 现在 a=b=6;
int c=--b+a; //--优先级比+高,先计算-- c此时b=5再加上a 5+6=11;再赋值
int c=a+(++b); //同理 括号优先级最高 先计算++b 此时b=7 6+7=13;再赋值
- 赋值运算符
= += -= *= /= %=
- 比较运算符
== != > < >= <= instanceof
- 逻辑运算符
& && | || ! ^
- 位运算符
<< >> >>> & | ^ ~
demo:
【面试题】 你能否写出最高效的2 * 8的实现方式?
答案:2 << 3 或 8 << 1
1、<< :在一定范围内,每向左移1位,相当于 * 2
2、>>:在一定范围内,每向右移1位,相当于 / 2
- 三元运算符
(条件表达式)? 表达式1 : 表达式2
两值交换问题
- 方式一,采用中间变量
int a = 1;
int b = 2;
int c = a;
a = b;
b = c;
- 方式二,减少空间法
a = a+b;//a空间存储的是两个元素之和 a==3 b没有变化 b==2
b = a-b;//利用两个元素的和减原来的b剩下是原来的a 赋值给b b==1 a==3
a = a-b;//利用a空间两个元素的和 减 原来的a 剩下是原来的b 赋值给a a==2 b==1
用户交互Scanner
- 使用:
// 创建Scanner的基本对象
Scanner scanner=new Scanner(System.in);
// 接收方式
nextInt();
scanner.next();
scan.nextLine();
nextFloat();
- next()和nextLine()的区别:
next():
1、一定要读取到有效字符后才可以结束输入。
2、对输入有效字符之前遇到的空白,next() 方法会自动将其去掉。
3、只有输入有效字符后才将其后面输入的空白作为分隔符或者结束符。
4、next() 不能得到带有空格的字符串。
nextLine():
1、以Enter为结束符,也就是说 nextLine()方法返回的是输入回车之前的所有字符。
2、可以获得空白。
break、return、continue
- break
break表示跳出当前层循环,某些时候需要在某种条件出现时强行终止循环,而不是等到循环条件为 false 时才退出循环。
break 用于完全结束一个循环,跳出循环体。不管是哪种循环,一旦在循环体中遇到 break,系统将完全结束该循环,开始执行循环之后的代码。
在java中,break 语句有 3 种作用,分别是:在 switch 语句中终止一个语句<序列、使用 break 语句直接强行退出循环和使用 break 语句实现 goto 的功能。
- return
return表示跳出当前方法的循环,return 语句用于终止函数的执行或退出类的方法,并把控制权返回该方法的调用者。
如果一个方法使用了 return 语句并且后面跟有该方法返回类型的值,那么调用此方法后,所得到的结果为该方法返回的值。
- continue
continue跳出本次循环,进入下一循环,continue 语句只能用在 while 、for 、 foreach 语句的循环体之中,在这之外的任何地方使用它都会引起语法错误。
Javadoc文档注释
@param,解释参数
@author,标记作者
@return,返回值描述
@throws,描述方法内部可能抛出的异常
@exception,用于描述方法签名的异常
数组
- 数组的初始化
方式一(静态初始化 有长度 有元素):int[] array = new int[]{10,20,30}; int[] array = {10,20,30,40,50};
方式二(有长度 没有元素(有默认值)):int[] array = new int[5];
- 数组的特点
- 数组本身是一个引用数据类型
- 数组是在堆内存中的一串连续的地址存在
- 数组在初始化时必须指定长度
- 堆内存的数组空间长度一旦确定 不能再次发生改变
- 栈内存的变量中存储的是数组的地址引用
- 数组内部存储的类型可以是基本的 也可以是引用
- 数组的遍历
// 普通for循环
for (int i = 0; i < ; i++) {}
// 增强for循环
for(k:v){}
- 基本数据类型、引用数据类型区别
- 所有的变量空间都存储在栈内存
- 变量空间可以存储基本数据类型 也可以存储引用数据类型
- 如果变量空间存储的是基本数据类型 存储的是值 一个变量的值改变 另一个不会跟着改变
- 如果变量空间存储的是引用数据类型 存储的是引用(地址) 一个变量地址对应的值改变 另一个跟着改变
- 数组中常见的运行时异常
- InputMisMatchException;输入类型不匹配
- ArrayIndexOutOfBoundsException 数组索引越界
- NegativeArraySizeException 数组长度不合法(长度出现负数)
- NullPointerException 空指针异常
4 面向对象编程
类的成员介绍
- 属性
属性 = 成员变量 = field = 域、字段
格式:权限修饰符 [特征修饰符] 属性类型 属性名字 [= 值];
public final int MAX = 110;
- 方法
方法 = 成员方法 = 函数 = method
格式:
权限修饰符 [特征修饰符] 返回值类型 方法名字 ([参数列表]) [抛出异常] [{方法体}]
public static void add(int a,int b){}
方法重载:
概念:一个类中的一组方法 相同的方法名字 不同的参数列表 这样的一组方法构成了方法重载
参数列表不同的体现:参数的个数 参数的类型 参数的顺序
作用:为了让使用者便于记忆与调用 只需要记录一个名字 执行不同的操作
- 构造器
格式:
权限修饰符 与类名相同的方法名 ([参数列表]) [抛出异常] {方法体}
// 假设当前为Person类
// 无参构造
public Person (){}
// 有参构造
public Person (String name){}
- 代码块
静态代码块
- 内部可以输出语句
- 语法:
static {}
- 随着类的加载而执行,而且只执行一次
- 作用:初始化类的信息
- 如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行
- 静态代码块的执行要优先于非静态代码块的执行
- 静态代码块内只能调用静态的属性、静态的方法,不能调用非静态的结构
非静态代码块
- 内部可以输出语句
- 语法:
{}
- 随着对象的创建而执行
- 每创建一个对象,就执行一次非静态代码块
- 作用:可以在创建对象时,对对象的属性等进行初始化
- 如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行
- 非静态代码块内可以调用静态的属性、静态的方法,或非静态的属性、非静态的方法
类的三大特征
- 封装性
why
程序设计追求“高内聚(类的内部数据操作细节自己完成,不允许外部干涉),低耦合(仅对外暴露少量的方法用于使用)”。
what
隐藏对象内部的复杂性,只对外公开简单的接口。
便于外界调用,从而提高系统的可扩展性、可维护性。通俗的说,把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。
利用修饰符实现对类的私化(private)
how
将类的属性xxx私化(private),同时,提供公共的(public)方法来获取(getXxx)和设置(setXxx)此属性的值
private double radius;
public void setRadius(double radius){
this.radius = radius;
}
public double getRadius(){
return radius;
}
java中的四种权限修饰符
限从小到大顺序为:private < 缺省 < protected < public
修饰符 | 类内部 | 同一个包 | 不同包的子类 | 同一个工程 |
---|---|---|---|---|
private | ✔ | |||
不填修饰符(却省) | ✔ | ✔ | ||
protected | ✔ | ✔ | ✔ | |
public | ✔ | ✔ | ✔ | ✔ |
注意:4种权限都可以用来修饰类的内部结构,属性、方法、构造器、内部类。修饰类的话,只能使用:缺省、public
- 继承性
why
- 减少了代码的冗余,提高了代码的复用性
- 便于功能的扩展
- 之后多态性的使用,提供了前提
what
- 一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有的属性和方法(非private)。
- 子类继承父类以后,还可以声明自己特有的属性或方法:实现功能的拓展。
- 一个类可以被多个子类继承,一个类只能有一个父类(单继承)
- 子类直接继承的父类,称为:直接父类。间接继承的父类称为:间接父类
- 子类继承父类以后,就获取了直接父类以及所间接父类中声明的属性和方法(非private)
how
**格式:**class A extends B{}
A:子类、派生类、subclass
B:父类、超类、基类、superclass
- 多态性
why
方便拓展子类
what
同种事物的多种形态,创建子类对象实现向上转型得到父类对象。
例如:猫可以是猫的类型。猫 m = new 猫(); 同时猫也是动物的一种,也可以把猫称为动物。动物 d = new 猫();
how
- 子类继承父类,并重写父类的某些方法;
- 创建子类对象,向上转型为父类对象;
- 使用转型得到的父类对象,调用被重写的方法;
向上转型:
将子类对象赋值给父类类型的变量,属于自动的。
格式: 父类类型 变量 = 子类对象;
向下转型:
将父类对象赋值给子类类型的变量.[强制的]。
格式: 子类 变量 = (子类类名)父类对象;
向下转型的条件:
必须确定子类和父类的继承关系
注意: 必须先向上转型[确定继承关系],然后才能向下转型
其他关键字
- this
this关键字指向的是当前对象的引用,用来区分成员变量和局部变量(重名问题)构造器中的有参构造
- super
java中规定,子类的构造方法必须调用super(),即父类的构造方法,而且必须放在构造方法的第一行。
当子类构造函数没有调用父类构造函数的时候是因为系统会自动添加调用。
- final
- 可以用来修饰:类、方法、变量
- 用来修饰一个类:此类不能被其他类所继承
- 用来修饰方法:表明此方法不可以被重写
- 用来修饰变量:此时的"变量"就称为是一个常量
- 用来修饰属性:全局常量
- abstract
- 可以用来修饰:类、方法
- abstract修饰的类,此类不能实例化,抽象类中一定有构造器,便于子类实例化时调用
- 开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作 —>抽象的使用前提:继承性
- abstract修饰的方法,没方法体
- 包含抽象方法的类,一定是一个抽象类。反之,抽象类中可以没有抽象方法的。
- 若子类重写了父类中的所的抽象方法后,此子类方可实例化
- 若子类没重写父类中的所的抽象方法,则此子类也是一个抽象类,需要使用abstract修饰
//父类
abstract class kabstract {
public abstract void say();
public abstract void run();
}
//普通子类
public class kextends extends kabstract {
//子类必须实现父类的抽象方法
@Override
public void say(){
System.out.println("I am saying");
}
@Override
public void run(){
System.out.println("I am running");
}
}
//抽象子类
abstract class kAbstractExtends extends kabstract{
//子类如果不实现父类的抽象方法,子类也必须为定义为abstract
}
- interface
- 接口中可以含有 变量和方法, 变量必须指定为:public static final变量,方法只能是public abstract方法。
- 接口中所有的方法不能有具体的实现,即:接口中的方法必须都是抽象方法
- 接口是一种极度抽象的类型,它比抽象类更加“抽象”,并且一般情况下不在接口中定义变量。
- 如果某个类要遵循特定的接口,就要用
implements
[实现,]关键字进行定义。(支持多实现)
public interface kinterface {
public static final int A = 99;
public abstract void say();
public abstract void run();
}
- package
Java的包名都有小写单词组成
个人
indi:多人完成,版权属于发起者
包名为indi.发起者名.项目名.模块名
indi.my.taobao.front
pers :独自完成,公开,版权主要属于个人。
包名为pers.个人名.项目名.模块名
pers.my.taobao.front
priv : 独自完成,非公开,版权属于个人。
包名为priv.个人名.项目名.模块名
priv.my.taobao.front
团队
team:团队项目指由团队发起,并由该团队开发的项目,版权属于该团队所有。
包名为team.团队名.项目名.模块名
team.win.taobao.front
公司
com:由公司发起,版权由项目发起的公司所有。
包名为com.公司名.项目名.模块名
com.github.taobao.dao
异常机制
- 异常体系结构

- 异常的处理
方式一:try-catch-finally
结构:
try{
//可能出现异常的代码
}catch(异常类型1 变量名1){
//处理异常的方式1
}catch(异常类型2 变量名2){
//处理异常的方式2
}catch(异常类型3 变量名3){
//处理异常的方式3
}
....
finally{
//一定会执行的代码
}
说明:
- 使用try-catch-finally处理编译时异常,使得程序在编译时就不再报错,但是运行时仍可能报错。
- 如果有多个catch下面的catch的关系得大于上级关系
方式二:throws
- "throws + 异常类型"写在方法的声明处。指明此方法执行时,可能会抛出的异常类型。
- throws的方式只是将异常抛给了方法的调用者。并没真正将异常处理掉。
- 异常的手动处理(throw)
在程序执行中,除了自动抛出异常对象的情况之外,我们还可以手动的throw一个异常类的对象。
throw 表示抛出一个异常类的对象,生成异常对象的过程。声明在方法体内。
throws 属于异常处理的一种方式,声明在方法的声明处。
throw new MyException("不能输入负数");
- 自定义异常
- 继承于现的异常结构:RuntimeException 、Exception
- 提供全局常量:serialVersionUID
- 提供重载的构造器
public class MyException extends Exception{
static final long serialVersionUID = -7034897193246939L;
public MyException(){
}
public MyException(String msg){
super(msg);
}
}
5 i/o详解
6 多线程
程序、进程、线程的理解?
- 程序是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码。
- 进程是程序的一次执行过程,或是正在运行的一个程序。
- 进程可进一步细化为线程,是一个程序内部的一条执行路径。(一个Java应用程序java.exe,其实至少三个线程:main()主线程,gc()垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。)
并行与并发?
- 并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
- 并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事
线程的实现方式
- extend Thread
- 创建一个继承于Thread类的子类
- 重写Thread类中的run():将此线程要执行的操作声明在run()
- 创建Thread的子类的对象
- 调用此对象的start(): 启动线程
public class Thread1 extends Thread {
@Override
public void run() {
// run方法线程体
for (int i = 0; i < 100; i++) {
System.out.println("i eating" + i);
}
}
public static void main(String[] args) {
// 创建线程对象
Thread1 thread1 = new Thread1();
// 调用start方法开启线程, 若调用run方法则只是简单执行,并没有开启多线程,执行完run后再执行main
thread1.start();
// 主线程
for (int i = 0; i < 100; i++) {
System.out.println("i studying" + i);
}
}
}
测试结果:
i studying9
i eating0
i eating1
i studying10
i eating2
......
- implement Runnable
- 创建一个实现Runnable接口的类
- 实现Runnable接口中的抽象方法:run():将创建的线程要执行的操作声明在此方法中
- 创建Runnable接口实现类的对象
- 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
- 调用Thread类中的start()
public class Thread2 implements Runnable {
@Override
public void run() {
// run方法线程体
for (int i = 0; i < 100; i++) {
System.out.println("i eating" + i);
}
}
public static void main(String[] args) {
// 创建线程对象
Thread2 thread2 = new Thread2();
// 创建Thread代理类对象
new Thread(thread2).start();
// 主线程
for (int i = 0; i < 100; i++) {
System.out.println("i studying" + i);
}
}
}
- 实现Callable接口
- 与使用Runnable相比, Callable功能更强大些
- 实现的call()方法相比run()方法,可以返回值
- 方法可以抛出异常
- 支持泛型的返回值
- 需要借助FutureTask类,比如获取返回结果
- Future接口可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
- FutureTask是Futrue接口的唯一的实现类
- FutureTask 同时实现了Runnable, Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
public class Thread3 {
public static void main(String[] args) {
// 创建未来任务类
FutureTask futureTask = new FutureTask<>(new Callable<Object>() {
@Override
public Object call() throws Exception {
int a = 100;
int b = 200;
Thread.sleep(3000);
return a + b;
}
});
// 创建线程对象
Thread thread = new Thread(futureTask);
thread.start();
// 获取线程的返回结果
try {
Object o = futureTask.get();
System.out.println(o);
} catch (Exception e) {
e.printStackTrace();
}
// main方法的线程要执行必须等待get()方法的执行结束
System.out.println("i am main()");
}
}
- 使用线程池实现
- 提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理
- demo
// 首先需要导包org.apache.commons.io
public class DownLoadP implements Runnable {
private String url;
private String name;
public DownLoadP(String url,String name){
this.url = url;
this.name = name;
}
@Override
public void run() {
WebDownLoader loader = new WebDownLoader();
loader.downLoadPicture(url,name);
System.out.println("下载了文件名为:"+name);
}
public static void main(String[] args) {
DownLoadP p1 = new DownLoadP("网图地址", "1.jpg");
DownLoadP p2 = new DownLoadP("网图地址", "2.jpg");
DownLoadP p3 = new DownLoadP("网图地址", "3.jpg");
new Thread(p1).start();
new Thread(p2).start();
new Thread(p3).start();
}
}
class WebDownLoader{
public void downLoadPicture(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downLoadPicture方法出现问题");
}
}
}
Lambda表达式
- 前提
- 接口是函数式接口(只有一个抽象方法的接口)
public interface Runnable {
public abstract void run();
}
public class TestLambda {
/*
总结:
1.lambda表达式只能有一行代码的情况下才能简化为一行,如果有多行,那么就用代码块包裹
2.前提接口是函数式接口
3.多个参数也可以去掉参数类型,要去掉就都去掉
4.多个参数必须加上括号
*/
public static void main(String[] args) {
ILove love = (int a,int b)->{
System.out.println("I Love u"+a);
};
love = (a,b)->{
System.out.println("I Love u"+a);
};
/*
ILove love = a->{
System.out.println("I Love u"+a);
};
ILove love = a->System.out.println("I Love u"+a);
*/
love.love(1);
}
}
interface ILove{
void love(int a,int b);
}
线程的方法
- 常用方法
方法 | 说明 |
---|---|
setPriority(int newPriority) | 更改线程的优先级 |
static void sleep(long millis) | 在指定的毫秒数内让当前正在执行县城休眠 |
void join() | 在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态。 |
static void yield() | 暂停当前正在执行的线程对象,并执行其他线程,释放当前cpu的执行权 |
void interrupt() | 中断线程,别用这个方式 |
boolean isAlive() | 测试线程是否处于活动状态 |
getName() | 获取当前线程的名字 |
setName() | 设置当前线程的名字 |
currentThread() | 静态方法,返回执行当前代码的线程 |
start() | 启动当前线程,调用当前线程的run() |
run() | 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中 |
- 停止线程
- 不推荐使用JDK提供的stop()、destroy()方法【已废弃】
- 推荐使用标志位终止,让线程自己停止下来
public class ThreadStop implements Runnable {
// 标志位
private boolean flag = true;
@Override
public void run() {
int i = 0;
while (flag) {
System.out.println("run...Thread" + i++);
}
}
// 终止方法
public void stop() {
this.flag = false;
}
public static void main(String[] args) {
ThreadStop threadStop = new ThreadStop();
new Thread(threadStop).start();
// 主线程
for (int i = 0; i < 200; i++) {
System.out.println("main ing " + i);
// 当主线程中的i=900时,run线程停止
if (i == 100) {
threadStop.stop();
System.out.println("stop ------");
}
}
}
}
- 线程休眠
- sleep(时间)指定当前线程的毫秒数
- sleep存在InterruptedExection的异常
- sleep时间达到后线程进入就绪状态
- sleep可以模拟网络延时,倒计时
- 每个对象都有一把锁,sleep不会释放锁
// 模拟网络延时:放大问题的发生性
public class ThreadSleep implements Runnable {
// 票数
private int ticketNums = 10;
@Override
public void run() {
while (true) {
if (ticketNums <= 0) {
break;
}
// 模拟网络延时
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "拿到了第" + (ticketNums--) + "张票");
}
}
public static void main(String[] args) {
ThreadSleep threadSleep = new ThreadSleep();
new Thread(threadSleep, "小明").start();
new Thread(threadSleep, "小王").start();
new Thread(threadSleep, "小李").start();
}
}
- 线程礼让
- 礼让线程,让当前正在执行的线程暂停,但不阻塞
- 将线程从运行状态转为就绪状态
- 让cpu重新调度,礼让不一定成功
public class ThreadYield {
public static void main(String[] args) {
MyYield myYield = new MyYield();
new Thread(myYield, "a").start();
new Thread(myYield, "b").start();
}
static class MyYield implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程开始执行");
Thread.yield();
System.out.println(Thread.currentThread().getName() + "线程开始执行");
}
}
}
- 线程插队
在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态。
public class ThreadJoin implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("i am vip" + i);
}
}
public static void main(String[] args) {
ThreadJoin threadJoin = new ThreadJoin();
Thread thread = new Thread(threadJoin);
thread.start();
// 主线程
for (int i = 0; i < 500; i++) {
if (i == 200) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("main " + i);
}
}
}
观测线程状态

线程共包括以下 5 种状态:
1. 新建状态(New): 线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。
2. 就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
3. 运行状态(Running): 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
4. 阻塞状态(Blocked): 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
- (01) 等待阻塞 – 通过调用线程的wait()方法,让线程等待某工作的完成。
- (02) 同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
- (03) 其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5. 死亡状态(Dead): 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
public static void main(String[] args) throws InterruptedException {
//Lamdba表达式
Thread thread = new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("///");
});
//getState()获得线程状态
Thread.State state = thread.getState();
System.out.println(state);//NEW
thread.start();
state = thread.getState();
System.out.println(state);//RUNNABLE
while (state != Thread.State.TERMINATED) {//直到线程为退出状态
Thread.sleep(100);
state = thread.getState();//更新线程状态
System.out.println(state);
}
}

线程优先级
- Java提供一个线程调度器来监控程序中启动后进入就绪状态的的所有线程,按照优先级决定应该调度哪个线程来执行。
- 线程的优先级用数字表示,范围从1~10
- thread.MIN_PRIORITY = 1 thread.NORM_PRIORITY = 5 thread.MAX_PRIORITY = 10
- 使用getPriority()和setPriority(int xxx)进行获取和改变优先级
public class ThreadPriority {
public static void main(String[] args) {
//主线程
System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());
Priority p = new Priority();
Thread t1 = new Thread(p);
Thread t2 = new Thread(p);
Thread t3 = new Thread(p);
Thread t4 = new Thread(p);
//先设置优先级再启动
t1.setPriority(3);
t1.start();
t2.setPriority(Thread.MAX_PRIORITY);//10
t2.start();
t3.start();//默认优先级为5
t4.setPriority(Thread.MIN_PRIORITY);//1
t4.start();
}
}
class Priority implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());
}
}
测试结果:
main-->5
Thread-1-->10
Thread-0-->3
Thread-2-->5
Thread-3-->1
注意:
优先级只是意味着获得调度的概率低,并不是优先级低的就不会被调用了,这都是看cpu的调度
守护线程
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不需要等待守护线程执行完毕
- 如,后台记录操作日志,监控内存,垃圾回收。
public class TestDaemon {
public static void main(String[] args) {
God god = new God();
You you = new You();
Thread god_th = new Thread(god);
//设置为守护线程
god_th.setDaemon(true);//默认为false-->默认为用户线程
god_th.start();
Thread you_th = new Thread(you);
you_th.start();
}
}
//上帝
class God implements Runnable{
@Override
public void run() {
while (true){
System.out.println("上帝保佑着你");
}
}
}
//你
class You implements Runnable{
@Override
public void run() {
for (int i = 0; i < 36500; i++) {
System.out.println("你每一天都在开心得活着");
}
System.out.println("====goodbye world!======");
}
}
线程同步机制
案例引入:
创建个窗口卖票,总票数为10张,使用实现Runnable接口的方式。
//买票不安全
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTick station = new BuyTick();
new Thread(station, "小明").start();
new Thread(station, "小红").start();
new Thread(station, "小李").start();
}
}
//买票
class BuyTick implements Runnable {
//票数
private int ticketNums = 10;
//线程停止标志
boolean flag = true;
@Override
public void run() {
while (flag) {
try {
buy();
//模拟延时,放大问题
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//买票
public void buy() {
if (ticketNums <= 0) {
flag = false;
return;
}
System.out.println(Thread.currentThread().getName() + " 买到了第 " + ticketNums-- + " 张票 ");
}
}
问题分析:
- 卖票过程中,出现了重票、错票 -->出现了线程的安全问题
- 当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。
- 如何解决:当一个线程在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,其他线程才可以开始操作ticket。
- 同步代码块
- 语法:synchronized(同步监视器){//需要被同步的代码}
- 操作共享数据的代码,即为需要被同步的代码。 -->不能包含代码多了,也不能包含代码少了。
- 共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
- 同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
- 在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
- 在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器。
// 使用synchronized代码块
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTick station = new BuyTick();
new Thread(station, "小明").start();
new Thread(station, "小红").start();
new Thread(station, "小李").start();
}
}
//买票
class BuyTick implements Runnable {
//票数
private int ticketNums = 10;
//线程停止标志
boolean flag = true;
@Override
public void run() {
while (flag) {
try {
synchronized (this) {
buy();
}
//模拟延时,放大问题
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//买票
public void buy() {
if (ticketNums <= 0) {
flag = false;
return;
}
System.out.println(Thread.currentThread().getName() + " 买到了第 " + ticketNums-- + " 张票 ");
}
}
- 同步方法
- 如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。
- 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
- 非静态的同步方法,同步监视器是:this
- 静态的同步方法,同步监视器是:当前类本身
// 使用synchronized同步方法
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTick station = new BuyTick();
new Thread(station, "小明").start();
new Thread(station, "小红").start();
new Thread(station, "小李").start();
}
}
//买票
class BuyTick implements Runnable {
//票数
private int ticketNums = 10;
//线程停止标志
boolean flag = true;
@Override
public void run() {
while (flag) {
try {
buy();
//模拟延时,放大问题
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//买票
public synchronized void buy() {
if (ticketNums <= 0) {
flag = false;
return;
}
System.out.println(Thread.currentThread().getName() + " 买到了第 " + ticketNums-- + " 张票 ");
}
}
- lock锁,JDK5.0新增
- synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器
- Lock需要手动的启动同步(lock(),同时结束同步也需要手动的实现(unlock())
- 同步的方式,解决了线程的安全问题,但是操作同步代码时,只能一个线程参与,其他线程等待,效率过低。
// 可重入锁
public class ReenLock {
public static void main(String[] args) {
TestClock clock = new TestClock();
new Thread(clock, "小明").start();
new Thread(clock, "小红").start();
new Thread(clock, "小李").start();
}
}
class TestClock implements Runnable {
int ticketNum = 10;
private final ReentrantLock reentrantLock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {//一般使用try语句块
//显式加锁
reentrantLock.lock();
if (ticketNum > 0) {
System.out.println(Thread.currentThread().getName() + " 拿到了 " + ticketNum--);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
} finally {
//显式释放锁
reentrantLock.unlock();
}
}
}
}
死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
出现死锁后,不会出现异常,不会出现提示,只是所的线程都处于阻塞状态,无法继续。
//死锁
public class DeadLock {
public static void main(String[] args) {
playWithToy xm = new playWithToy(0, " 小明 ");
playWithToy lw = new playWithToy(1, "老王 ");
xm.start();
lw.start();
}
}
//玩具车
class ToyCar {
}
//玩具枪
class ToyGun {
}
//玩玩具
class playWithToy extends Thread {
//使用static关键字是这个类只有唯一一个资源,即这有一个玩具车和一个玩具枪
static ToyCar car = new ToyCar();
static ToyGun gun = new ToyGun();
int choice;//选择
String name;//玩玩具的人
public playWithToy(int choice, String name) {
this.choice = choice;
this.name = name;
}
@Override
public void run() {
//玩玩具
try {
play();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//玩玩具的方法
public void play() throws InterruptedException {
if (choice == 0) {
//嵌套锁
synchronized (car) {
System.out.println(this.name + " 拿到了玩具车 ");
Thread.sleep(1000);
synchronized (gun) {
System.out.println(this.name + " 拿到了玩具枪 ");
}
}
} else {
synchronized (gun) {
System.out.println(this.name + " 拿到了玩具枪");
Thread.sleep(2000);
synchronized (car) {
System.out.println(this.name + " 拿到了玩具车 ");
}
}
}
}
}
形成死锁的四个条件:
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而阻塞时,对已持有的资源保持不放
- 不剥夺条件:进程已获得的资源,在未使用完之前,不能抢行剥夺
- 循环等待条件:若干进程之间形成一个头尾相接的循环等待资源关系
线程池
我们使用需要开启关闭的资源是非常消耗内存的,要是有一个容器能存放资源,我们用完就放到容器中,需要的时候再从容器中获取,这样就不会很耗费资源。
public class TestPool {
public static void main(String[] args) {
//newFixedThreadPool的参数是线程池中线程的数量
ExecutorService service = Executors.newFixedThreadPool(10);
service.execute(new Test());
service.execute(new Test());
service.execute(new Test());
service.execute(new Test());
service.shutdown();
}
}
class Test implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
7 java常用API
Random
所在位置:
java.util.Random
构造方法:
public Random() :创建一个新的随机数生成器。
成员方法:
public int nextInt(int n) :返回一个伪随机数,范围在 0 (包括)和指定值 n (不包括)之间的int 值。
代码测试:
public class RandomTest {
public static void main(String[] args) {
Random random = new Random();
int i = random.nextInt(10);
System.out.println(i);
}
}
ArrayList
所在位置:
java.util.ArrayList
构造方法:
ArrayList()
,构造一个默认容量为10的空列表。
常用方法:
public boolean add(E e) : 将指定的元素添加到此集合的尾部。
public E remove(int index) :移除此集合中指定位置上的元素。返回被删除的元素。
public E remove(Object ) :移除此集合中指定位置上的元素。返回被删除的元素。
public E get(int index) :返回此集合中指定位置上的元素。返回获取的元素。
public int size() :返回此集合中的元素数。遍历集合时,可以控制索引范围,防止越界。
代码测试:
import java.util.ArrayList;
import java.util.Random;
public class ArrayListTest {
public static void main(String[] args) {
// 产生10个随机数,存入集合
ArrayList<Object> list = new ArrayList<>();
Random random = new Random();
for (int i = 0; i < 10; i++) {
int value = random.nextInt(10) + 1;
// 添加到集合
list.add(value);
}
// 查看集合长度
System.out.println(list.size());
// 查看集合所有值
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i) + " ");
}
System.out.println();
// 取出索引为2的值
System.out.println("取出索引为2的值:" + list.get(2));
// 移除索引为7的值
System.out.println("移除索引为7的值:" + list.remove(7));
System.out.println(list.size());
}
}
public Random() :创建一个新的随机数生成器。
成员方法:
public int nextInt(int n) :返回一个伪随机数,范围在 0 (包括)和指定值 n (不包括)之间的int 值。
代码测试:
public class RandomTest {
public static void main(String[] args) {
Random random = new Random();
int i = random.nextInt(10);
System.out.println(i);
}
}
ArrayList
所在位置:
java.util.ArrayList
构造方法:
ArrayList()
,构造一个默认容量为10的空列表。
常用方法:
public boolean add(E e) : 将指定的元素添加到此集合的尾部。
public E remove(int index) :移除此集合中指定位置上的元素。返回被删除的元素。
public E remove(Object ) :移除此集合中指定位置上的元素。返回被删除的元素。
public E get(int index) :返回此集合中指定位置上的元素。返回获取的元素。
public int size() :返回此集合中的元素数。遍历集合时,可以控制索引范围,防止越界。
代码测试:
import java.util.ArrayList;
import java.util.Random;
public class ArrayListTest {
public static void main(String[] args) {
// 产生10个随机数,存入集合
ArrayList<Object> list = new ArrayList<>();
Random random = new Random();
for (int i = 0; i < 10; i++) {
int value = random.nextInt(10) + 1;
// 添加到集合
list.add(value);
}
// 查看集合长度
System.out.println(list.size());
// 查看集合所有值
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i) + " ");
}
System.out.println();
// 取出索引为2的值
System.out.println("取出索引为2的值:" + list.get(2));
// 移除索引为7的值
System.out.println("移除索引为7的值:" + list.remove(7));
System.out.println(list.size());
}
}