您的位置:首页 > 职场人生

黑马程序员---异常,同步

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类的引用
生产者应该不断的生产信息,消费者不断的取出,所以现实多线程的操作。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: