异常与线程基础
一、异常
异常传递
在方法实现中出现的异常,如果在实现中没有处理,那么会往调用处传递
换句话说,可以在方法实现处或方法调用处进行异常处理(捕获)
运行时异常传递
public static void main(String[] args) {
//异常传递:在方法实现中出现异常;可传递到调用处
//异常传递可在实现处或调用处处理
//案例:运行时异常传递
//1.方法实现处捕获---方法实现处后面也能执行
//2.方法调用处捕获---main方法后面可以执行
try {
a();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("最后执行...");
}
private static void a() {
//try {
int i=1/0;
//}catch (Exception e) {
// e.printStackTrace();
//}
System.out.println("方法实现的最后执行..");
}
编译时异常传递
public static void main(String[] args) {
//异常传递:在方法实现中出现异常;可传递到调用处
//异常传递可在实现处或调用处处理
//案例:运行时异常传递
//1.方法实现处捕获---方法实现处后面也能执行
//2.方法调用处捕获---main方法后面可以执行
try {
a();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("最后执行...");
}
private static void a() {
//try {
int i=1/0;
//}catch (Exception e) {
// e.printStackTrace();
//}
System.out.println("方法实现的最后执行..");
}
finally
finally主要用在捕获中,表示无论是否捕获住,都会执行改区域的代码
try{
}catch(Exception e){
}finally{
}
try{
}finally{
}
//finally用法:配合try使用, 无论是否崩溃,都会执行到finally
/*
try {
int i=1/0;
}finally {
System.out.println("最后...");
}*/
//案例:测试return与finally的优先级
//结论:finally优先级更高,return之前,必须先执行finally
//finally应用场景:关闭资源-io,数据库等
try {
int i = 1/0;
}catch (Exception e) {
System.out.println("捕获异常..");
return;
}finally {
System.out.println("最后..");
}
System.out.println("main最后");
自定义异常
在项目中,如果我们需要自己定制一个异常的提示,那么我们需要自己来设计;例如ID为空的异常
//自定义异常案例:创建学生对象,属性需要进行封装,
//并判断姓名长度不能大于6;年龄必须在1~100之间
//自定义异常好处:可读性更好
class StuAttrException extends RuntimeException{
public StuAttrException(String msg) {
super(msg);
}
}
class Student{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
if(name.length()>6) {
//System.out.println("姓名出现异常...重新录入");
//抛出单个对象--手动产生异常
throw new StuAttrException("姓名出现异常...重新录入");
}else {
this.name = name;
}
}
public int getAge() {
return age;
}
public void setAge(int age) {
if(age>100||age<=0) {
//System.out.println("年龄出现异常...重新录入");
throw new StuAttrException("年龄出现异常...重新录入");
}else {
this.age = age;
}
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
}
public class Test1 {
public static void main(String[] args) {
Student st = new Student();
try {
st.setName("张三丰凤飞飞纷纷");
} catch (Exception e) {
e.printStackTrace();
}
try {
st.setAge(120);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("执行最后...");
}
}
了解重写中的异常
//重写中的异常:不能抛出比父类更宽泛的异常
class Animal{
public void eat() throws RuntimeException {
}
}
class Dog extends Animal{
@Override
public void eat() throws RuntimeException { //此处子类不能抛Exception异常
}
}
二、线程与进程
进程
概述:运行起来的程序就是进程
特点:进程与进程之间的资源是独立的,操作系统分配的基本单位
多进程:同时可以运行多个程序,“宏观并行,微观串行”
线程
概述:进程里面的一条执行路径;如果有多条执行路径,则是多线程
现实中的进程与线程:
进程: QQ音乐,再执行迅雷下载
线程:迅雷中分多条执行路径下载大片;特点:在该进行中多线程会互抢资源
进程与线程区别:
进程是系统分配的基本单位; 线程是cpu调度的基本单位
一个进程一般会有多个线程,至少有一个线程
进程间的资源是独立的; 进程中的多个线程资源是共享的
问题:从main方法出发,执行的代码是单线程还是多线程?
答案:多线程,在后台会有一个守护线程也跟着执行;
守护线程就是守护主线程的,主线程存在,守护线程也存在;主线程不存在,则守护线程也停止(垃圾回收器就在守护线程中)
线程的组成
cpu时间片:多个线程互抢cpu资源,谁抢到谁执行
运行的数据:
栈数据:在线程中执行的局部变量—天生线程安全(多个线程互抢资源和局部变量无关)
堆数据:在线程中执行的成员变量—多线程互抢资源,需要考虑堆数据的安全性
执行的代码:一些线程中的逻辑代码
三、线程创建
继承Thread
//线程的创建:继承Thread
//案例:主线程(main的执行路径)和子线(创建的)程都打印1~200;查看执行的结果
class MyThread extends Thread{
@Override
public void run() { //run中的执行就是子线程的执行
for(int i=1;i<=200;i++) {
System.out.println("子线程..."+i);
}
}
}
public class Test1 {
public static void main(String[] args) {
//创建子线程的注意事项:1.子线程的创建要放到主线程执行前面 2.调start方法
MyThread thread = new MyThread();
//将当前线程对象放入线程组,供cpu调度;当cpu调度到你,则你执行;调度到其他线程,则就绪状态
thread.start();
//thread.start(); //new一次线程对象,不能多次调用,否则报错
new MyThread().start(); //new次new,多次调用,则不会有问题
for(int i=1;i<=200;i++) {
System.out.println("主线程..."+i);
}
}
}
实现Runnable
//线程的创建:实现任务的方式
//案例:主线程(main的执行路径)和子线(创建的)程都打印1~200;查看执行的结果
class Task implements Runnable{
@Override
public void run() {
for(int i=1;i<=200;i++) {
System.out.println("子线程..."+i);
}
}
}
public class Test2 {
public static void main(String[] args) {
Thread th = new Thread(new Task()); //传入任务对象
th.start();
for(int i=1;i<=200;i++) {
System.out.println("主线程..."+i);
}
}
}
线程的状态
基本状态:
new线程(初始状态)—调start—>就绪状态<—-cpu调度run–>执行状态—>线程终止
四、线程的方法
优先级与sleep
线程的优先级,可以改变线程的执行的效率;但不是绝对性可以改变哪个线程先执行完
//线程的优先级: 优先级高,则大概率先执行完,但不绝对
//案例:两个子线程都打印1~200;查看执行的结果
class MyThread extends Thread{
public MyThread(String name) {
super(name); //值传给父类
}
@Override
public void run() {
for(int i=1;i<=200;i++) {
try {
Thread.sleep(1); //睡眠-单位毫秒; 好处复现线程抢占效果
} catch (InterruptedException e) {}
System.out.println(getName()+"..."+i); //调用父类name值
}
}
}
public class Test1 {
public static void main(String[] args) {
MyThread thread = new MyThread("线程1");
thread.setPriority(Thread.MAX_PRIORITY); //设置高优先级
thread.start();
MyThread thread2 = new MyThread("线程2");
thread2.setPriority(Thread.MIN_PRIORITY); //设置低优先级
thread2.start();
}
}
线程礼让
线程礼让yield:中断当次线程,继续和其他资源争抢; 礼让的线程相对会执行的慢一些,但不是绝对性的。
//线程的礼让:
//案例:两个子线程都打印1~200;其中一个线程每次执行都进行礼让,查看执行的结果
class A extends Thread{
@Override
public void run() {
for(int i=1;i<=200;i++) {
Thread.yield(); //线程礼让;理论上会执行得慢一点
System.out.println("线程A..."+i);
}
}
}
class B extends Thread{
@Override
public void run() {
for(int i=1;i<=200;i++) {
System.out.println("线程B..."+i); //调用父类name值
}
}
}
public class Test2 {
public static void main(String[] args) {
new A().start();
new B().start();
}
}
线程合并
线程合并join,也叫插队; 插队的线程绝对性的先执行完
应用场景: 子线程插主线程的队
//线程插队:
//案例:子线程和主线程各打印1~200;主线程执行到5,让子线程插队先执行完,查看执行的结果
class MyTh extends Thread{
@Override
public void run() {
for(int i=1;i<=200;i++) {
System.out.println("子线程..."+i);
}
}
}
public class Test3 {
public static void main(String[] args) throws InterruptedException {
MyTh th = new MyTh();
th.start();
for(int i=1;i<=200;i++) {
System.out.println("主线程线程..."+i);
if(i==5) {
th.join(); //子线程插队
}
}
}
}
线程的状态
线程的基本状态+等待状态
在基本状态的基础上,运行状态里面可以进入等待状态
有限期等待sleep: 等待睡眠毫秒值结束,即可进入到就绪状态; 放到主线程则主线程休眠,放到子线程则子线程休眠
无限期等待join:插队的线程执行完后,即可进入就绪状态;没有执行完,则一直等待下去
五、线程安全
大家思考下,前面的所学的多线程案例中,是否有线程安全的隐患?
没有安全隐患,因为前面的例子都是局部变量,天生线程安全
概述
在多线程的执行中,如果出现了共享数据,且共享数据进行了复合操作,则这样的共享数据就有安全隐患
例如:前面学习集合ArrayList,就是线程不安全的,我们可以模拟集合的不安全性
应用案例
//案例:模拟ArrayList集合在多线程中的不安全性
//安全隐患:通过睡眠可以复现出问题
//共享数据:a,index--两个线程都会执行到;且在方法中会有复合操作篡改数据
//处理: 加锁,将共享数据的操作锁住
//锁的分类:同步代码块,同步方法
//同步代码块: synchronized
//锁的注意事项:1.同一把锁-同一个锁对象 2.锁的范围
class MyList{
String[] a = {null,null};
int index = 0;
public void add(String value) {
//this代表调用add方法的外围list对象,能确保同一个对象
synchronized (this) { //字符串常量 this.getClass()
a[index] = value;
try {
Thread.sleep(8); //睡眠-复现问题
} catch (InterruptedException e) {}
index++;
}
}
}
public class Test1 {
public static void main(String[] args) throws InterruptedException {
MyList list = new MyList();
Thread th1 = new Thread(new Runnable() {
@Override
public void run() {
list.add("hello");
}
});
th1.start();
Thread th2 = new Thread(new Runnable() {
@Override
public void run() {
list.add("world");
}
});
th2.start();
th1.join(); th2.join(); //两个子线程都需要插主线程的队
System.out.println(Arrays.toString(list.a));
}
}
六、总结与作业
总结
1.异常
异常传递及处理;finally的使用-关资源;
自定义异常类-可读性更强;了解重写中的异常
2.线程与进程(重点)
进程概述;线程概述;区别;线程的组成
3.线程的创建(重点)
继承Thread;实现Runnable;线程的基本状态
4.线程的方法
优先级与sleep使用;线程的礼让;
线程合并(插队)-(重点)
线程的等待状态
5.线程安全(重点)
线程的安全隐患描述;复现安全问题;处理安全-加锁;锁的注意事项
作业
1.输入两个数字实现两个数字相除,异常处理try...catch
提示:例如输入字符串,例如初始为0
2.输入两个数字实现两个数字相除,异常处理try...catch...finally
3.自定义异常 /年龄异常/性别异常
4. 输入1~3之间任一个数字,程序将输出相应的课程名称 如:Map<Integer,String> maps =...... maps.put(1,"java")输入正确,输出对应课程名称。 如果输入错误,给出错误提示,不管输入是否正确,均输出“欢迎提出建议”语句
5.今天线程基础案例,是否有数据安全性的问题?为什么?
6.使用匿名内部类方式创建及启动线程
7.为什么模拟List集合的线程安全的锁对象用this能确保同一个锁对象?请说明理由
晨考
1.异常传递处理有哪些方式?有什么区别?
答:方法实现处捕获--方法实现处后面可执行,方法调用处捕获--main方法后可执行。
2.进程与线程的区别
答:进程是指运行起来的程序,线程是指进程中单个的执行路径。
3.Thread 类中的start() 和 run() 方法有什么区别
答:start()方法是开始实现多线程的方法,run()方法是线程中运行的方法。
4.以下代码的调试结果为?
public class Bground extends Thread{
public static void main(String argv[]){
Bground b = new Bground();
b.run();
}
public void start(){
for (int i = 0; i <10; i++){
System.out.println("Value of i = " + i);
} } }
答案:C
A.编译错误,没有定义线程的run方法;
B.由于没有定义线程的run方法,而出现运行错误;
C. 编译通过,运行输出 values 0 to 9
D. 编译通过,运行无输出