(一)异常机制
所谓异常是指在程序运行的过程发生的一些不正常事件,如除0溢出,数组下标越界,所要读取的文件不存在、网络连接
失败、非法参数等
Java程序的执行过程中如出现异常事件,可以生成一个异常类对象,该异常对象封装了异常事件的信息,并将其交给java运
行时系统,这个过程称为抛出异常,不处理的话将会导致程序中断
Java通过API中Throwable类的众多子类描述各种不同的异常,因而Java异常都是对象,是Throwable子类的实例,描述了
出现在一段编码中的错误条件,当条件生成时,错误将引发异常
(1)Throwable(可抛出): 有两个重要的子类:Exception(异常)和 Error(错误)都是 Java 异常处理的重要子
类,各自都包含大量子类
通常异常处理常用3个函数来获取异常的有关信息:
(1)getCause():返回抛出异常的原因。如果 cause 不存在或未知,则返回 null
(2)getMeage():返回异常的消息信息
(3)printStackTrace():对象的堆栈跟踪输出至错误输出流,作为字段 System.err 的值
(2)Error(错误):是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作
无关,而表示代码运行时 JVM出现的问题,这些错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是
程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异
常状况
(3)Exception(异常):是程序本身可以处理的异常,Exception 类有一个重要的子类RuntimeException
,RuntimeException 类及其子类表示 JVM 常用操作引发的错误,如若试图使用空值对象引用、除数为零或数组越界,则分别引
发运行时异常(NullPointerException、ArithmeticException)和ArrayIndexOutOfBoundException
【异常分类】
1、编译时异常:编译的时候要检查,并且一定要写 try-catch 编译才能通过(IOException、SQLException、
JSONException、ClassNotFoundException等)
2、运行时异常:表现为编译时正常,运行的时候出异常,RuntimeException是 java.lang 包底下所有的异常当中的唯一
一个运行时的异常(NullPointerException、ArithmeticException 和 ArrayIndexOutOfBoundException等)
【异常处理机制】(预处理,防止程序中断)
在 Java 应用程序中,异常处理机制为:捕获异常 和 抛出异常
1、捕获异常(try、catch 和 finally):在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器(exception
handler)。潜在的异常处理器是异常发生时依次存留在调用栈中的方法的集合,当异常处理器所能处理的异常类型与方法抛出的
异常类型相符时,即为合适的异常处理器。运行时系统从发生异常的方法开始,依次回查调用栈中的方法,直至找到含有合适异
常处理器的方法并执行。当运行时系统遍历调用栈而未找到合适 的异常处理器,则运行时系统终止,同时意味着Java程序的终止
【try-catch语句】
public static void main(String[] args) {
// TODO Auto-generated method stub
int a = 6;
int b = 0;
try { // try监控区
if (b == 0)
throw new ArithmeticException();// 通过throw语句抛出异常( ArithmeticException是RuntimException的子类,运行时异常将由Java运行时系统自动抛出,不需要使用throw语句)
System.out.println("a/b的值是:" + a / b);
} catch (ArithmeticException e) {// catch捕获异常(捕获异常,可以不处理)
System.out.println("程序出现异常,变量b不能为0");
}catch (ArrayIndexOutOfBoundsException e) {
System.out.println("数组下标越界异常");
}catch(Exception e){
System.out.println("我是异常");
}
System.out.println("程序正常结束");
}
}
PS: try-catch语句块可以捕获多个异常,但是要注意的是先捕获子类的异常再捕获父类的异常
【try-catch- finally语句】
1、try 块:用于抛出异常,其后可接0个或多个catch块,如果没有catch块,则必须跟一个finally块
2、catch 块:用于捕获并处理try块中抛出的异常
3、finally 块:无论是否捕获或处理异常,finally块里的语句都会被执行,当在try块或catch块中遇到return语句时,
finally语句块将在方法返回之前被执行,在以下4种特殊情况下,finally块不会被执行:
1)在finally语句块中发生了异常
2)在前面的代码中用了System.exit()退出程序
3)程序所在的线程死亡
4)关闭CPU
4、语法格式:
try{
(可能发生异常的程序代码)
}catch(异常类 异常对象){
(捕获异常对象并进行处理)
}finally{
(无论是否产生异常,是否捕获异常,最后必须执行的语句)
}
public static void main(String args[]){
// TODO Auto-generated method stub
int i=0;
String greetings[]={"hello world !","hello world !!","helloworld !!!"};
while(i<4){
try{
System.out.println(greetings[i++]);
}catch(ArrayIndexOutOfBoundsException e){
System.out.println("数组下标越界异常");
}finally{
System.out.println("--------------");
}
}
}
5、try、catch、finally语句块的执行顺序
2、抛出异常:throw(任何位置,手动抛出异常)和 throws(声明方法可能要抛出的异常)当一个方法出现错误引发异
常时,方法创建异常对象并交付运行时系统,异常对象中包含了异常类型和异常出现时的程序状态等异常信息,运行时系统负责
寻找处置异常的代码并执行)
throws 关键字 :用于在方法上标识要暴露的异常,抛出的异常交由调用者处理
throw 关键字:手动抛出,可以在任意位置上抛出(用于自己写的异常,今后会在strus框架个性化异常的时候使用)
两者区别:throws修饰在方法上,告诉调用者此方法可能会抛出异常,后面跟上可能要抛出的异常类名,而throw用在方
法内,后面跟上要抛出的异常类对象(往往两个结合一起使用)
class Bar {
public void enter(int age) throws Exception {
if (age < 18) {
// 受查异常(必须捕获,不捕获编译都通不过)和非受查异常
// throw new IllegalArgumentException("年龄不合格");
throw new Exception("年龄不合格");// 直接抛出父异常
} else {
System.out.println("欢迎光临");
}
}
}
【Java中常见的异常类】
1. RuntimeException子类:
(1)java.lang.ArrayIndexOutOfBoundsException,数组索引越界异常,当对数组的索引值为负数或大于等于数组大小时抛出
(2)java.lang.ArithmeticException:算术条件异常,比如整数除零等。
(3)java.lang.NullPointerException:空指针异常。当应用试图在要求使用对象的地方使用了null时,抛出该异常。譬如:调用null对象的实例方法、访问null对象的属性、计算null对象的长度、使用throw语句抛出null等等
(4)java.lang.ClassNotFoundException:找不到类异常。当应用试图根据字符串形式的类名构造类,而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常。
(5)java.lang.NegativeArraySizeException :数组长度为负异常
(6)java.lang.ArrayStoreException:数组中包含不兼容的值抛出的异常
(7)java.lang.SecurityException:安全性异常
(8)java.lang.IllegalArgumentException:非法参数异常
2. IOException
(1)IOException:操作输入流和输出流时可能出现的异常。
(2)EOFException:文件已结束异常
(3)FileNotFoundException:文件未找到异常
3. 其他
(1)ClassCastException:类型转换异常类
(2)ArrayStoreException:数组中包含不兼容的值抛出的异常
(3)SQLException:操作数据库异常类
(4)NoSuchFieldException:字段未找到异常
(5)NoSuchMethodException:方法未找到抛出的异常
(6)NumberFormatException:字符串转换为数字抛出的异常
(7)StringIndexOutOfBoundsException:字符串索引超出范围抛出的异常
(8)IllegalAccessException:不允许访问某类异常
(9)InstantiationException:当应用程序试图使用Class类中的newInstance()方法创建一个类的实例,而指定的类对象无法被实例化时,抛出该异常
(二)IO框架
流是一个很形象的概念,当程序需要读取数据的时候,就会开启一个通向数据源的流(可以是文件,内存,或是网络连
接),而当程序需要写入数据的时候,就会开启一个通向目的地的流,有起点和终点的字节集合,是对数据传输的总称或抽象,
即数据在两设备间的传输称为流
流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作
1、IO流:JAVA与其他节点间互换数据使用的通道
2、作用: IO流用来处理设备之间的数据传输(Java对数据的操作是通过流的方式, Java用于操作流的对象都在 java.io
包中)
3、分类:
(1)按照数据流向不同分为:输入流、输出流
JAVA会在我们的电脑内存中新建一块内存(JAVA内存),而另外一个节点比如说是我们硬盘中的一个文件(硬盘),
当JAVA内存的数据输入到硬盘的时那就叫做输出,相反硬盘把数据读到JAVA内存中就叫输入。输入和输出是相对于JAVA内存在
讲的,凡是往JAVA内存中添加数据就是输入(Input),反之就是输出(Output)
(2)流按照处理数据类型不同分为:字节流、字符流
字节流读取的最小单位是一个(1byte=8bit),而字符流一次可以读取一个字符(1char = 2byte = 16bit)
字节流:一个字节一个字节来传输,如果是要用来传输中文字符的话会把中文拆分成两个字节一个一个传输
字符流:一个字符一个字符传输(JAVA中一个字符是2个字节)
(3)按功能上来分:节点流、处理流(缓冲流、转换流 和 数据流)
节点流:直接从一个数据源读写数据的流(没有经过包装和修饰)
处理流(装饰流):不能直接接在节点上,只能套在节点流上,是在对节点流封装的基础上的一种流,作用给节点
流增加额外的功能,对原始读取流进行转换其它格式的流(对数据进行过滤)
FileInputStream是一个节点流,可以直接从文件读取数据,但是BufferedInputStream可以包装 FileInputStream,使
得其有缓冲功能
(1)节点流类型
(2)节点流类型
该类型是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写,处理流的构造方法总是要带
一个其他流对象作为参数,主要可以分为以下几种:
1)缓冲流(BufferedInPutStream/BufferedOutPutStream和BufferedWriter/BufferedReader),给节点流增加缓冲
区的功能,可以提高对流的操作效率
2)转换流(InputStreamReader/OutputStreamWriter)把字节流转换成字符流,所以只能套在字节流(基本上没有
多大作用)该类型时字节流和字符流之间的桥梁,该流对象中可以对读取到的字节数据进行指定编码的编码转换
3)数据流(DataInputStream/DataOutputStream)
4)过滤流(FilterInputStream/FilterOutputStream)
4、JAVA IO流对象
JAVA 的流都是从四个抽象类中继承的:InputStream/OutputStream(字节流)和Reader/Writer(字符流),并且
InputStream和Reader,OutputStream和Writer的作用是相类似的
public class Test {
public static void main(String[] args) {
//java 得到文件length()方法 然后(int)(File.length() /1024)+1 是什么意思
File file=new File("C:\\log.txt");
System.out.println("得到文件的字节数:"+file.length());
/**
* file.length()得到文件长度
* 1024是1KB等于1024B
* (int)(file.length() /1024)+1 : 小于1KB的文件不要为0,加1就是为了结果不要显示0
*/
System.out.println("得到文件的字节数:"+(int)(file.length() /1024)+1);
}
}
5、IO流总结
(1)关于字节流和字符流的区别
实际上字节流在操作的时候本身是不会用到缓冲区的,是文件本身的直接操作的,但是字符流在操作的时候下后是会用
到缓冲区的,是通过缓冲区来操作文件的(需要刷新缓冲区或关掉资源)
硬盘上的所有文件都是以字节的形式进行传输或者保存的,包括图片等内容,但是字符只是在内存中才会形成的,所以
在开发中字节流使用广泛( 结论:只要是处理纯文本数据,就优先考虑使用字符流,除此之外都使用字节流)
(2)学习Adapter和Decorator模式会基本明白IO架构
(3)流中的方法都声明抛出异常,调用流方法时必须处理异常,否则不能通过编译(IOException)
(4)IO抽象类所实现的常用接口: Closeable(close()方法来实现对PrintWriter的关闭)和 Flushable接口(flush()方法
来实现人为的刷新)
(三)多线程机制
线程,有时被称为轻量级进程(Lightweight Process,LWP),是进程中能够独立运行执行的实体(控制流),是处理器
调度和分配的基本单位
线程有两个基本类型:(1)用户级线程:管理过程全部由用户程序完成,操作系统内核心只对进程进行管理;(2)系
统级线程(核心级线程):由操作系统内核进行管理,操作系统内核给应用程序提供相应的系统调用和应用程序接口API,以使
用户程序可以创建、执行、撤消线程
【Java中线程模型】
Java 虚拟机(JVM)允许应用程序并发地运行多个执行线程,Java语言提供了多线程编程的扩展点(需要操作系统的支
持),并给出了功能强大的线程控制API ,在Java中多线程的实现有两种方式:
(1)继承(扩展)java.lang.Thread类
(2)实现 java.lang.Runnable接口(打算多重继承时,优先选择实现Runnable)
1、Thread 创建线程
在程序中创建新的线程的方法之一是继承 Thread 类,并通过 Thread子类声明线程对象。继承Thread 类并覆盖 Thread类
的 run 方法完成线程类的声明, 通过new创建派生线程类的线程对象(run 中的代码实现了线程的行为)
public class ThreadDemo1 extends Thread {
/*
* 新的线程类(派生类)
*/
public ThreadDemo1() {
}
public ThreadDemo1(String name) {
super(name);
}
// 重写run方法
public void run() {
for (int count = 1, row = 1; row < 10; row++, count++) {
for (int i = 0; i < count; i++) {// 循环输出指定的count数目的*
System.out.print('*');
}
System.out.println();
}
}
public static void main(String[] args) {
/*
* 注意:Java线程并不能按调用顺序执行,而是并行执行的单独代码。如果要想得到完整的直角三角形,需要在执行一个线程之前,判断程序前面的线程是否终止,如果已经终止,再来调用该线程(面向JVM)
*/
ThreadDemo1 td1 = new ThreadDemo1();// 只产生一个新的线程
ThreadDemo1 td2 = new ThreadDemo1();// 并没有按照程序中调用的顺序来执行, 而是产生了多个线程赛跑现象
ThreadDemo1 td3 = new ThreadDemo1();
td1.start();
td2.start();
td3.start();
}
}
2、Runnable 接口创建线程
通过实现 Runnable 接口的方法是创建线程类的第二种方法,利用实现 Runnable 接口来创建线程的方法可以解决 Java
语言不支持的多重继承问题。Runnable 接口提供了 run()方法的原型,因此创建新的线程类时,只要实现此接口,即只要特定的
程序代码实现Runnable接口中的 run()方法,就可完成新线程类的运行
public class ThreadDemo2 implements Runnable {
public static void main(String[] args) {
// TODO Auto-generated method stub
Runnable rb = new ThreadDemo2();//目标对象(可以声明,但不能实例)
Thread tr = new Thread(rb);//线程对象
tr.start();// 使该线程开始执行(Java 虚拟机调用该线程的 run 方法)
}
@Override
public void run() {
for (int count = 1, row = 1; row < 10; row++, count++) {
for (int i = 0; i < count; i++) {// 循环输出指定的count数目的*
System.out.print('*');
}
System.out.println();
}
}
}
【线程生命周期】
1、新建状态(New):新创建了一个线程对象
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法(该状态的线程位于可运行线程池
中,变得可运行,等待获取CPU的使用权)
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
阻塞的情况分三种:
(1)等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中
(2)同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中
(3)其他阻塞:运行的线程执行sleep()或join()方法或者发出了I/O请求时,JVM会把该线程置为阻塞状态,当
sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期
【Java多线程编程】
1、线程同步机制
如果并发执行的多个线程间需要共享资源或交换数据,则这一组线程称为交互线程,交互线程并发执行时相互之间会干扰
或影响其他线程的执行结果,因此需要有同步机制(存在两种关系:竞争关系和协作关系),线程同步机制让多个线程有序的访
问共享资源,而不是同时操作共享资源(线程的同步机制包括线程互斥和线程同步,线程互斥是线程同步的特殊情况)
Java 应用程序中的多线程可以共享资源,例如文件、数据库、内存等。当线程以并发模式访问共享数据时,共享数据可能
会发生冲突,Java引入线程同步的概念,以实现共享数据的一致性
为了解决 线程不同步而导致错误问题,Java 提供了 锁机制 实现线程的同步。 锁机制的原理是每个线程进入共享代码之前
获得锁,否则不能进入共享代码区,并且在退出共享代码之前释放该锁,这样就解决了多个线程竞争共享代码的情况,达到线程
同步的目的。Java中锁机制的实现方法是共享代码之前加入 synchronized 关键字
同步格式:
Java 提供关键字 synchornized 用于声明一段程序为临界区,使线程对临界资源采用互斥使用方式,有两种用法:声明
一条语句块,或者声明一个方法(竞争关系,采用线程互斥实现)
(1)方法同步:用关键字 synchonized 可将方法声明为同步,格式如下:
class 类名{
public synchonized 类型名称 方法名称(){
......
}
}
(2)语句块同步: 对于同步块,synchornized 获取的是参数中的对象锁
synchornized(obj){
//………………….
}
当线程执行到这里的同步块时,必须获取 obj 这个对象的锁才能执行同步块;否则线程只能等待获得锁,必须注意的
是obj对象的作用范围不同,控制情况不尽相同
(3)同步类的属性:如果同步的是类的属性,同步类的成员变量的一般格式如下 :
class method{
Object o = new Object(); //创建Object类型的成员变量o
public void test(){
synchornized(o) //同步块
{
//………………………
}
}
}
当两个并发线程访问同一个对象的 synchornized(o)同步代码块时,一段时间内只能有一个线程运行,另外的线程必须
等到当前线程执行完同步代码块释放锁之后,获得锁的线程将执行同步代码块
(4) synchronized 静态方法与非静态方法:
synchronized 关键字加 static 静态方法上是给 Class 类上锁,可以对类的所有实例对象起作用;synchronized
关键字加到非 static 静态方法上是给对象上锁,对该对象起作用(不是同一个锁)
2、线程通信(协作关系,采用线程同步实现)
java.lang.Object类提供wait()、notify()和notifyAll()方法实现线程通信,方法声明如下:
public final void wait() throws InterruptedException //等待,最终方法
public final void wait(long timeout) throws InterruptedException // 等待指定时间
public final void notify() //唤醒一个等待线程
public final void notifyAll() //唤醒所有等待线程
wait()、notify()和notifyAll()方法提供线程间通信方法,对于线程同步问题,仅有这三个方法是不够的,还必须设置信号量
及状态,约定对于共享变量的多种互斥操作方法
1、生产者与消费者
生产者与消费者是个很好的线程通信的例子,生产者在一个循环中不断生产共享数据,而消费者则不断消费生产者生
产的共享数据。程序必须保证有共享数据,如果没有,消费者必须等待生产新的共享数据。两者之间的数据关系如下:
1) 生产者生产前,如果共享数据没有被消费,则生产等待;生产者生产后,通知消费者消费
2)消费者消费前,如果共享数据已经被消费完,则消费者等待;消费者消费后,通知生产者生产
为了解决生产者和消费者的矛盾,引入了等待/通知(wait/notify)机制(wait使线程停止运行,notify 使停止的线程继续运行)
2、共享队列
共享队列类是用于保存生产者生产、消费者消费的共享数据。共享队列有两个域:value(元素的数目)、isEmpty(队列
的状态),提供了put和 get 两个方法
3、运行生产者与消费者
注意:考虑到程序的安全性,多数情况下使用 notifiAll(),除非明确可以知道唤醒哪一个线程。wait方法调用的前提条件是
当前线程获取了这个对象的锁,也就是说 wait方法必须放在同步块或同步方法中
3、线程死锁
为了保证数据安全使用 synchronized同步机制, 当线程进入堵塞状态 (不可运行状态和等待状态)时,其他线程无法访
问那个加锁对象(除非同步锁被解除),所以一个线程会一直处于等待另一个对象的状态, 而另一个对象又会处于等待下一个对
象的状态,以此类推,这个线程“等待”状态链会发生很糟糕的情形,即封闭环状态(也就是说最后那个对象在等待第一个对象的
锁)。此时,所有的线程都陷入毫无止境的等待状态中,无法继续运行,这种情况就称为 死锁 (虽然这种情况发生的概率很
小,一旦出现,程序的调试变得困难而且查错也是一件很麻烦的事情)
Java 语言本身并没有提供防止死锁的具体方法,但是在具体程序设计时必须要谨慎,以防止出现死锁现象。通常在程序
设计中应注意,不要使用 stop()、suspend()、resume()以及 destroy()方法。 stop()方法不安全,它会解除由该线程获得的所有对
象锁,而且可能使对象处于不连贯状态,如果其他线程此时访问对象,而导致的错误很难检查出来。suspend()/resume ()方法也
极不安全,调用 suspend()方法时,线程会停下来,但是该线程并没有放弃对象的锁,导致其他线程并不能获得对象锁。调用
destroy()会强制终止线程,但是该线程也不会释放对象锁
4、线程定时器Timer
Timer 是一个普通的类,其中有几个重要的方法,而TimerTask则是一个抽象类,其中有一个抽象方法run(),类型线程中的
run()方法。我们使用Timer创建一个对象,然后使用这对象的schedule方法来完成这种间隔的操作。
Timer 就是一个线程,使用schedule方法完成对TimerTask的调度,多个TimerTask可以共用一个Timer,也就是说Timer对
象调用一次schedule方法就是创建了一个线程,并且调用一次schedule后TimerTask是无限制的循环下去的,使用Timer的
cancel() 停止操作。当然同一个Timer执行一次cancel()方法后,所有Timer线程都被终止
【Java线程池】
在多线程项目中,如果建立的线程过多,反而可能导致运行速度大大减慢,由于线程建立所花费的时间和资源都比较多,
所以我们在多线程中必须很好地来管理线程, 在很好利用多线程能同步工作的好处之外,更有效地提高程序运行速度(避免了大
量线程创建销毁的资源消耗)
线程池是指具有固定数量的线程组成的一种组件,这些线程用来循环执行多个应用逻辑, 建立线程池主要包括4个部分,
分别是:
(1)线程管理:主要是用来建立,启动,销毁工作线程和把工作任务加入工作线程
(2)工作线程:是真正的线程类,运行工作任务
(3)工作队列:它是用来封装线程的容器
(4)工作任务:是实现应用逻辑的具体类
Java中的线程池技术主要用的是ThreadPoolExecutor 这个类(池化资源技术)
【线程总结】
(1)主要是同步机制用的比较多(实现数据同步,火车站售票 和 银行存取款)
(2) 常用的Servlet 或者C/S结构的窗体工作与后台处理必须是多线程的(处理多个用户的请求),Web开发中由于
Servlet容器都给封装,框架也给封装,简单的可以不必考虑多线程
(3)UI 编程,一般UI界面绘制于主线程,为了不阻塞主线程让用户体验更流畅,需要创建单独的线程处理耗时操作,处理完
了再更新主界面(典型的案例就是 Android 应用开发)
(4)线程池在项目开发的运用,有效地提高程序运行速度(Spring)