异常与线程基础

一、异常

异常传递

在方法实现中出现的异常,如果在实现中没有处理,那么会往调用处传递

换句话说,可以在方法实现处或方法调用处进行异常处理(捕获)

运行时异常传递

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. 编译通过,运行无输出