一.面向对象的异常和包
1.异常
(1)概念的理解:异常指的是程序运行时出现的不正常情况。如同汽车出了故障不能行驶,程序抛出异常后
也将无法正常工作,必须要先处理异常问题,不能处理的,直接将程序停止。
出现异常时,将先处理异常,后面的语句不执行。
简单说,异常的发生,将功能停止。
异常类:在java中,通过面向对象的思想,用类的形式对不正常的情况进行描述和封装对象。
不同的问题用不同的类描述,如角标越界,空指针,ConcurrentModificationException等。
问题越多,意味着描述的异常类越多,将其共性内容不断的向上抽取,就形成了异常体系。
异常体系的特点:子类的后缀名都是用其父类名作为后缀,阅读性很强。
最终问题分成两大类:
1.一般不可处理的,即Error。
Error是由JVM虚拟机抛出的严重性的问题。这种问题一旦发生,一般不针对性处理,
直接修改程序。
2.可以处理的,即为Exception。
(2)可抛性的理解
Throwable:无论是error,还是exception,问题发生就应该可以抛出,让调用者知道并处理。
该体系的特点就在于Throwable及其所有的子类都具有可抛性。
其实就是通过两个关键字来体现的:throws,throw。凡是可以被这两个关键字所操作的类和对象都
具备可抛性。
(3)throws和 throw的区别:
1.throws使用在函数上。
throw使用在函数内。
2.throws抛出的是异常类,而且可以抛出多个,用逗号隔开。
throw抛出的是异常对象。
(4)处理异常的方式:
①声明。在可能抛出异常的函数后面用throws申明该异常类,以便调用者使用功能时很快明白可能出现
的问题。并在函数内部抛出该异常对象。
②捕捉。这是可以对异常进行针对性处理的方式。
异常捕捉的格式:
try//接收功能抛出的异常。
{
//需要被检测异常的代码。
}
catch(异常类 变量)//该变量用于接收发生的异常对象。
{
//处理异常的代码。
}
finally
{
//一定会被执行的代码。
}
一个功能如果抛出了多个异常,那么调用时,必须有对应多个catch进行针对性处理。
内部有几个需要检测的异常,就抛出几个异常,抛出几个,就catch几个。
(5)什么时候catch,什么时候throws呢?(在调用者那儿)
功能内容可以解决,用catch。
解决不了,用throws告诉调用者,由调用者解决。
(6)细节:多态的catch中,如果有父类的catch在,一定要将父类的catch放在最后,
如果放在第一个,所有异常都所属于这个父类catch,将先给第一个catch来处理。
catch (Exception e)//多catch时,父类的catch放在最下面。
{
}
(7)finally说明
举个例子,连接数据库时一般有两个动作:查询和关闭连接。查询时,数据库会规定一定的时间,访问
数据库时,无论查询是正常的还是异常。必须在有限时间内。超过规定时间,数据库会自动关闭连接,以释
放资源。所以,关闭连接是必须要执行的。这时,“关闭连接”就放在finally代码块中实现。
在trycatch finally代码块组合中,当没有必要资源需要释放时,可以不定义finally。但是, 异常无法
直接catch处理,但是资源需要关闭时,需要定义finally。
2.自定义异常
对于角标是正数不存在,可以用角标越界来表示。对于负数为角标的情况,则可用负数角标异常来表示。
可是负数角标异常在java中没有定义。我们可以按照java异常的创建思想,用面向对象思想将负数角标异常进
行自定义描述,并封装成对象。这种自定义的问题描述称为自定义异常。
如果让一个类称为异常类,必须要继承异常体系。只有称为异常类体系的子类才有资格具备可抛性。
才可以被throws ,throw关键字操作。
而异常的分类有:
(1)编译时被检测异常:只要是Exception和其子类都是。除了特殊子类RuntimeException体系。
这种问题一旦出现,希望在编译时就进行检测,让这种问题有对应的处理方式。
(2)编译时不受检测异常(运行时异常):RuntimeException和其子类。
这种问题的发生,无法让功能继续,运算无法进行,更多是因为调用者的原因导致的,或者引发
了内部状态的改变而导致的。
这种问题一般不处理,直接编译通过,在运行时,让调用者运行程序时强行停止,让调用者修改
代码。
所以自定义异常时,要么继承Exception,要么继承RuntimeException。
3.异常的处理事项:
(1)子类在覆盖父类的方法时,父类的方法如果抛出了异常。那么子类的方法只能抛出父类的异常或者该
异常的子类。或者不抛。但是,不能抛出其他异常。
(2)如果父类抛出多个异常,那么子类只能抛出父类异常的子集。
简单说,子类覆盖父类只能抛出父类的异常或者子类或者子集。
注意:如果父类的方法没有抛出异常,那么子类覆盖时绝对不能抛出。
4.包(package)
(1)Jar包
Jar:java的压缩包。
生成压缩包的命令行: jar -cf haha.jar pack
说明: c 创建归档目录
f 指定归档文件名
pack 表示把pack这个包压缩。
haha 生成压缩包的文件名
jar -xvf haha.jar 解压压缩包。
但是一般不需要解压,只需要设置classpath= ./haha.jar。就是说,jar包可以直接使用。
(2)包的理解
包其实也是一种封装形式。它可以对类文件进行分类管理,给类提供多层命名空间。
所以,类名的全称为:包名.类名。
包名中所有字母都是小写的。
(3)导入包:导入包是为了简化类名书写。
导包的原则是:用到哪个类,就导入哪个类。
注意:包与包之间的类进行访问,被访问的包中的类必须是public的,被访问的包中的类的方法也必须
是public的。
二.多线程
1.进程和线程
进程:正在进行中的程序。
线程:就是进程中一个负责程序执行的控制单元(执行路径)。一个进程至少要有一个线程。
每一个线程都有自己运行的内容,这个内容可以称为线程要执行的任务。
2.多线程:一个进程中可以同时执行多条路径,称为多线程。开启多线程是为了同时运行多部分代码。
多线程的好处和弊端:
好处:解决了多部分代码同时运行的问题。
弊端:线程太多会导致效率降低。
其实,应用程序的执行都是CPU在做着快速的切换完成的。
单核的时候,其实程序不是同时运行,只是切换速度快,肉眼分辨不出而已。
双核的时候,才有可能真正实现同时运行。
3.创建线程方式一:继承Thread类
(1) 步骤:
①定义一个类继承Thread类。
②覆盖Tread类中的run方法。
③将要运行的代码封装到run方法,创建Thread子类对象,创建线程。
④调用start方法开启线程并调用线程的任务run方法,执行。
创建线程的目的是为了开启一条执行路径,去运行指定的代码和其他代码同时运行。
运行的指定代码就是执行路径的任务。jvm创建的主线程的任务都定义在了主函数中。
(2)自定义线程的任务
Thread类用于描述线程,线程是需要任务的,所以Thread类也有对任务的描述。
这个任务就通过Thread类中的run方法来体现的。
也就是说,run方法就是封装自定义线程运行任务的函数。
(3)调用run和调用start方法的区别:
创建线程后,如果不调用start()方法而直接调用run()方法时,自定义的线程没有被开启,run方
法还是在主线程中运行,就是简单的对象调用功能。 不论有多少个对象调用run方法,都会按照主线程来
运行,不存在cpu切换的情况。
但是调用start()方法,会开启线程,并调用run方法执行线程任务。
而且如果是多个自定义线程同时运行,CPU会随机快速切换来运行线程。
小细节:
Demo d1 = new Demo("旺财");Demo d2 = new Demo("xiaoqiang");d1.start();//start()的作用是开启线程,调用run()方法。d2.start();//cpu线程切换是随机的,所以结果也不会是顺序的。System.out.println("over"+Thread.currentThread().getName());//说明cpu切换线程是随机的。
(4)通过继承Thread类来创建线程的弊端:
①继承Thread类目的仅仅是为了覆盖run方法,而现在子类继承了Thread类中所有的方法。
②当一个类有自己的父类的时候,不能再继承Thread类。java不能多继承。只能有一个父类。
这时候,就需要用别的方法来创建线程。
4.创建线程方式二:实现Runnable接口
(1)创建线程的步骤:
①定义类实现Runnable接口。
②在类中定义run方法,覆盖Runnable接口的run方法。并将线程的任务代码封装到类中的run方法。
③通过Thread类创建线程对象,并将接口的子类对象作为Thread类的构造函数的参数进行传递。
因为线程的任务都封装在子类对象的run方法中。所以在线程对象创建前就要明确线程的任务。
④调用线程对象的start方法开启线程。
(2)实现Runnable接口的好处(和继承Thread类的区别):
①将线程的任务从线程的子类分离,进行了单独的封装。按照面向对象的思想将任务封装成对象。
②避免了java单继承的局限性。子类如果有了自己的父类,但是有一部分代码需要单独开辟线程来执
行。但是java只能单继承,不能多继承,所以该子类不能再继承Thread类。不过可以使用接口来扩
展额外功能。就是实现Runnable.
所以,此创建线程的方法较为常用。
(3)细节:没有接口子类对象传递时,调用的是自身的run方法,
有接口子类对象传递时,调用的是子类对象中的run方法。
5. 同步代码块
(1)线程安全问题
线程安全问题产生的原因:
①多个线程在操作共享的数据。
②操作共享数据线程代码有多条。
当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算。
就会导致线程安全问题的产生。
解决线程安全的问题就是,将操作共享数据的代码封装,当一个线程执行这些代码时,其他代码不能参
与运算。只有当前线程运算完这些代码后,其他线程才能参与运算。
这就是同步代码块存在的意义。
(2)同步代码块
同步代码块就是为了解决线程安全问题而存在。
其格式是:
synchronized(对象)
{
//需要被同步的代码。
}
①同步代码块的原理
一个线程进来后,占据了标志的值,其他线程进来时,没有拿到标记位,进不来,所以就保证同步
代码块每次只能有一个线程执行。相当于锁的功能,称为同步锁。
②同步代码块的利弊
好处:解决了线程安全的问题。
弊端:同步外的线程每次都要判断锁,效率降低。
③同步的前提:必须有多个线程并共用一个锁。
一般用Object类的对象。但是一定要定义为成员变量。不然多线程用的不是同一个锁。
(3)同步函数
同步函数同样可以解决线程安全的问题,它是同步代码块的简写。因为它的锁默认为this。
所以只需要用synchronized修饰函数即可。
格式一般为:public synchronized void show()
{}
静态同步函数:静态函数没有this。需要修改成静态同步函数时,使用的锁是该函数所属的字节码文
件对象。可以用getClass方法获取,也可以用当前 类名.class表示。
(4)死锁
死锁是由同步的嵌套引起的。如下图。同步函数里有同步代码块,代码块里又调用同步函数。
class Ticket implements Runnable{private static int num = 100;Object obj = new Object();booleanflag = true;public void run(){if(flag){while (true){synchronized(obj){show();}}}elsewhile(true)this.show();}public synchronized void show(){synchronized(obj){if(num>0){try{Thread.sleep(10);}catch (InterruptedException e){}System.out.println(Thread.currentThread().getName()+"...sale..."+num--);}}}}
对于死锁,在编程中应避免。
针对同步嵌套,解决办法是:将多个锁都放到同一个锁中。即在嵌套外面,再加一层锁。这样在拿到
其中一个锁之前,都要先拿到最外面的锁。
class Test implements Runnable{private boolean flag=true;Test(boolean flag){this.flag = flag;}public void run(){if(flag){while(true)synchronized(MyLock.lockc){synchronized(MyLock.locka){System.out.println(Thread.currentThread().getName()+"...if...locka");synchronized(MyLock.lockb){System.out.println(Thread.currentThread().getName()+"...if...lockb");}}}}else{while(true)synchronized(MyLock.lockc){synchronized(MyLock.lockb){System.out.println(Thread.currentThread().getName()+"...else...lockb");synchronized(MyLock.locka){System.out.println(Thread.currentThread().getName()+"...else...locka");}}}}}}class MyLock{public static final Object locka = new Object();public static final Object lockb = new Object(); public static final Object lockc = new Object();}class DeadLockTest {public static void main(String[] args) {Test a = new Test(true);Test b = new Test(false);Thread t1 = new Thread(a);Thread t2 = new Thread(b);t1.start();t2.start();}}
(5)线程间的通信
多个线程在处理同一资源,但是任务却不同。
多线程运行时出现了安全问题,在各自任务中加了obj同步锁之后,还是会出现安全问题,同步的前
提是同步中必须有多个线程并使用同一个锁。而现在在两个任务中使用了不同的锁。
如果资源是唯一的,可以用资源做锁。
(6)等待唤醒机制
涉及的方法:
①wait():让线程处于冻结状态,被wait的线程会被存储到线程池中。
②notify():唤醒线程池中任意一个线程。
③notifyAll():唤醒线程池中的所有线程。
这些方法都必须定义在同步中。因为这些方法是用于操作线程状态的方法。必须要明确到底操作的是哪
个锁上的线程。
为什么操作线程的方法wait,notify,notifyAll定义在了Object类中?
因为这些方法是监视器的方法,监视器其实就是锁。
锁可以是任意的对象,任意的对象调用的方式之一定定义在Object类中
(7)停止线程
有两种方法:①stop方法结束
②run方法结束
控制线程的任务结束: 任务中都会有循环结构,只要控制住循环,就可以结束任务。
控制循环通常就用定义标记来完成。
但是如果线程处于冻结状态,无法读取标记,可以使用interrupt()方法将线程从冻结状态强制恢复到运
行状态中来,让线程具备cpu的执行资格。但是强制动作会发生interruptedException,记得要处理。
(8)wait 和sleep的区别:
1.wait可以指定时间,也可以不指定。
sleep必须指定时间。
2.在同步中,对于cpu的执行权和锁的处理不同。
wait:释放执行权,释放锁。
sleep:释放执行权,不释放锁。(它不需要被人唤醒。)