有两种方法可以获得自身的Class对象引用(对每一个被装载的类型(类或接口),虚拟机都会为它创建一个java.lang.Class的实例):
1) Class c = Class.forName(“com.briup.ch06.Student”); //虚拟机中没有该类的Class的实例对象
2) Class c = stu.getClass(); //虚拟机已经存在Class的实例对象
注意:类和它所创建的所有对象通过反射获得的Class对象都是同一个.
反射可以让我们利用这个Class对象来获取和修改私有的变量和方法,不通过共有的方法去获得(原来我们例子都是通过一个public的方法(get)来设置和获取私有的变量),可以破坏数据的封装性。
常用到反射方式:
1) 可以创建对象
2) 可以访问对象中的属性
3) 可以访问对象中的方法。
4) 可以访问对象中的构造器。
反射机制通过在运行时探查字段和方法,从而可以帮助写出通用性很好的程序,这项能力对系统编程来说特别有用,但它并不适合于应用编程。
Book book = (Book)forName.newInstance();//调用无参构造器
Field field2 =forName.getDeclaredField("id");
field2.setAccessible(true);
field2.set(book, 20);//用反射设置私有属性
System.out.println(book.getId());
//Class.getDeclaredMethod(String name,Class<?>... parameterTypes)
Method declaredMethod =forName.getDeclaredMethod("getName",String.class,int.class);
declaredMethod.setAccessible(true);
declaredMethod.invoke(book,"hello",10);//用反射调方法
JDK5.0之后出现的新的特性:
三、加强的for循环
JDK1.5之后提供了一种更简洁的遍历方法语法:
for(元素类型 变量名:数组/集合变量名) {
//.....
}
如果要遍历的对象是集合,那么
要求集合要求实现了Iterable接口,
其实Collection接口已经继承了Iterable,
所以所有Collection接口的实现都可以用在增强的for循环中
还可以用增强for循环遍历数组
可变参数
JDK1.5引入了可变参数,一个方法可以接受任意个数的参数(但是必须是同一类型的),在接收时候使用数组来接收
语法: test(int... args) {}其实args表示的就是一个数组
不传递值时,接收的可变参数数组并不是null,而是一个长度为0的数组,可变参数可传多个参数
注意:如果方法存在多个参数,可变参数放在最后一个
泛型Generic
如果能够在构建类时候传入一个参数,类中某些变量的类型根据这个参数确定就能解决这些问题,泛型就是来解决这个问题的:
泛型可以使类中的属性的类型可以由外部决定
不需要强制类型转换
泛型可以在*编译期间*进行类型检查
提供了类型安全的操作
编译器的泛型兼容性检查:
I.泛型类型和原始类型的兼容性,原始类型可以引用一个泛型类型
eg:List list = new ArrayList<String>();
泛型类型可以引用一个原始类型的对象
eg:List<String> list = new ArrayList();
II.类型参数不同的泛型类型不能互相引用,不考虑类型参数间的继承性(和继承无关)
List<Object> l = newArrayList<String>(); //错误
枚举类型
JDK1.5增加了枚举类型,可以使用enum来定义
eg:
public enum Gender{
MALE,FEMALE;
}其中每一个枚举元素都是该枚举类型的一个实例
使用
Gender gender = Gender.MALE;//直接用类名遍历枚举类型中的对象
for(Gender s : Gender.values()) {
}
Enum类和enum关键字的区别
1.使用enum关键字定义的枚举类型,实际上就相当于定义了一个类,此类继承了java.lang.Enum类
2.每一个枚举值都是一个枚举类型的实例,它们被预设为publicstatic final
Enum类的只有一个受保护的构造方法protectdEnum(String name,int ordinal)
ordinal: 枚举元素的编号
实际上对于每一个枚举元素一旦声明之后,就表示自动调用此构造方法,所有的编号采用自动编号的方式进行(编号从0开始);
获得枚举类型对象的俩种方式:
第一种方式:类名.ValueOf(“对象名”),比较灵活
第二种方式:类名.对象(直接用类名调用),只能调用指定的对象
枚举类型可以有属性和方法
属性和方法必须定义在元素列表声明之后
枚举类型中定义构造方法
1.枚举的构造方法只能定义在元素列表之后
2.枚举类型的构造方法只能是private的,不写默认也是private
3.元素如果需要调用有参构造方法,则在元素后面加上"(参数)"
枚举隐式继承于Enum因此,无法再继承其他类
枚举实现接口
枚举可以实现接口,有以下两种方式
1.与普通class类一样,在枚举类中实现接口一样
2.枚举中单独实现接口中的抽象方法或者各个枚举元素对象中分别实现这个接口中的抽象方法
枚举中定义抽象方法
在一个枚举中可以定义多个抽象方法,但枚举中的每个元素必须分别实现这些抽象方法
所谓程序的健壮性,指程序在多数情况下能够正常运行,返回预期的正确结果;
Java异常处理机制具有以下优点:
. 把各种不同类型的异常情况进行分类,用Java类来表示异常情况,这种类被称为异常类。把异常情况表示成异常类,可以充分发挥类的可扩展和可重用的优势。
. 异常流程的代码和正常流程的代码分离,提高了程序的可读性,简化了程序的结构。
. 可以灵活地处理异常,如果当前方法有能力处理异常,就捕获并处理它,否则只需要抛出异常,由方法调用者来处理它。
1. 异常产生的条件或者称为异常情况。
a. 整数相除运算中,分母为0;
b. 通过一个没有指向任何具体对象的引用去访问对象的方法;
c. 使用数组长度作为下标访问数组元素;
d. 将一个引用强制转化成不相干的对象;
2. 异常会改变正常程序流程;
异常产生后,正常的程序流程被打破了,要么程序中止,要么程序被转向异常处理的语句;
3. 当一个异常的事件发生后,该异常被虚拟机封装形成异常对象抛出。
4. 用来负责处理异常的代码被称为异常处理器
5. 通过异常处理器来捕获异常
在Java语言中,用try...catch语句来捕获处理异常。格式如下:
try {
可能会出现异常情况的代码;
}catch(异常类型异常参数) {
异常处理代码或者自定义异常
}finally{
一定要执行的代码
}
1. 如果try代码块中没有抛出异常,try代码块中语句会顺序执行完,catch代码块内容不会被执行;
2. 如果try代码块中抛出catch代码块所声明的异常类型对象,程序跳过try代码块中接下来代码,直接执行catch代码块中对应内容;
a. 可以存在多个catch代码块,究竟执行哪个,看抛出的异常对象是否是catch代码块中异常类型;
b. 异常只能被一个异常处理器所处理, 不能声明两个异常处理器处理相同类型的异常;
c. 多个catch语句块所声明的异常类型不能越来越小;
d. 不能捕获一个在try语句块中没有抛出的异常;
3. 如果try代码块中抛出catch代码块未声明的异常类型对象,异常被抛给调用者;哪个调用了这段语句块哪个负责处理这个异常;
finally语句: 任何情况下都必须执行的代码由于异常会强制中断正常流程,这会使得某些不管在任何情况下都必须执行的步骤被忽略,从而影响程序的健壮性。
异常调用栈
异常处理时所经过的一系列方法调用过程被称为异常调用栈。
1. 异常的传播
a. 异常情况发生后,发生异常所在的方法可以处理;
b. 异常所在的方法内部没有处理,该异常将被抛给该方法调用者,调用者可以处理;
c. 如调用者没有处理,异常将被继续抛出;如一直没有对异常处理,异常将被抛至虚拟机;
2. 如果异常没有被捕获,那么异常将使你的程序将被停止。
异常产生后,如果一直没有进行捕获处理,该异常被抛给虚拟机。程序将被终止。
3. 经常会使用的异常API
getMessage:获得具体的异常出错信息,可能为null。
printStatckTrace():打印异常在传播过程中所经过的一系列方法的信息,简称异常处理方法调用栈信息;在程序调试阶段,此方法可用于跟踪错误。
异常层级关系
所有异常类的祖先类为java.lang.Throwable类。它有两个直接的子类:
1. Error类:表示仅靠程序本身无法恢复的严重错误,比如内存空间不足,或者Java虚拟机的方法调用栈溢出。
2. Exception类:表示程序本身可以处理的异常。Exception还可以分为两种:运行时异常和编译时异常。
a. 运行时异常
RuntimeException类及其子类都被称为运行时异常,这种异常的特点是Java编译器不会检查它,如divide()方法的参数b为0, 执行a/b操作时会出现ArithmeticException异常,数组下标越界,空指针,它属于运行时异常,Java编译器不会检查它。
2) 深入解析
运行时异常表示无法让程序恢复运行的异常,导致这种异常的原因通常是由于执行了错误操作。一旦出现了错误操作,建议终止程序,因此Java编译器不检查这种异常。
运行时异常应该尽量避免。在程序调试阶段,遇到这种异常时,正确的做法是改进程序的设计和实现方式,修改程序中的错误,从而避免这种异常。捕获它并且使程序恢复运行并不是明智的办法。
3) 对比
与Error类相比:
相同点:i. Java编译器都不会检查它们;
ii.当程序运行时出现它们, 都会终止程序;
不同点:
i. Error类及其子类表示的错误通常是由Java虚拟机抛出的,在JDK中预定义了一些错误类,比如OutOfMemoryError和StackOutofMemoryError。RuntimeException表示程序代码中的错误;
ii.Error类一般不会扩展来创建用户自定义的错误类;RuntimeException是可以扩展的,用户可以根据特定的问题领域来创建相关的运行时异常类;
b. 受检查异常。
Exception类及其子类都属于受检查异常(CheckedException)。这种异常的特点是Java编译器会检查它,当程序中可能出现这类异常时,要么用try...catch语句捕获它,要么用throws子句声明抛出它,否则编译不会通过。
一些未检查的异常RuntimeException
1.java.lang.ArithmeticException算术异常 如:除0;
2.java.lang.NullPointerException 空指针引用如:没初始化一个References便使用;
3.java.lang.ArrayIndexoutofBoundsException数组越界 如:调用一个有十个元素的Array的第十一个元素的内容;
4.java.lang.ClassCastException强制类型转换异常
5.java.lang.NumberFormatException数据格式异常 如:Integer.parseInt("a");
6.java.lang.NegativeArraySizeException 数组长度为负数异常
异常声明和处理
1. 自己主动使用throw语句的时候代码会抛出异常;
2. 使用try-catch-finally语句结构处理或在方法声明上声明throws继续抛出;
异常处理语句的语法规则:
1.try代码块不能脱离catch代码块或finally代码块而单独存在。try代码块后面至少有一个catch代码块或finally代码块。
2.try代码块后面可以有零个或多个catch代码块,还可以有零个或至多一个finally代码块。如果catch代码块和finally代码块并存,finally代码块必须在catch代码块后面。
3. try代码块后面可以只跟finally代码块。
4. 在try代码块中定义的变量的作用域为try代码块,在catch代码块和finally代码块中不能访问该变量。
5. 当try代码块后面有多个catch代码块时,Java虚拟机会把实际抛出的异常对象依次和各个catch代码块声明的异常类型匹配,
如果异常对象为某个异常或其子类的实例,就执行这个catch代码块,而不会再执行其他的catch代码块。
6. 如果一个方法可能出现受检查异常,要么用try...catch语句捕获,要么用throws子句声明将它抛出。
7. throw语句后面不允许紧跟其它语句,因为这些语句永远不会被执行。
八. 编写/使用自己的异常类,
在特定的问题领域,可以通过扩展Exception类或RuntimeException类来创建自定义的异常。异常类包含了和异常相关的信息,这有助于负责捕获异常的catch代码块,正确地分析并处理异常。
如果程序中有太多的检查,程序的运行就会慢好多。如果在测试阶段会有这种检查,而在发布阶段能自动删除这些东西。该多好!这就是断言机制。
断言使用
在JDK1.4中,Java语言引入一个新的关键字: assert。该关键字有两种形式:
assert 条件
以及
assert 条件:表达式
这两种形式都会对条件进行评估,如果结果为假则抛出AssertionError。在第二种形式中,表达式会传入AssertionError的构造器并转成一个消息字符串。表达式字符串部分的唯一目的就是生成一个消息字符串。AssertionError对象并不存储表达式的值,因此你不可能在以后获取它。
要断言x不是负数,只需要使用如下简单的语句:
2. 断言内容代码编译
因为assert是一个新的关键字,因此在使用时需要告诉编译器你编译所使用jdk的版本号。
javac -source 1.4 MyClass.java,在jdk的后续版本中,对断言的支持成为默认特性(我们使用的是JDK5.0以上,使用不需要使用这个编译,默认就支持的)。
3. 断言内容代码执行
默认情况下,断言是关闭的。要通过-enableassertions或者-ea选项来运行程序以打开断言:
java -enableassertions com.briup.ch07.Xxxx
java -ea com.briup.ch07.Xxxx
打开或关闭断言是类装载器的功能。当断言功能被关闭时,类装载器会跳过那些和断言相关的代码,因此不会降低程序运行速度。
注意:使用eclipse运行代码的时候也是可以传参数的(包括俩种参数)
java -xx com.briup.ch07.Test yy
xx是给JVM传的参数 yy是给Test类的main方法传的参数
一. 什么是线程:
进程是指运行中的应用程序,每一个进程都有自己独立的内存空间。一个应用程序可以同时启动多个进程。
线程是指进程中的一个执行流程。一个进程可以由多个线程组成。即在一个进程中可以同时运行多个不同的线程,它们分别执行不同的任务,当进程内的多个线程同时运行时,这种运行方式称为并发运行。
线程与进程的主要区别在于:每个进程都需要操作系统为其分配独立的内存地址空间,而同一进程中的所有线程在同一块地址空间中工作,这些线程可以共享同一块内存和系统资源。比如共享一个对象或者共享已经打开的一个文件。
二. java中的线程
在java虚拟机进程中,执行程序代码的任务是由线程来完成的。每当用java命令启动一个Java虚拟机进程时,Java虚拟机都会创建一个主线程。该线程从程序入口main()方法开始执行。
计算机中机器指令的真正执行者是CPU,线程必须获得CPU的使用权,才能执行一条指令。
java中大致可以把线程分为前台线程(执行线程)、后台线程(守护线程或者叫精灵线程)
三. 线程的创建和启动
前面我们提到Java虚拟机的主线程,它从启动类的main()方法开始运行。此外,用户还可以创建自己的线程,它将和主线程并发运行。创建线程有两种方式,如下:
. 扩展java.lang.Thread类;
. 实现Runnable接口;
Thread类代表线程类,它的最主要的两个方法是:
. run()——包含线程运行时所执行的代码;
. start()——用于启动线程;
用户的线程类只需要继承Thread类,覆盖Thread类的run()方法即可。在Thread类中,run()方法的定义如下:
public void run(); //没有抛异常,所以子类重写亦不能抛异常
1) 主线程与用户自定义的线程并发运行
a. Thread类的run()方法是专门被自身的线程执行的,主线程不能调用Thread类的run()方法,否则违背了Thread类提供run()方法的初衷;
b. Thread thread =Thread.currentThread(); 返回当前正在执行这行代码的线程引用;
String name = thread.getName(); 获得线程名字;
每个线程都有默认名字,主线程默认的名字为main, 用户创建的第一个线程的默认名字为"Thread-0",第二个线程的默认名字为"Thread-1",依引类推。Thread类的setName()方法可以显示地设置线程的名字;
2) 多个线程可以共享同一个对象的实例变量。
3) 不要覆盖Thread类的start()方法
创建了一个线程对象,线程并不自动开始运行,必须调用它自己的start()方法。
4) 一个线程只能被启动一次,//多次启动抛出IllegalThreadStateException异常
实现Runnable接口
Java不允许一个类继承多个类,因此一旦一个类继承了Thread类,就不能再继承其他的类。为了解决这一问题,Java提供了java.lang.Runnable接口,它有一个run()方法,定义如下:
public void run();
实现接口的类对象必须被包装成thread类对象才能start.
线程状态
1. 新建状态(New)
用new语句创建的线程对象处于新建状态,此时它和其他Java对象一样;仅在堆区中被分配了内存;
2. 就绪状态(Runnable)
当一个线程对象创建后,其他线程调用它的start()方法,该线程就进入就绪状态,处于这个状态的线程位于可运行池中,等待获得CPU的使用权。
3. 运行状态(Running)
处于这个状态的线程占用CPU,执行程序代码。在并发运行环境中,如果计算机只有一个CPU, 那么任何时刻只会有一个线程处于这个状态。如果计算机有多个CPU, 那么同一时刻可以让几个线程占用不同的CPU,使它们都处于运行状态。只有处于就绪状态的线程才有机会转到运行状态。
4. 阻塞状态(Blocked)
指线程因为某些原因放弃CPU,暂时停止运行。当线程处于阻塞状态时,Java虚拟机不会给线程分配CPU,直到线程重新进入就绪状态,它才有机会转到运行状态。
阻塞状态可分为三种:
. 位于对象等待池中的阻塞状态(Blockedin objects' wait pool): 运行状态时,执行某个对象的wait()方法;
. 位于对象锁池中的阻塞状态(Blockedin object's lock pool): 当线程处于运行状态,试图获得某个对象的同步锁时,如该对象的同步锁已经被其他线程占用,Java虚拟机就会把这个线程放到这个对象的锁池中;
. 其他阻塞状态(OtherwiseBlocked): 当前线程执行了sleep()方法,或者调用了其他线程的join()方法,或者发出了I/O请求时,就会进入这个状态。
当一个线程执行System.in.read()方法时,就会发出一个I/O请求,该线程放弃cpu, 进入阻塞状态,直到I/O处理完毕,该线程才会恢复运行。
5. 死亡状态(Dead)
当线程退出run()方法时,就进入死亡状态,该线程结束生命周期。线程有可能是正常执行完run()方法退出,也有可能是遇到异常而退出。不管该线程正常结束还是异常结束,都不会对其他线程造成影响。
五. 线程调度
计算机通常只有一个CPU, 在任意时刻只能执行一条机器指令,每个线程只有获得CPU的使用权才能执行指令。所谓多线程的并发运行,其实是指从宏观上看,各个线程轮流获得CPU的使用权,分别执行各自的任务。在可运行池中,会有多个处于就绪状态的线程在等待CPU,Java虚拟机的一项任务就是负责线程的调度。线程的调度是指按照特定的机制为多个线程分配CPU 的使用权。有两种调度模型:
. 分时调度模型:让所有线程轮流获得CPU的使用权,并且平均分配每个线程占用CPU的时间片。
. 抢占式调度模型:优先让可运行池中优先级高的线程较多可能占用CPU(概率高),如果可运行池中线程的优先级相同,那么就随机选择一个线程,使其占用CPU。处于可运行状态的线程会一直运行,直至它不得不放弃CPU。Java虚拟机采用这种。
一个线程会因为以下原因而放弃CPU:
. Java虚拟机让当前线程暂时放弃CPU,转到就绪状态;
. 当前线程因为某些原因而进入阻塞状态;
. 线程运行结束;
线程的调度不是跨平台的,它不仅取决于Java虚拟机,还依赖于操作系统。在某些操作系统中,只要运行中的线程没有阻塞,就不会放弃CPU;在某些操作系统中,即使运行中的线程没有遇到阻塞,也会在运行一段时间后放弃CPU,给其他线程运行机会。
1. stop
Thread类的stop()方法可以强制终止一个线程,但从JDK1.2开始废弃了stop()方法。在实际编程中,一般是在受控制的线程中定义一个标志变量,其他线程通过改变标志变量的值,来控制线程的自然终止、暂停及恢复运行。
2. isAlive:
final boolean isAlive():判定某个线程是否是活着的(该线程如果处于可运行状态、运行状态和阻塞状态、对象等待队列和对象的锁池中返回true)
3. Thread.sleep(5000);
放弃CPU, 转到阻塞状态。当结束睡眠后,首先转到就绪状态,如有其它线程在运行,不一定运行,而是在可运行池中等待获得CPU。线程在睡眠时如果被中断,就会收到一个InterrupedException异常,线程跳到异常处理代码块。
4. void sleepingThread.interrupt():
中断某个线程
5. boolean otherThread.isInterrupted():
测试某个线程是否被中断,与static boolean interrupted()不同,对它的调用不会改变该线程的“中断”状态。
6. public void join();
public void join(long timeout);
挂起当前线程(一般是主线程),直至它所调用的线程终止才被运行。线程A中调用线程B.join(),是使A线程阻塞,因为是A线程调用的B.join()这个方法谁调用谁阻塞。
线程的同步
多个线程在操纵共享资源——实例变量时,有可能引起共享资源的况争。为了保证每个线程能正常执行操作,保证共享资源能正常访问和修改。Java引入了同步进制,具体做法是在有可能引起共享资源竞争的代码前加上synchronized标记。这样的代码被称为同步代码块。
每个Java对象都有且只有一个同步锁,在任何时刻,最多只允许一个线程拥有这把锁。当一个线程试图执行带有synchronized标记的代码块时,该线程必须首先获得this关键字引用的对象的锁。
. 如果这个锁已经被其他线程占用,Java虚拟机就会把这个线程放到this指定对象的锁池中,线程进入阻塞状态。在对象的锁池中可能会有许多等待锁的线程。等到其他线程释放了锁,Java虚拟机会从锁池中随机取出一个线程,使这个线程拥有锁,并且转到就绪状态。
. 假如这个锁没有被其他线程占用,线程就会获得这把锁,开始执行同步代码块。在一般情况下,线程只有执行完同步代码块,才会释放锁,使得其他线程能够获得锁。
如果一个方法中的所有代码都属于同步代码,则可以直接在方法前用synchronized修饰。
public synchronizedString pop(){...}
等价于
public String pop(){
synchronized(this){...}
}
线程同步的特征:
1. 如果一个同步代码块和非同步代码块同时操纵共享资源,仍然会造成对共享资源的竞争。因为当一个线程执行一个对象的同步代码块时,其他线程仍然可以执行对象的非同步代码块。
2. 每个对象都有唯一的同步锁。
3. 在静态方法前面也可以使用synchronized修饰符。此时该同步锁的对象为类对象(类的Class对象)。
4. 当一个线程开始执行同步代码块时,并不意味着必须以不中断的方式运行。进入同步代码块的线程也可以执行Thread.sleep()或者执行Thread.yield()方法,此时它并没有释放锁,只是把运行机会(即CPU)让给了其他的线程。
5.synchnozied声明不会被继承。
同步是解决共享资源竞争的有效手段。当一个线程已经在操纵共享资源时,其他共享线程只能等待。为了提升并发性能,应该使同步代码块中包含尽可能少的操作,使得一个线程能尽快释放锁,减少其他线程等待锁的时间。
线程的通信
锁对象.wait(): 执行该方法的线程释放对象的锁,Java虚拟机把该线程放到该对象的等待池中。该线程等待其它线程将它唤醒;
锁对象.notify(): 执行该方法的线程唤醒在对象的等待池中等待的一个线程。Java虚拟机从对象的等待池中随机选择一个线程,把它转到对象的锁池中。如果对象的等待池中没有任何线程,那么notify()方法什么也不做。
锁对象.notifyAll():会把对象的等待池中的所有线程都转到对象的锁池中。
注意:notify notifyAll只会唤醒等待池中等待同一个锁对象的线程,因为同一个时刻在等到池中可能会有多个线程,而这多个线程可能是在等待不同的锁对象
八. 线程的死锁
A线程等待B线程持有的锁,而B线程正在等待A持有的锁;
九. 线程让步
Thread.yield()静态方法,如果此时具有相同优先级的其他线程处于就绪状态,那么yield()方法将把当前运行的线程放到可运行池中并使另一个线程运行。如果没有相同优先级的可运行线程,则yield()方法什么也不做。
sleep()和yield()方法都是Thread类的静态方法,都会使当前处于运行状态的线程放弃CPU,把运行机会让给别的线程。区别:
. sleep()不考虑其他线程优先级; yield()只会给相同优先级或者更高优先级的线程一个运行的机会。
. sleep()转到阻塞状态; yield()转到就绪状态;
. sleep()会抛出InterruptedException异常, yield()不抛任何异常
. sleep()比yield方法具有更好的可移植性。对于大多数程序员来说,yield()方法的唯一用途是在测试期间人为地提高程序的并发性能,以帮助发现一些隐藏的错误,所以yield()并不常用。
调整线程优先级
注意:优先级高的线程只能获得较多运行的概率,但是实际中不一定真的有效果
线程优先级的使用原则与操作系统有着密切的联系因此在JAVA中的线程的调度是完全受其所运行平台的操作系统的线程调度程序控制的。所有虽然我们可以设置线程的优先级但是在运行的时候不一定能够确切的体现出来。所有处于就绪状态的线程根据优先级存放在可运行池中,优先级低的线程获得较少的运行机会,优先级高的线程获得较多的运行机会。Thread类的setPriority(int)和getPriority()方法分别用来设置优先级和读取优先级。优先级用整数来表示,取值范围是1-10,Thread类有以下3个静态常量。
. MAX_PRIORITY: 10, 最高;
. MIN_PRIORITY: 1, 最低;
. NORM_PRIORITY: 5, 默认优先级;
其它:stop(): 中止线程运行;已过时
resume(): 使暂停线程恢复运行 已过时
suspend(): 暂停线程,不释放锁; 已过时
释放对象的锁:
. 执行完同步代码块;
. 执行同步代码块过程中,遇到异常而导致线程终止,释放锁;
. 执行同步代码块过程中,执行了锁所属对象的wait()方法,释放锁进入对象的等待池;
线程不释放锁:
. Thread.sleep()方法,放弃CPU,进入阻塞状态;
. Thread.yield()方法,放弃CPU,进入就绪状态;
. suspend()方法,暂停当前线程,已过时;
关于interrupt、isInterrupted、interrupted三个方法的理解
interrupt和isInterrupted 是Thread类中的非静态方法可以用线程对象来访问t.interrupt() t.isInterrupted()
interrupted 是Thread类中的静态方法 可以用类名来访问Thread.interrupted()
interrupt 可以用来中断线程
interrupted isInterrupted 可以用来判断线程是否被中断过
一个线程A是不可能调用线程B的interrupt方法来中断B线程的运行, 因为线程A调用线程B的interrupt方法的时候,线程A肯定是在使用cup运行这个中断方法的代码,这个时候线程B那就肯定没有在使用CPU运行代码了(同一时刻值只可能有一个线程使用cup在运行代码),所以就谈不上说线程A调用线程B的interrupt方法来把正在运行的线程B给中断了这里你要明白另外一个事情,那就是一个线程在使用cpu运行的时候,可以自己调用interrupt来中断自己,就是自己让自己立马交出cpu的使用权,然后回到线程的就绪状态。
既然线程A不能调用线程B的interrupt方法来中断线程B,那么我们在线程A中真的调用了线程B的interrupt方法的话,这个操作具有什么意义呢?
这个时候我们要了解另外一个事情,那就是每一个线程对象中有一个特别的标记,那就是可以标记出这个线程有没有被其他线程进行尝试中断过,因为线程A中是可以调用线程B的interrupt方法来尝试中断线程B的,虽然这样的中断并不能起真正的中断作用(这个上面已说明过了),但是可以把线程B对象中的按个标识从false改为true,表示在某个情况满足下有其他线程想中断你了。
那么也就是说我们可以在线程A中通知线程B,我在某个条件满足的情况下至少尝试着调用你的interrupt方法去中断你了,然后这个时候线程B在运行的时候过程中就可以通过另外俩个方法isInterrupted和 interrupted来知道是否有其他线程在试图中断自己了,然后就是线程B根据这个信息,来自己决定是否要中断自己(自己可以调用自己的 interrupted方法来中断自己),或者不理不睬这样的信息通知而去继续运行下去,具体是要进行那种选择,那么就要看你到时候的具体的业务逻辑需求了。
最后还有一个问题,那就是为什么线程在wait或者sleep期间被中断时候会抛出InterruptedException呢?
因为处于这些状态的线程是不可能拿到cup的使用权的,那么就意味着即使我调用你的interrupt方法去尝试的通知你说:"我试图在中断你",那么你也是不可能拿到cup的使用权去处理的我的这种中断的通知信息的,所以这时候就会抛出异常了(当然我们也可以利用这特点来改变一个阻塞状态线程的状态)。
当然这里所描述的一些细节情况可能学生们不能马上理解,因为它的实际用途和应用需要在具体业务逻辑业务中才能体现出其真正的作用和意义