黑马程序员---异常,同步
2012-12-11 21:41
232 查看
重要掌握:死记硬背 1:异常的产生原因及产生的效果 2:异常处理的标准结构 3:throw和throws 关键字 4:自定义异常 5:assert 3.1 为什么要有异常处理? 在进行异常处理的讲解之前,先来看一下以下的程序: public class ExceptionDemo01 { public static void main(String[] args) { int i=0; int j=0; System.out.println("====计算开始====="); System.out.println("====计算结果:"+i/j); System.out.println("====计算结束=====); } } 以上的代码在进行计算时计算完成之后,没有正确的退出,而是因为产生了异常所以次退出。 计算机的发展中有两大杀手: 1:断电:UPS电源 2:被除数为0; 那么所谓的异常处理,就是指程序在出现问题时依然可以正确的执行完。 异常的处理格式: 在JAVA中使用try...catch 进行异常的处理,完整格式如下: try { 可能出现异常的语句; } catch(异常类 异常对象) { 异常处理; } catch(异常类 异常对象) { 异常处理; } catch(异常类 异常对象) { 异常处理; } finally { 异常的出口; } ArithmeticException 数学异常 NumberFormatException 输入的不是数字异常 ArrayIndexOutOfBoundsException 输入的参数不对异常 Exception 异常 throws 和 throw的区别: throws 使用在函数上 throws后面跟异常类,可以跟多个。用逗号隔开。 throw 使用在函数内 throw 后面跟的异常对象。 throws 关键字 在程序的方法声明处可以使用throws关键字,使用此关键字的最大好处, 在方法中不处理任何的异常,而交给被调用处处理。 class Math { public int div(int i,int j)throws Exception { return i/j; } } public class ExceptionDemo4 { public static void main(String[] args) { Math m =new Math(); try { int temp=m.div(10,0); System.out.println(temp); } catch (Exception e) { e.printStackTrace(); //打印异常 } System.out.println("计算结束"); } } 既然在普通方法上可以使用throws,在主方法上呢? class Math { public int div (int x ,int y) throws Exception { return x/y; } } public class ExceptionDemo4 { public static void main(String[] args)throws Exception { Math m = new Math(); int temp=m.div(10,0); System.out.println(temp); } } 结论:如果在主方法上使用了throws关键字的话,则所有的异常交给了最大的头,JVM进行处理了。 throw 关键字 public class ExceptionDemo4 { public static void main(String[] args) { try { throw new Exception(); } catch (Exception e) { System.out.println(e); } } } 总结: 在程序中可以使用throw关键字,人为的抛出一个异常 在异常的处理中,实际上每次产生异常的时候就产生了一个实例话异常对象 那么此时,也就可以通过抛出异常对象的方式完成. 异常的标准格式: 现在定义一个div()方法,那么此方法要求有以下的功能: 1:进入方法执行计算前要有输出 2:此方法执行完之后也要有输出 3:如果有异常,则交给被调用处处理。 class Thew { public int div(int x,int y)throws Exception { System.out.println("计算前输出"); int temp =0; try { temp =x/y; } catch (Exception e) { throw e; } finally { System.out.println("计算后输出"); } return temp; } } class ExceptionDemo4 { public static void main(String[] args) { Thew t =new Thew (); try { int temp =t.div(10,0); System.out.println(temp); } catch (Exception e) { System.out.println("出现数学异常"+e); } } } 总结: 这是一个异常处理标准格式:在日后的开发中,所有的异常处理结构,都采用这样的类处理格式。是一个非常关键的处理格式,throw 和throws和finally就在这快一起使用! RuntimeException : 第一个例子: 之前学过将字符串变为int型数据: public static int parseInt(String s) throws NumberFormatException 发现在此方法中有throws关键字,那么既然有此关键字,则意味着try..catch。。进行处理操作 public class ExceptionDemo4 { public static void main(String[] args) { Integer.parseInt("123"); } } 但是,在使用时发现程序中根本就没有必要进行异常的处理。 NumberFormatException 是RuntimeException的子类 那么也就是说只哟是RuntimeException的异常对象,虽然使用了throws。但是 程序中也可以不使用try。。catch。 第2个例子: Exception中有一个特殊的子类异常RuntimeException运行时异常。 它有什么特点呢? 如果在函数内抛出该异常,函数上可以不用声明。编译一样通过。 如果在函数上声明了该异常,调用者可以不用进行处理,编译一样通过。 3。8 自定义异常: 一个类只要继承了Exception 则就表示一个自定义的异常,当发现系统中提供的异常类 不够的时候就可以这么做。 class Demo extends Exception { public Demo(String msg) { super(msg); } } public class ExceptionDemo4 { public static void main(String[] args) { try { throw new Demo("自定义异常"); } catch (Exception e) { e.printStackTrace(); } } } 2.2 包 讲解知识点: 1:包的定义及导入 2:JAR 命令的使用 3:JDK 1.5的新特性---静态导入 4:访问控制权限 5:命名规范 1:包 实际上就是一个文件夹,在不同的文件夹中可以存在同名的类,那么这就是包的作用. 在JAVA中使用package语法定义包. package li.hong.aini; public class Demo3 { public static void main(String[] args) { System.out.println("hello"); } } 此时,使用package在类中定义了一个包,在生成class文件的时候需要将所有的class类放到指定包中. 之后,通过以下的命令进行打包编译: javac -d . 类名.java -d 表示生成目录,根据package的定义生成 . 表示在当前所在的文件夹中生成. 那么,此时的完成的类名称: 包.类名称了 2:导入包 在程序中有很多的类都存在不同的包中,如果现在要导入不同的类, 则可以使用import语句. package org.demoa; class SayHello { public void print() { System.out.println("hello word"); } } 此类完成之后,在不同包的类中导入此类,产生对象并使用. package org.demob; import org.demoa.SayHello; //导入所需要的类 public class TextSayHello { public static void main(String[] args) { SayHello sh = new SayHello(); sh.printl(); } } 在进行导包操作的时候,一定要注意点,如果一个包中的类需要被外部访问,那么此类一定 声明成public class 类型. package org.demoa; public class SayHello { public void print() { System.out.println("hello word"); } } 当然,如果现在假设要导入一个包中的多个类,如果分开导入的话会很麻烦. 但是,在使用包的时候也有一点注意: package org.demob; import org.demoa.*; //导入所需要的类 import org.democ.*; //导入所需要的类 public class TestSayHello { public static void main(String[] args) { SayHello sh = new SayHello(); sh.print(); } } 因为democ和demova下都存在SayHello,所以,此时最好明确的指出哪个包中的 SayHello类 package org.demob; import org.demoa.*; //导入所需要的类 import org.democ.*; //导入所需要的类 public class TestSayHello { public static void main(String[] args) { org.demoa.SayHello sh = new org.demoa.SayHello(); sh.print(); } } 在进行开发的时候一定要始终注意一个原则: 所有的类必然要放在一个包之中,没有包的类是不存在的. 3.3 静态导入 如果一个包中的某个类中的方法全部都是static类型的,则就可以使用静态导入. Math 类 package org.demox; public class Math { public static int add(int x,int y) { return x+y; } public static int sub(int x,int y) { return x-y; } } 在demox中定义的Math类中的全部方法都是静态操作,那么此时就可以使用 import static 语句完成. import static org.demox.Math.*; public class TestMath { public static void main(String [] args) { System.out.println(add(1,1)); System.out.println(sub(1,1)); } } 3.4 系统常用包 在JAVA中提供了大量的系统开发包,这些包: 1: java.lang 此包中包含了各种常用的类; 例如: String ,此包属于自动导入,但是在JDK 1.0的时候 此包必须手工导入. 2: java.lang.reflect 此包为反射机制包.是整个java乃至整个java世界中最重要的包. 此包可以完成大量底层操作. 3:java.util包:工具包,如果把此包掌握的非常清楚,则可以方便的做各种设计,各种开发. 最重要的!重点学! 4:java.io 包: IO 操作 5:java.net : 网络编程 6:java.sql :数据库编程 7: java.text 国际化程序的应用. ========================================= 第一天: 2.1 JDK 是开发工具。里面包括开发和运行坏竟 JRE 里面只有运行坏竟 里面包含着虚拟机 什么是标识符??? 在程序中自定义的一些名称。 注意使用表示符的规则: 由26个英文字母大小写,数字:0-9 符号:_ $ 组成 定义合法标识符的规则: 1:数字不可以开头。 2:不可以使用关键字。 JAVA中严格区分大小写。 注意:在起名字时,为了提高阅读性,要尽量有意义。 包名:多单词组成时所有字母都小写。 类名接口名 :多单词组成时,所有单词的首字母大写。 变量名和函数名:多单词组成时,第一个单词首字母小写。 第2个单词开始每个单词首字母大写 xxxYyyZzz 常量名:所有字母都大写。多个单词时每个单词用下划线连接 XXX_YYY_ZZZ 2.2 常量与变量 1:常量表示不能改变的数值 2:JAVA中常量的分类: 1,整数常量。所有整数 2,小数常量。所有小数 3,布尔型常量。较为特有,只有两个数字。true false 4,字符常量。将一个数字字母或者符号用单引号(‘’)标识 5,字符串常量。将一个或者多个字符用双引号标识 6,null常量。只有一个数值,null 3:对于整数:JAVA有三种表现形式。 1,十进制: 0-9 ,满10进1 2,八进制: 0-7 ,满8进1 用0开头表示 3,十六进制 0-9 A-F 满16进1 用0x开头表示 ============================================ 线程: 2:了解多线程的基本操作方法 3:理解同步及死锁的概念。 3。1 进程与线程 从计算机操作系统发展来看,经历了2个阶段: 1是单进程处理:在DOS系统中只要出现病毒,则立刻就有反映. 因为在DOS系统中属于单进程处理,即, 在同一时间段上只能有一个程序在执行. 2是多进程处理:windows操作系统是一个多进程,假设在windows中 出现病毒了,则系统照样可以使用. 对于资源来讲,所有的IO设备,CPU等等都只有一个,那么对于多进程的处理 来讲,在同一时间段上会有多个程序在运行,但在同一时间点上只能 有一个程序运行. 线程和进程的关系: 线程是在进程基础上进一步划分,如果进程消失,则线程就消失, 而线程消失的话,则进程依然会执行,未必会消失. 1:掌握多线程两种实现手段。 JAVA中可以有两种方式实现多线程操作, 一种是继承Tread类, 另 外一种是实现Runnable接口. 一个类只要继承了Thread类,同时复写了本类中的run方法,则就可以实现多线程 package chen.li.hong; public class MyThread extends Thread { private String name; //定义name属性 public MyThread(String name) //构造函数,给对象初始化 { this.name=name; } public void run() //覆写run()方法 { for(int i=0;i<10;i++) { System.out.println("Thread运行:"+name+",i="+i); } } } 以上的类已经完成了多线程的操作类,那么下面就启动线程。 package chen.li.hong; public class ThreadDemo01 { public static void main(String []args) { MyThread mt1=new MyThread("线程A"); MyThread mt2=new MyThread("线程B"); mt1.run(); //调用线程体 mt2.run(); //调用线程体 } } 此时的执行结果非常有规律,先执行第一个对象,之后执行第2个对象。 并没有实现交互的运行。 从JDK的文挡中可以发现,一旦调用strt()方法,则会通过JVM找到run()方法 public void start() 使用start()方法启动线程看一下: package chen.li.hong; public class ThreadDemo01 { public static void main(String []args) { MyThread mt1=new MyThread("线程A"); MyThread mt2=new MyThread("线程B"); mt1.start(); //调用线程体 mt2.start(); //调用线程体 } } 此时,程序已经可以正常的进程交互的运行了。 但是,需要思考的是,为什么非要使用start()方法启动多线程呢? 在JDK的安装路径下,src.zip是全部的java源程序,通过此代码找到Thread类中的start()方法的定义 public synchronized void start() //定义的start()方法 { if(started) //判断程序是否已经启动 throw new IllegalThreadStateException(); started = true; //如果没有启动则修改状态 start0(); //调用start0()方法 } private native void start0(); //使用native关键字声明的方法,没有方法体 操作系统有很多种,Windows Linux UNLX 既然多线程操作中要进行CPU资源的强占,也就是 说要等待CPU调度, 那么这些调度的操作是由各个操作系统的地层事先的,所以在JAVA程序中根本就没法实现。 那么此时的JAVA的设计者定义了 NATIVE关键字,使用此关键字表示可以调用操作系统的地层函数,那么这样的技术又成为 JNI技术, 而且,此方法在执行的时候将调用RUN方法完成,由系统默认调用。 但是,第一种操作中有一个最大的限制,一个类只能继承一个父类。 3。22 Runnable 接口 (Runnable 的意思是 可追捕的,可猎取的) 在实际开发中一个多线程的操作 类很少去使用THREAD类完成,而是通过Runnable接口完成。 观察Runnable接口的定义: public interface Runnable { public void run(); } 所以一个类要实现此接口,并覆写run()方法。 (implements Runnable 实现借口的意思) package chen.li.hong; public class MyThread implements Runnable //实现Runnable 接口 { private String name; public MyThread(String name) { this.name=name; } public void run() { for(int i=0;i<50;i++) { System.out.println("Thread运行:"+name+",i="+i); } } } 完成之后,下面继续启动多线程。 但是在使用Runnable定义的子类中并没有start()方法,而只有Threade类中有 在Thread类中存在以下一个构造方法: public Thread(Runnable target) 此构造方法接收Runnable的子类实例,也就是说现在可以通过Thread类来启动Runnable 实现的多线程。 package chen.li.hong; public class ThreadDemo01 { public static void main(String []args) { MyThread mt1=new MyThread("线程A"); MyThread mt2=new MyThread("线程B"); new Thread(mt1).start(); //调用线程体 new Thread(mt2).start(); //调用线程体 } } 以上的操作代码也属于交替的运行,所以此时程序也同样实现了多线程的操作。 3。2。3 两种实现方式的区别及联系 在程序的方法中只要是多线程则肯定永远是实现Runnable接口为正统操作。 因为实现Runnable接口想比继承Thread类有如下的好处: 1:避免单继承的局限,一个类可以同时实现多个接口 2:适合于资源的共享 (重要了解) 以卖票的程序为例: 通过Thread类完成 package chen.li.hong; public class MyThread extends Thread //继承Thread类 { private int ticket=5; //一共才5张票 public void run() //覆写run()方法 { for(int i=0;i<50;i++) { if(this.ticket>0) { System.out.println("卖票:ticket="+this.ticket--); } } } } 下面建立三个线程对象,同时卖票: package chen.li.hong; public class ThreadDemo01 { public static void main(String []args) { MyThread mt1=new MyThread(); //一个线程 MyThread mt2=new MyThread();//一个线程 MyThread mt3=new MyThread();//一个线程 mt1.start(); //开始卖票 mt2.start(); //开始卖票 mt3.start(); //开始卖票 } } 运行结果:发现现在一共卖了15张票,但是实际上只有5张票。 所以证明每一个线程都在卖自己的票,没有达到资源共享的目的。 那么,如果现在实现的是Runnable接口的话,则就可以实现资源的共享: package chen.li.hong; public class MyThread implements Runnable //实现Runnable接口 { private int ticket=5; //一共才5张票 public void run() //覆写run()方法 { for(int i=0;i<50;i++) { if(this.ticket>0) { System.out.println("卖票:ticket="+this.ticket--); } } } } 编写多个线程卖票的操作: package chen.li.hong; public class ThreadDemo01 { public static void main(String []args) { MyThread mt=new MyThread(); //一个线程 new Thread(mt).start(); new Thread(mt).start(); new Thread(mt).start(); } } 虽然现在程序中有3个线程,但是3个线程一共才卖出了5张票。 也就是说使用Runnable实现的多线程可以达到资源共享的目的。 实际上Runnable接口和Thread类之间还是存在联系的: 看老师在42片中结尾画的图: 3。3 线程的操作方法 对于线程来讲所有的操作方法都是在Thread类中定义的,所以如果要想明确的理解操作方法 则肯定要从thread类着手: 3。3。1 设置和取得名字 在Thread类中可以存在以下几个方法可以设置和取得名字: 1:设置名字: set: public final void setName(String name) 构造: public Thread(Runnable target.String name) public Thread(Srting name) 2: 取得名字: public final String getName() 在线程的操作中因为其操作的不确定性,所以提供了一个方法,可以取得当前的操作线程 public static Thread currentThread() 对于线程的名字一般内是在启动前进行设置,最好不要设置相同的名字, 最好不要为一个线程改名字。 package chen.li.hong; public class MyThread implements Runnable //实现Runnable接口 { public void run() { for(int i=0;i<10;i++) { System.out.println(Thread.currentThread().getName()+"线程正在运行"); //Thread.ourrentThread().getName() 找到该线程点的 名字 } } } 那么此时测试一下,上面是取得了线程的名字。 package chen.li.hong; public class ThreadDemo01 { public static void main(String []args) { MyThread mt = new MyThread(); //Runnable子类实例 Thread thread1 =new Thread(mt,"线程A"); //声明线程 Thread thread2 =new Thread(mt,"线程B"); Thread thread3 =new Thread(mt,"线程C"); thread1.start();//启动线程 thread2.start(); thread3.start(); } } 明白代码的作用之后,在来看如下的程序: package chen.li.hong; public class ThreadDemo01 { public static void main(String []args) { MyThread mt = new MyThread(); //Runnable子类实例 new Thread(mt,"自定义线程").start(); mt.run(); //对象直接调用方法 } } 得出一个结论,在程序运行时主方法实际上就是一个主线程。 一直强调JAVA是多线程的操作语言,那么它是怎么实现多线程的呢? 实际上对于JAVA来讲,每一次执行JAVA命令对于操作系统来讲都将启动一个JVM的进程,那么主方法实际上只是这个进程上的一步划分。 问题: 在JAVA执行中 一个JAVA程序中至少启动几个线程? 至少启动2个: main 主线程 gc 垃圾集收线程 3 3 2 线程的休眠 (重要 知识点) 让一个线程稍微小小的休息一下,之后起来继续工作,称为休眠。 public static void sleep(long millis) throws InterruptedException 在使用此方法 需要进行try catch 处理。。。。。 package chen.li.hong; public class MyThread implements Runnable //实现Runnable接口 { public void run() { for(int i=0;i<10;i++) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(e); //打印异常 } System.out.println(Thread.currentThread().getName()+"线程正在运行"); //Thread.ourrentThread().getName() 找到该线程点的 名字 } } } package chen.li.hong; public class ThreadDemo01 { public static void main(String []args) { MyThread mt = new MyThread(); //Runnable子类实例 new Thread(mt,"线程A").start(); new Thread(mt,"线程B").start(); } } 结果:程序的执行变得缓慢起来。 3。3。3 线程的中断 在sleep()方法中存在interruptException 那么会造成异常的方法就是中断。 public void interrupt() 3。3。4 线程的优先级 在多线程的操作中,所有的代码实际上都是存在优先级的,优先级高的就有可能先执行。在线程中使用以下的方法设置: public final void setPriority(int newPriority) 对于优先级来说 在线程中有如下三种: 最高:public static final int MAX_PRIORITY 中等:public static final int NORM_PRIORITY 最底:public static final int MIN_PRIORITY package chen.li.hong; class MyThread implements Runnable { public void run() { for(int i=0;i<10;i++) { try { Thread.sleep(600); //延迟操作 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"线程运行"); } } } package chen.li.hong; public class PriorityDemo01 { public static void main(String [] args) { MyThread mt =new MyThread(); Thread t1 =new Thread(mt,"线程A"); Thread t2 =new Thread(mt,"线程b"); Thread t3 =new Thread(mt,"线程c"); t1.setPriority(Thread.MAX_PRIORITY); t2.setPriority(Thread.NORM_PRIORITY); t3.setPriority(Thread.MIN_PRIORITY); t1.start(); t2.start(); t3.start(); } } 各个线程可以设置优先级,那么主方法的优先级是什么呢? 3。4 同步与死锁 (理解)面试中经常问到的一个问题。 要能用最精辟的一句话解释这样的一个问题。 使用 Runnable接口 有个特点,他的所有的操作中,是多个线程共享资源。 例子: package chen.li.hong; public class MyTicketThread implements Runnable { private int ticket=5; public void run() { for(int i=0;i<50;i++) { if(this.ticket>0) { try { Thread.sleep(300); //延迟300毫秒 这个目的就是延迟 //延缓和判断结果this.ticket-- 修改值的操作 } catch (InterruptedException e ) { e.printStackTrace(); } System.out.println("卖票"+this.ticket--); } } } } package chen.li.hong; public class SynDemo01 { public static void main(String [] args) { MyTicketThread mt = new MyTicketThread(); new Thread(mt,"A").start(); new Thread(mt,"B").start(); new Thread(mt,"C").start(); } } 造成此类问题的根本原因在于,判断剩余票数和修改票数之间加入了延迟操作。 在JAVA中可以通过同步代码的方式进行代码的枷锁操作,同步的实现有2种方式: 1:同步代码快 2:同步方法 同步代码快如何使用?: 使用synchronized 在JAVA中最复杂的一个关键字 此关键字进行同步代码快的声明,使用此关键字之前,必须明确到底要锁定 哪个对象。一般都是以当前对象为主: synchronized(对象) //一般都是将this进行锁定 { 需要同步的代码快; } 使用同步代码修改之前的代码: package chen.li.hong; public class MyTicketThread implements Runnable//实现接口 { private int ticket=5; //总共票数 public void run() //复写run方法 { for(int i=0;i<50;i++) { this.sale(); //调用同步方法 } } public synchronized void sale() //同步方法 { if(this.ticket>0) { try { Thread.sleep(300); } catch (InterruptedException e ) { e.printStackTrace(); } System.out.println("卖票="+this.ticket--); } } } package chen.li.hong; public class SynDemo01 { public static void main(String [] args) { MyTicketThread mt = new MyTicketThread(); new Thread(mt,"A").start(); new Thread(mt,"B").start(); new Thread(mt,"C").start(); } } 此时,实际上就可以给出java中方法定义的完整的格式了: 访问权限:public protected default private 看本记录。 发现同步可以保证资源的完整性,但是过多的同步也可会出现问题。 3。4。2 死锁:: 在程序中过多的同步会产生死锁的问题。那么死锁属于程序运行时时候发生的一种 特殊状态,本章演示一个简单的操作代码,只要观察到死锁最终的运行状态即可: 有时间看看死锁练习题 3。5 生产者===消费者 (理解) 在多线程中有一个最经典的操作案例--- 生产者和消费者,生产者不断生产内容,但是消费者不断取出内容。 3。5。1 基本实现: 现在假设生产的内容都保存在info类中,则在生产者要有info类的引用,而消费者中也要存在info类的引用 生产者应该不断的生产信息,消费者不断的取出,所以现实多线程的操作。
相关文章推荐
- AdaultBird--我的黑马程序员之路!Chapter2---异常体系,多线程,同步代码块和同步函数,死锁
- 黑马程序员 Java面向对象——异常
- 黑马程序员--内部类、包、异常、多态(Java)
- [黑马程序员]——java中的异常Exception
- 黑马程序员_异常
- 黑马程序员-------关于异常
- 黑马程序员——异常篇
- [黑马程序员]--Java语言异常
- 黑马程序员--Java基础--05内部类及异常
- 黑马程序员JAVA基础-异常
- 黑马程序员7. finally&异常总结&包
- 黑马程序员--------java基础 Exception (异常)
- 黑马程序员 ——Java基础之内部类、异常、包
- 黑马程序员──异常
- 黑马程序员---异常处理
- 黑马程序员——Java基础——内部类、异常、包
- 黑马程序员——面对对象(异常)
- 某企业虚拟化平台时间同步异常排查
- 黑马程序员——11,多线程,同步函数,死锁,一些零散的小知识点
- 黑马程序员——Java面向对象(三)之内部类、异常、包等