day15
第一章 异常
1.1 Java 异常处理
异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。
比如说,你的代码少了一个分号,那么运行出来结果是提示是错误 java.lang.Error;如果你用System.out.println(11/0),那么你是因为你用0做了除数,会抛出 java.lang.ArithmeticException 的异常。
异常发生的原因有很多,通常包含以下几大类:
- 用户输入了非法数据。
- 要打开的文件不存在。
- 网络通信时连接中断,或者JVM内存溢出。
这些异常有的是因为用户错误引起,有的是程序错误引起的,还有其它一些是因为物理错误引起的。-
要理解Java异常处理是如何工作的,你需要掌握以下三种类型的异常: - 检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。
- 运行时异常: 运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。
- 错误: 错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。
1.2 Exception 类的层次
所有的异常类是从 java.lang.Exception 类继承的子类。
Exception 类是 Throwable 类的子类。除了Exception类外,Throwable还有一个子类Error 。
Java 程序通常不捕获错误。错误一般发生在严重故障时,它们在Java程序处理的范畴之外。
Error 用来指示运行时环境发生的错误。
例如,JVM 内存溢出。一般地,程序不会从错误中恢复。
异常类有两个主要的子类:IOException 类和 RuntimeException 类。
1.3 Java 内置异常类
Java 语言定义了一些异常类在 java.lang 标准包中。
标准运行时异常类的子类是最常见的异常类。由于 java.lang 包是默认加载到所有的 Java 程序的,所以大部分从运行时异常类继承而来的异常都可以直接使用。
Java 根据各个类库也定义了一些其他的异常,下面的表中列出了 Java 的非检查性异常。
异常 | 描述 |
---|---|
ArithmeticException | 当出现异常的运算条件时,抛出此异常。例如,一个整数"除以零"时,抛出此类的一个实例。 |
ArrayIndexOutOfBoundsException | 用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引。 |
ArrayStoreException | 试图将错误类型的对象存储到一个对象数组时抛出的异常。 |
ClassCastException | 当试图将对象强制转换为不是实例的子类时,抛出该异常。 |
IllegalArgumentException | 抛出的异常表明向方法传递了一个不合法或不正确的参数。 |
IllegalMonitorStateException | 抛出的异常表明某一线程已经试图等待对象的监视器,或者试图通知其他正在等待对象的监视器而本身没有指定监视器的线程。 |
IllegalStateException | 在非法或不适当的时间调用方法时产生的信号。换句话说,即 Java 环境或 Java 应用程序没有处于请求操作所要求的适当状态下。 |
IllegalThreadStateException | 线程没有处于请求操作所要求的适当状态时抛出的异常。 |
IndexOutOfBoundsException | 指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。 |
NegativeArraySizeException | 如果应用程序试图创建大小为负的数组,则抛出该异常。 |
NullPointerException | 当应用程序试图在需要对象的地方使用 null 时,抛出该异常。 |
NumberFormatException | 当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。 |
SecurityException | 由安全管理器抛出的异常,指示存在安全侵犯。 |
StringIndexOutOfBoundsException | 此异常由 String 方法抛出,指示索引或者为负,或者超出字符串的大小。 |
UnsupportedOperationException | 当不支持请求的操作时,抛出该异常。 |
下面的表中列出了 Java 定义在 java.lang 包中的检查性异常类。
异常 | 描述 |
---|---|
ClassNotFoundException | 应用程序试图加载类时,找不到相应的类,抛出该异常。 |
CloneNotSupportedException | 当调用 Object 类中的 clone 方法克隆对象,但该对象的类无法实现 Cloneable 接口时,抛出该异常。 |
IllegalAccessException | 拒绝访问一个类的时候,抛出该异常。 |
InstantiationException | 当试图使用 Class 类中的 newInstance 方法创建一个类的实例,而指定的类对象因为是一个接口或是一个抽象类而无法实例化时,抛出该异常。 |
InterruptedException | 一个线程被另一个线程中断,抛出该异常。 |
NoSuchFieldException | 请求的变量不存在 |
NoSuchMethodException | 请求的方法不存在 |
第二章 异常处理
2.1 throw关键字
/*
throw关键字
作用:
可以使用throw关键字在指定的方法中抛出指定的异常使用
格式:
throw new XXXException(”异常产生的原因");
注意:
1.throw关键字必须写在方法的内部
2.throw关键字后边nev的对象必须是Exception或者Exception的子类对象
3.throw关键字抛出指定的异常对象,我们就必须处理这个异常对象
throw关键字后边创建的是RuntimeException或者是RuntimeException的子类对象,我们可以不处理,默认交给JWw处理(打印异常对象,中断程序)
throw关键字后边创建的是编译异常(写代码的时候报错),我们就必须处理这个异常,要么throws,要么try. ..catch
*/
public class Demo03Throw {
public static void main(String[] args) {
int[] arr = new int[3];
int e = getElement(arr, 3);
System.out.println(e);
}
/*
定义一个方法,获取数组指定索引处的元素
参数:
int[] arr
int index
以后(工作中)我们首先必须对方法传递过来的参数进行合法性校验
如果参数不合法,那么我们就必须使用批出异常的方式,告知方法的调用者,传递的参数有问题
*/
public static int getElement(int[] arr, int index){
/*
我们可以对传递过来的参数数组,进行合法性校验
如果数组arr的值是nulL
那么我们就抛出空指针异常,告知方法的调用者"传递的数组的值是nuLl"
*/
if (arr == null){
throw new NullPointerException("传递的数组的值是null");
}
/*
我们可以对传递过来的参数index进行合法性校验
如果index的范围不在数组的索引范围内
那么我们就抛出数组索引越界异常,告知方法的调用者“传递的索引超出了数组的使用范围”
*/
if(index < 0 || index > arr.length - 1){
throw new ArrayIndexOutOfBoundsException("传递的索引超出了数组的使用范围");
}
int ele = arr[index];
return ele;
}
}
Objects非空判断
还记得我们学习过一个类Objects吗,曾经提到过它由一些静态的实用方法组成,这些方法是null-save (空指针安全的)或null-tolerant(容忍空指针的),那么在它的源码中,对对象为null的值进行了抛出异常操作。
public static <T> T requireNonNull(T obj)
:查看指定引用对象不是null。
查看源码发现这里对为null的进行了抛出异常操作︰
public static <T> T requireNonNull(T obj) {
if (obj -= null)
throw new NullPointerException( ) ;
return obj;
}
import java.util.Objects;
/*
Objects类中的静态方法:
public static <T> T requireNonNull(T obj):查看指定引用对象不是null。
查看源码发现这里对为null的进行了抛出异常操作︰
public static <T> T requireNonNull(T obj) {
if (obj -= null)
throw new NullPointerException( ) ;
return obj;
}
*/
public class Demo04Objects {
public static void main(String[] args) {
method(null);
}
public static void method(Object obj){
//对传递过来的参数进行合法判断,判断是否为null
/*if(obj == null){
throw new NullPointerException("传递的对象值是null");
}*/
//Objects.requireNonNull(obj);
Objects.requireNonNull(obj, "空指针异常");
}
}
2.2 声明异常throws
声明异常︰将问题标识出来,报告给调用者。如果方法内通过throw抛出了编译时异常,而没有捕获处理(稍后讲解该方式),那么必须通过throws进行声明,让调用者去处理。
关键字throws运用于方法声明之上,用于表示当前方法不处理异常,而是提醒该方法的调用者来处理异常(抛出异常)
声明异常格式︰
修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2...{}
import java.io.FileNotFoundException;
import java.io.IOException;
/*
throws关键字:异常处理的第—种方式,交给别人处理
作用:
当方法内部抛出异常对象的时候,那么我们就必须处理这个异常对象
可以使用throws关键字处理异常对象,会把异常对象声明抛出给方法的调用者处理(自己不处理,给别人处理),最终交给JW处理-->中断处理
使用格式:在方法声明时使用
修饰符 返回值类型 方法名(参数列表) throws AAAException,BBBException...{
throw new AAAException("产生原因");
throw new 8BBException( "产生原因");
}
注意:
1.throws关键字必须写在方法声明处
2.throws关键字后边声明的异常必须是Exception或者是Exception的子类
3.方法内部如果抛出了多个异常对象,那么throws后边必须也声明多个异常
如果抛出的多个异常对象有子父类关系,那么直接声明父类异常即可
4.调用了一个声明抛出异常的方法,我们就必须的处理声明的异常
要么继续使用throws声明抛出,交给方法的调用者处理,最终交给JVM
要么try...catch自己处理异常
*/
public class Demo05Throws {
/*
FileNotFoundException extends IOException
如果抛出的多个异常有子父类关系,那么直接声明父类异常即可
*/
public static void main(String[] args) throws FileNotFoundException, IOException {
readFile("c:\\a.xt");
}
/*
定义一个方法,对传递的文件路径进行合法性判断
如果路径不是"c:\\a.txt",那么我们就抛出文件找不到异常对象,告知方法的调用者
注意:
FileNotFoundException是编译异常,抛出了编译异常,就必须处理这个异常
可以使用throws继续声明抛出FileNotFoundException这个异常对象,让方法的调用者处理
*/
public static void readFile(String fileName) throws FileNotFoundException, IOException {
if (!fileName.equals("c:\\a.txt")){
throw new FileNotFoundException("传递的文件路径不是c:\\a.txt");
}
/*
如果传递的路径,不是.txt结尾
那么我们就抛出Io异常对象,告知方法的调用者,文件的后缀名不对
*/
if (!fileName.endsWith(".txt")){
throw new IOException("文件的后缀名不对");
}
System.out.println("路径没有问题,读取文件");
}
}
2.3 捕获异常try……catch
如果异常出现的话,会立刻终止程序,所以我们得处理异常:
- 该方法不处理,而是声明抛出,由该方法的调用者来处理(throws)。
- 在方法中使用try-catch的语句块来处理异常。
try-catch的方式就是捕获异常。
- 捕获异常∶Java中对异常有针对性的语句进行捕获,可以对出现的异常进行指定方式的处理。捕获异常语法如下:
try{
编写可能会出现异常的代码
}catch(异常类型 e){
处理异常的代码
//记录日志/打印异常信息/继续抛出异常
}
Throwable类中定义了3个异常处理的方法
String getMessage()
返回此 throwable的简短描述。
String toString()
返回此throwable 的详细消息字符串。
void printStackTrace()
JVM打印异常对象,默认此方法,打印的异常信息是最全面的
import java.io.FileNotFoundException;
import java.io.IOException;
/*
try……catch:异常处理的第二种方式,自己处理异常
格式;
try{
可能产生异常的代码
}catch(定义一个异常的变量,用来接收try中抛出的异常对象){
异常的处理逻辑,异常异常对象之后,怎么处理异常对象
一般在工作中,会把异常的信息记录到一个日志中
}
...
catch (异常类名 变量名){
}
注意:
1.try中可能会抛出多个异常对象,那么就可以使用多个catch来处理这些异常对象
2.如果try中产生了异常,那么就会执行catch中的异常处理逻辑,执行完毕catch中的处理逻辑,继续执行try ...catch之后的代码
如果try中没有产生异常,那么就不会执行catch中异常的处理逻辑,执行完try中的代码,继!续执行try. ..catch之后的代码
*/
public class Demo01TryCatch {
public static void main(String[] args) {
try{
//可能产生异常的代码
readFile("d:\\a.tx");
}catch (IOException e){//try中抛出什么异常对象,catch就定义什么异常变量,用来接收这个异常对象
//异常的处理逻辑,异常对象之后,怎么处理异常对象
//System.out.println("catch - 传递的文件后缀不是.txt");
/*
Throwable类中定义了3个异常处理的方法
String getMessage() 返回此 throwable的简短描述。
String toString() 返回此throwable 的详细消息字符串。
void printStackTrace() JVM打印异常对象,默认此方法,打印的异常信息是最全面的
*/
System.out.println(e.getMessage());//文件的后缀名不对
System.out.println(e.toString());//重写Object类的toString,java.io.IOException: 文件的后缀名不对
System.out.println(e);//java.io.IOException: 文件的后缀名不对
/*
java.io.IOException: 文件的后缀名不对
at demo02.Exception.Demo01TryCatch.readFile(Demo01TryCatch.java:61)
at demo02.Exception.Demo01TryCatch.main(Demo01TryCatch.java:29)
*/
e.printStackTrace();
}
System.out.println("后续代码");
}
/*
如果传递的路径,不是.txt结尾
那么我们就抛出Io异常对象,告知方法的调用者,文件的后缀名不对
*/
public static void readFile(String fileName) throws FileNotFoundException, IOException {
/*if (!fileName.equals("c:\\a.txt")){
throw new FileNotFoundException("传递的文件路径不是c:\\a.txt");
}*/
if (!fileName.endsWith(".txt")){
throw new IOException("文件的后缀名不对");
}
System.out.println("路径没有问题,读取文件");
}
}
2.4 多重捕获块
一个 try 代码块后面跟随多个 catch 代码块的情况就叫多重捕获。
多重捕获块的语法如下所示:
try{
// 程序代码
}catch(异常类型1 异常的变量名1){
// 程序代码
}catch(异常类型2 异常的变量名2){
// 程序代码
}catch(异常类型3 异常的变量名3){
// 程序代码
}
上面的代码段包含了 3 个 catch块。
可以在 try 语句后面添加任意数量的 catch 块。
如果保护代码中发生异常,异常被抛给第一个 catch 块。
如果抛出异常的数据类型与 ExceptionType1 匹配,它在这里就会被捕获。
如果不匹配,它会被传递给第二个 catch 块。
如此,直到异常被捕获或者通过所有的 catch 块。
实例
该实例展示了怎么使用多重 try/catch。
try {
file = new FileInputStream(fileName);
x = (byte) file.read();
} catch(FileNotFoundException f) { // Not valid!
f.printStackTrace();
return -1;
} catch(IOException i) {
i.printStackTrace();
return -1;
}
注意:
catch里边定义的异常变量,如果有子父类关系,那么子类的异常变量必须写在上边,否则就会报错
2.5 finally关键字
finally 关键字用来创建在 try 代码块后面执行的代码块。
无论是否发生异常,finally 代码块中的代码总会被执行。
在 finally 代码块中,可以运行清理类型等收尾善后性质的语句。
import java.io.FileNotFoundException;
import java.io.IOException;
/*
finaLLy代码块格式:
try{
可能产生异常的代码
}catch(定义一个异常的变量,用来接收try中抛出的异常对象){
异常的处理逻辑,异常异常对象之后,怎么处理异常对象
一般在工作中,会把异常的信息记录到一个日志中
}
catch(异常类名 变量名){
}finally{
无论是否出现异常都会执行
}
注意:
1.finalLy不能单独使用,必须和try一起使用
2.finally一般用于资源释放(资源回收),无论程序是否出现异常,最后都要资源释放(IO)
*/
public class Demo02TryCatchFinally {
public static void main(String[] args){
try {
//可能会产生异常的代码
readFile("c:\\a.xt");
} catch (IOException e) {
//异常的处理逻辑
e.printStackTrace();
}finally {
//无论是否出现异常,都会执行
System.out.println("资源释放");
}
}
public static void readFile(String fileName) throws FileNotFoundException, IOException {
if (!fileName.equals("c:\\a.txt")){
throw new FileNotFoundException("传递的文件路径不是c:\\a.txt");
}
/*
如果传递的路径,不是.txt结尾
那么我们就抛出Io异常对象,告知方法的调用者,文件的后缀名不对
*/
if (!fileName.endsWith(".txt")){
throw new IOException("文件的后缀名不对");
}
System.out.println("路径没有问题,读取文件");
}
}
注意下面事项:
- catch 不能独立于 try 存在。
- 在 try/catch 后面添加 finally 块并非强制性要求的。
- try 代码后不能既没 catch 块也没 finally 块。
- try, catch, finally 块之间不能添加任何代码。
- 如果finally有return语句,永远返回finally中的结果,避免该情况
/*
如果finally有return语句,永远返回finally中的结果,避免该情况
*/
public class Demo02Exception {
public static void main(String[] args) {
System.out.println(getA());//100
}
public static int getA(){
int a = 10;
try{
return a;
}catch (Exception e){
System.out.println(e);
}finally {
a = 100;
return a;
}
}
}
/*
子父类的异常:
-如果父类抛出了多个异常,子类重写父类方法时,抛出和父类相同的异常或者是父类异常的子类或者不抛出异常。
-父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常。此时子类产生该异常,只能捕获处理,不能声明抛出
注意:
父类异常时什么样,子类异常就什么样
*/
public class Fu {
public void show01() throws NullPointerException, ClassCastException{}
public void show02() throws IndexOutOfBoundsException{}
public void show03() throws IndexOutOfBoundsException{}
public void show04() {}
}
class Zi extends Fu{
//子类重写父类方法时,抛出和父类相同的异常
public void show01() throws NullPointerException, ClassCastException{}
//子类重写父类方法时,抛出父类异常的子类
public void show02() throws ArrayIndexOutOfBoundsException{}
//子类重写父类方法时,不抛出异常
public void show03() {}
/*
父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常
此时子类产生该异常,只能捕获处理,不能声明抛出
*/
public void show04() {
try{
throw new Exception ("编译器异常");
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.6 自定义异常
在 Java 中你可以自定义异常。编写自己的异常类时需要记住下面的几点。
- 所有异常都必须是 Throwable 的子类。
- 如果希望写一个检查性异常类,则需要继承 Exception 类。
- 如果你想写一个运行时异常类,那么需要继承 RuntimeException 类。
/*
自定义异常类:
java提供的异常类,不够我们使用,需要自己定义一些异常类
格式:
public class XXXException extends Exception / RuntimeException{
添加一个空参数的构造方法
添加一个带异常信息的构造方法
}
注意:
1.自定义异常类一般都是以Exception结尾,说明该类是一个异常类
2.自定义异常类,必须的继承Exception或者RuntimeException
继承Exception:那么自定义的异常类就是一个编译期异常,如果方法内部抛出了编译期异常,就必须处理这个异常,要么throws,要么try...catch
继承RuntimeException:那么自定义的异常类就是一个运行期异常,无需处理,交给虚拟机处理(中断处理)
*/
public class RegisterException extends Exception{
//添加一个空参数的构造方法
public RegisterException() {
}
//添加一个带异常信息的构造方法
//查看源码发现,所有的异常类都会有一个带异常信息的构造方法,方法内部会调用父类带异常信息的构造方法,让父类来处理这个异常信息
public RegisterException(String message) {
super(message);
}
}
2.7 自定义异常类练习
import java.util.Scanner;
/*
要求:我们模拟注册操作,如果用户名已存在,则抛出异常并提示:亲,该用户名已经被注册。
分析:
1.使用数组保存已经注册过的用户名(数据库)
2.使用Scanner获取用户输入的注册的用户名(前端,页面)
3.定义一个方法,对用户输入的中注册的用户名进行判断
遍历存储已经注册过用户名的数组,获取每一个用户名
使用获取到的用户名和用户输入的用户名比较
true:
用户名已经存在,抛出RegisterException异常,告知用户"亲,该用户名已经被注册"";
false:
继续遍历比较
如果循环结束了,还没有找到重复的用户名,提示用户“恭喜您,注册成功中;
*/
public class Demo01RegisterException {
//1.使用数组保存已经注册过的用户名(数据库)
static String[] userNames = {"张三","李四","王五"};
//2.使用Scanner获取用户输入的注册的用户名(前端,页面)
public static void main(String[] args) /*throws RegisterException*/ {
Scanner sc = new Scanner(System.in);
System.out.println("请输入您要注册的用户名:");
String username = sc.next();
checkUserName(username);
}
//3.定义一个方法,对用户输入的中注册的用户名进行判断
public static void checkUserName(String username) /*throws RegisterException*/ {
//遍历存储已经注册过用户名的数组,获取每一个用户名
for (String name : userNames){
if (name.equals(username)){
//true:用户名已经存在,抛出RegisterException异常,告知用户"亲,该用户名已经被注册"";
try {
throw new RegisterException("亲,该用户已经被注册");
} catch (RegisterException e) {
e.printStackTrace();
return;
}
}
}
//如果循环结束了,还没有找到重复的用户名,提示用户“恭喜您,注册成功中;
System.out.println("恭喜你注册成功");
}
}
2.8 通用异常
在Java中定义了两种类型的异常和错误。
- JVM(
- Java虚拟机) 异常:由 JVM 抛出的异常或错误。例如:NullPointerException 类,ArrayIndexOutOfBoundsException 类,ClassCastException 类。
- 程序级异常:由程序或者API程序抛出的异常。例如 IllegalArgumentException 类,IllegalStateException 类。
第三章 多线程
3.1 并发和并行
**并发:**指两个或多个事件在同一个时问段内发生。
**并行:**指两个或多个事件在同一时刻发生(同时发生).
3.2 线程和进程
**进程︰**是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位﹔系统运行一个程序即是一个进程从创建、运行到消亡的过程。
**线程︰**线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程
线程调度:
- 分时调度工
所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间。 - 抢占式调度
优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
3.3 Java 多线程编程
Java 给多线程编程提供了内置的支持。 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销。
这里定义和线程相关的另一个术语 - 进程:一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。
多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。
3.4 创建线程类
主线程: 执行主(main)方法的线程
单线程程序: java程序中只有一个线程,执行从main方法开始,从上到下依次执行,JVM执行main方法, main方法会进入到栈内存,JVM会找操作系统开辟一条main方法通向cpu的执行路径,cpu就可以通过这个路径来执行main方法,而这个路径有一个名字,叫main (主)线程。
创建一个线程
Java 提供了三种创建线程的方法:
- 通过继承 Thread 类本身;
- 通过实现 Runnable 接口;
- 通过 Callable 和 Future 创建线程。
方法一:通过继承 Thread 类本身
Java使用java.lang.Thread
类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。Java中通过继承Thread类来创建并启动多线程的步骤如下:
- 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。
- 创建Thread子类的实例,即创建了线程对象
- 调用线程对象的start()方法来启动该线程
/*
创建多线程程序的第一种方式:创建Thread类的子类
java.lang.Thread类:是描述线程的类,我们想要实现多线程程序,就必须继承Thread类
实现步骤:
1.创建一个Thread类的子类
2.在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么?)
3.创建Thread类的子类对象
4.调用Thread类中的方法start方法,开启新的线程,执行run方法
void start()使该线程开始执行;Java虚拟机调用该线程的run方法。
结果是两个线程并发地运行;当前线程( main线程)和另一个线程(创建的新线程,执行其run 方法)
多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。
java程序属于抢占式调度,那个线程的优先级高,那个线程优先执行;同一个优先级,随机选择一个执行
*/
//1.创建一个Thread类的子类
public class MyThread extends Thread {
//2.在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么?)
@Override
public void run() {
for (int i = 0; i < 20; i++){
System.out.println("run" + i);
}
}
}
public class Demo01Thread {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
for (int i = 0; i < 20; i++) {
System.out.println("main" + i);
}
}
}
多线程原理_随机打印
多线程原理_多线程内存图解
在我们完成操作过程中用到了java.lang.Thread类,API中该类中定义了有关线程的一些方法,具体如下∶
构造方法:
public Thread()
:分配一个新的线程对象。public Thread(String name)
:分配一个指定名字的新的线程对象。public Thread(Runnable target)
:分配一个带有指定目标新的线程对象。public Thread(Runnable target,String name)
:分配一个带有指定目标新的线程对象并指定名字。
常用方法:public string getName()
:获取当前线程名称。public void start()
:导致此线程开始执行; Java虚拟机调用此线程的run方法。public void run()
:此线程要执行的任务在此处定义代码。public static void sleep(long millis)
:使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。public static Thread currentThread()
:返回对当前正在执行的线程对象的引用。
/*
获取线程的名称:
1.使用Thread类中的方法getName()
String getName() 返回该线程的名称。
2.可以先获取到当前正在执行的线程,使用线程中的方法getName()获取线程的名称
static Thread currentThread()返回对当前正在执行的线程对象的引用。
*/
public class MyThread extends Thread{
@Override
public void run() {
// String name = getName();
// System.out.println(name);
Thread t = currentThre();
System.out.println(t);
String name = t.getName();
System.out.println(name);
}
}
/*
线程的名称:
主线程: main
新线程: Thread-0 , Thread-1, Thread-2……
*/
public class Demo01GetThreadName {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
//获取主线程的main
System.out.println(Thread.currentThread().getName());
}
}
/*
设置线程的名称:(了解)
1.使用Thread类中的方法setName(名字)
void setName (String name)改变线程名称,使之与参数name相同。
⒉.创建一个带参数的构造方法,参数传递线程的名称;调用父类的带参构造方法,把线程名称传递给父类,让父类(Thread)给子线程起一个名字
Thread (String name)分配新的 Thread 对象。
*/
public class MyThread extends Thread{
public MyThread(){}
public MyThread(String name){
super(name);
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
public class Demo01SetThreadName {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.setName("小强");
mt.start();
//开启多线程
new MyThread("旺财").start();
}
}
/*
public static void sleep(Long millis):使当前正在执行的线程以指定的毫秒数暂停〈暂时停止执行)。
毫秒数结束之后,线程继续执行
*/
public class Demoi01Sleep {
public static void main(String[] args) {
for (int i = 0; i < 60; i++) {
System.out.println(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
方法二:通过实现 Runnable 接口
/*
创建多线程程序的第二种方式:实现Runnable接口
java.Lang. Runnable
Runnable接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为run 的无参数方法。
java.Lang. Thread类的构造方法
Thread(Runnable target)分配新的 Thread 对象。
Thread ( Runnable target,string name)分配新的 Thread 对象。
实现步骤:
1.创建一个Runnable接口的实现类
2.在实现类中重写Runnable接口的run方法,设置线程任务
3.创建一个Runnable接口的实现类对象
4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
5.调用Thread类中的start方法,开启新的线程执行run方法
实现Runnable接口创建多线程程序的好处:
1.避免了单继承的局限性
一个类只能继承一个类(一个人只能有一个亲爹),类继承了Thread类就不能继承其他的类
实现了Runnable接口,还可以继承其他的类,实现其他的接口
2.增强了程序的扩展性,降低了程序的耦合性(解耦)
实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)
实现类中,重写了run方法:用来设置线程任务
创建Thread类对象,调用start方法:用来开启新线程
*/
//1.创建一个Runnable接口的实现类
public class RunnableImpl implements Runnable {
//2.在实现类中重写Runnable接口的run方法,设置线程任务
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}
public class Demo01Runnable {
public static void main(String[] args) {
//3.创建一个Runnable接口的实现类对象
RunnableImpl run = new RunnableImpl();
//4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
Thread t = new Thread(run);
//5.调用Thread类中的start方法,开启新的线程执行run方法
t.start();
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}
方法三:通过 Callable 和 Future 创建线程
- 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。
- 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
- 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
- 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。
public class CallableThreadTest implements Callable<Integer> {
public static void main(String[] args)
{
CallableThreadTest ctt = new CallableThreadTest();
FutureTask<Integer> ft = new FutureTask<>(ctt);
for(int i = 0;i < 100;i++)
{
System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);
if(i==20)
{
new Thread(ft,"有返回值的线程").start();
}
}
try
{
System.out.println("子线程的返回值:"+ft.get());
} catch (InterruptedException e)
{
e.printStackTrace();
} catch (ExecutionException e)
{
e.printStackTrace();
}
}
@Override
public Integer call() throws Exception
{
int i = 0;
for(;i<100;i++)
{
System.out.println(Thread.currentThread().getName()+" "+i);
}
return i;
}
}
创建线程的三种方式的对比
- 采用实现 Runnable、Callable 接口的方式创建多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
- 使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。