2022/1/21
1.异常
程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止。
在Java等面向对象的编程语言中,异常本身是一个类,产生异常就是创建异常对象并抛出了一个异常对象。Java处理异常的方式是中断处理。
异常并不是指语法错误,语法错了,编译不通过,不会产生字节码文件,根本不能运行。
2.异常体系
异常机制其实是帮助我们找到程序中的问题,异常的根类是java.lang.Throwable,其下有两个子类:java.lang.Eror与java.lang.Exception.
Error:是指出现了错误。(很严重)
Exception:是指出现了异常。(解决了异常程序就可以正常运行)
3.异常产生的过程解析
1)访问数组中没有的索引,这时候JVM就会检测出程序会出现异常
JVM会做两件事情:
<1>JVM会根据异常产生的原因创建一个异常对象,这个异常对象包含了异常产生的(内容,
原因,位置)
<2>在出错的那个方法中,没有异常处理逻辑(try...catch),那么JVM就会把异常对象抛出
给方法的调用者main方法来处理这个异常。
2)main方法接受到了这个异常对象,main方法也没有异常处理逻辑,继续把对象抛出给main方法的调用者JVM处理
3)JVM接收到了这个对象,又做了两件事
<1>把异常对象(内容,原因,位置)以红色的字体打印在控制台
<2>JVM会终止当前正在执行的Java程序-->中断处理
4.throw关键字
package ln.javatest.day12.demo03; public class Demo01Throw { public static void main(String[] args) { //int[] arr = null; int[] arr = {1,2,3}; int a = getElement(arr,3); System.out.println(a); } public static int getElement(int[] arr,int index){ if(arr == null){ throw new NullPointerException("传递的数组的值是null"); } if(index<0 || index>arr.length-1){ throw new ArrayIndexOutOfBoundsException("传递的索引超出了数组的使用范围"); } int ele = arr[index]; return ele; } }
5.Objects非空判断,requireNonNull
package ln.javatest.day12.demo03; import java.util.Objects; public class Demo02Objects { public static void main(String[] args) { method(null); } private static void method(Object obj) { //对传递过来的参数进行合法性判断,判断是否为null //Objects.requireNonNull(obj); Objects.requireNonNull(obj,"传递的对象的值是null"); //上面三个效果一样 } }
6.异常处理的第一种方式:throws关键字,交给别人处理
package ln.javatest.day12.demo03; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.FileAlreadyExistsException; public class Demo03Throws { public static void main(String[] args)throws IOException{ readFile("c:\a.txt"); } public static void readFile(String fileName) throws IOException{ if(!fileName.equals("c:\a.txt")){ throw new FileNotFoundException("传递的文件路径不是c:\a.txt"); } if(!fileName.endsWith(".txt")){ throw new IOException("文件的后缀名不对"); } System.out.println("路径没有问题,读取文件"); } }
7.异常处理的第二种方式:try...catch,自己处理
8.Throwable类中的3个异常处理方法
使用throws处理异常后,JVM直接中断程序,后续代码不再运行
try...catch可以实现后续代码接着运行
package ln.javatest.day12.demo03; import java.io.IOException; public class Demo04TryCatch { public static void main(String[] args) { try { readFile("d:\a.tx"); }catch(IOException e){ //try中抛出什么异常,catch就定义什么异常变量,用来接收这个异常对象 //System.out.println("catch- 传递的文件后缀不是.txt"); System.out.println(e.getMessage()); } System.out.println("h后续代码"); } public static void readFile(String fileName) throws IOException { if(!fileName.endsWith(".txt")){ throw new IOException("文件的后缀名不对"); } System.out.println("路径没有问题,读取文件"); } }
9.finally代码块
package ln.javatest.day12.demo03; import java.io.IOException; public class Demo05TryCatchFinally { public static void main(String[] args) { try { readFile("d:\a.tx"); }catch(IOException e){ //try中抛出什么异常,catch就定义什么异常变量,用来接收这个异常对象 //System.out.println("catch- 传递的文件后缀不是.txt"); System.out.println(e.getMessage()); }finally { System.out.println("资源释放");//无论程序是否出现异常,都要执行的 } System.out.println("h后续代码"); } public static void readFile(String fileName) throws IOException { if(!fileName.endsWith(".txt")){ throw new IOException("文件的后缀名不对"); } System.out.println("路径没有问题,读取文件"); } }
10.多个异常使用捕获如何处理
1.多个异常分别处理(一个try一个异常,一个catch一个处理)
2.多个异常一次捕获,多次处理(一个try多个异常,多个catch多个处理)
3.多个异常一次捕获一次处理(一个try多个异常,一个catch多次处理)
注意:
1)这种异常的处理方式,要求多个catch中的异常不能相同,并且若catch中的多个异常之间有子父类异常的关系,那么子类异常要求在上面的catch处理,父类异常在下面的cat吃处理。
2)运行时异常被抛出可以不处理。即不捕获也不声明抛出
3)如果finally有return语句,永远返回finally中的结果,所以要避免这种情况。
4)子父类的异常
1>如果父类抛出了多个异常,子类覆盖父类方法时,只能抛出相同的异常或者是他的子集
或者不抛出异常
2> 如果父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常。此时子类产生该
异常,只能捕获处理(try...catch),不能声明抛出
注意: 父类异常是什么样,子类异常就什么样
11.自定义异常类
package ln.javatest.day12.demo03; public class RegisterException extends Exception{ //添加一个空参数的构造方法 public RegisterException(){ super(); } //添加一个带异常信息的构造方法 //查看源码发现,所有的异常类都会有一个带异常信息的构造方法,方法内部会调用父类带异常信息的构造方法,让父类来处理这个异常 public RegisterException(String message){ super(message); } }
12.练习
package ln.javatest.day12.demo03; public class RegisterException extends RuntimeException{ //添加一个空参数的构造方法 public RegisterException(){ super(); } //添加一个带异常信息的构造方法 //查看源码发现,所有的异常类都会有一个带异常信息的构造方法,方法内部会调用父类带异常信息的构造方法,让父类来处理这个异常 public RegisterException(String message){ super(message); } }
package ln.javatest.day12.demo03; import java.util.Scanner; public class Demo01RegisterException { //1.使用数组保存已经注册过的用户名(数据库) static String[] usernames = {"张三","李四","王五"}; public static void main(String[] args) { // 2.使用Scanner获取用户输入的注册的用户名(前端,页面) Scanner sc = new Scanner(System.in); System.out.println("请输入您要注册的用户名:"); String username = sc.next(); checkUsername(username); } //3.定义一个方法,对用户输入的用户名进行判断 public static void checkUsername(String username) { // 遍历存储已经注册过用户名的数组,获取每一个用户名 for (String name : usernames) { //使用获取到的用户名和用户输入的用户名比较 if(name.equals(username)){ // true:用户名已经存在,抛出RegisterException异常,告知用户“亲,该用户名已经被注册” throw new RegisterException("亲,该用户名已经被注册"); } } //如果循环结束了,还没有找到重复的用户名,提示用户“恭喜您,注册成功!” System.out.println("恭喜您,注册成功!"); } }
13.并发与并行
并发:CPU在多个任务见交替执行。(指两个或多个时间在同一个时间段内发生)
并行:CPU分成多个对多个任务同时执行。(指两个或多个事件在同一时刻发生)
并行比并发执行速度快
14.进程:
进入到内存的程序叫进程;
进程也是程序的一次执行过程,是系统运行程序的基本单位;
15.线程:
是进程中的一个执行单元,负责当前进程中程序的执行;
一个进程中至少有一个线程;
一个进程中也可以有多个线程,这个应用程序称为多线程程序。
eg.腾讯电脑管家,功能病毒查杀,清理垃圾等
打开腾讯管家应用软件,进入到cpu执行(进程)
点击功能(病毒查杀,清理垃圾,电脑加速)执行
就会开启一条应用程序到Cpu的执行路径
cpu就可以通过这个路径执行功能
这个路径就叫做线程
16.线程的调度
1)分时调度:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间。
2)抢占式调度:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
17.主线程
单线程程序:java程序中只有一个线程(执行从main方法开始,从上到下依次执行)
JVM执行main方法:
main方法会进入到栈内存
JVM会找 *** 作系统开辟一条main方法通向cpu的执行路径
cpu就可以通过这个路径来执行main方法
而这个路径有一个名字,叫main(主)线程
特点:单线程的弊端,程序执行过程中出现问题后,后面的都不能运行了
18.创建多线程程序的第一种方式:创建Thread类的子类
package ln.javatest.day12.demo03; // 1.创建一个Thread类的子类 public class MyThread extends Thread { // 2.在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么) @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("run"+i); } } }
package ln.javatest.day12.demo03; public class Demo06Thread { public static void main(String[] args) { // 3.创建Thread类的子类对象 MyThread mt = new MyThread(); //4.调用Thread类中的方法,开启新的线程,执行run方法 mt.start(); for (int i = 0; i < 5; i++) { System.out.println("main"+i); } } }
19.多线程原理
1>JVM执行main方法,找OS开辟一条main方法通向cpu的路径
这个路径叫main线程,主线程
cpu通过这个线程,这个路径可以执行main方法
2>new MYThread(创建了一个Thread类的子类对象):
开辟了一条通向cpu的新路径,用来执行run方法
3>对于cpu而言,就有了两条执行的路径
cpu就有了选择权,想执行哪个就执行哪个,还可能交替执行(这就是程序的随机打印结果)
两个线程一起抢夺cpu的执行时间
注意:
在main方法中,如果直接用Thread子类对象调用run方法,则不单独开辟一个线程,而是在和main方法的同一个栈中运行,那么就不是多线程了。
必须用Thread子类对象调用start方法,才是开辟新的线程(栈空间),不同栈运行,多个栈多个线程,共享一个cpu。
20.获取线程名称
package ln.javatest.day12.demo03; // 1.创建一个Thread类的子类 public class MyThread extends Thread { // 2.在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么) @Override public void run() { String name = getName(); // System.out.println(name); //currentThread是个静态方法,可直接用类名调用 String t = Thread.currentThread().getName(); System.out.println(t); } }
package ln.javatest.day12.demo03; public class Demo06Thread { public static void main(String[] args) { // 3.创建Thread类的子类对象 MyThread mt = new MyThread(); //4.调用Thread类中的方法,开启新的线程,执行run方法 mt.start(); //获取main线程的线程名 System.out.println(Thread.currentThread().getName()); } }
21.设置线程名称
package ln.javatest.day12.demo03; // 1.创建一个Thread类的子类 public class MyThread extends Thread { // 2.创建一个带参数的构造方法,参数传递线程的名称,调用父类的带参构造方法,把线程名称传递给父类 public MyThread(){ } public MyThread(String name){ super(name); } @Override public void run() { System.out.println(Thread.currentThread().getName()); } }
package ln.javatest.day12.demo03; public class Demo06Thread { public static void main(String[] args) { // 创建Thread类的子类对象 MyThread mt = new MyThread(); // 1.使用Thread类中的方法setName(名字) mt.setName("王嘉尔"); //调用Thread类中的方法,开启新的线程,执行run方法 mt.start(); //调用2带参构造方法 new MyThread("易烊千玺"); } }
22.Thread类的sleep方法
public static void sleep(long millis):让当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)
毫秒数结束之后,线程继续执行package ln.javatest.day12.demo03; public class Demo07Sleep { public static void main(String[] args) { //模拟秒表 for (int i = 0; i < 60; i++) { System.out.println(i); } //因为上面运行速度过快,为了模拟钟表一秒钟一次 //可使用Thread类的sleep方法让程序睡眠1秒钟 try { Thread.sleep(1000);//1000毫秒=1秒 } catch (InterruptedException e) { e.printStackTrace(); } } }
23.开启多线程的第二种方式
package ln.javatest.day12.demo04; public class Demo01Runnable { public static void main(String[] args) { // 3.创建一个Runnable接口的实现类对象 Demo01Runnableimpl i = new Demo01Runnableimpl(); // 4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象 Thread t = new Thread(i); //5.调用Thread类中的start方法,开启新的线程执行run方法 t.start(); for (int j = 0; j < 5; j++) { System.out.println(Thread.currentThread().getName() + "-->" + j); } } }
package ln.javatest.day12.demo04; //1.创建一个Runnable接口的实现类 public class Demo01Runnableimpl implements Runnable{ //2.在实现类中重写Runnable接口的run方法,设置线程任务 @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + "-->" + i); } } }
24.Thread和Runnable的区别
实现Runnable接口创建多线程程序的好处:
1.避免了单继承的局限性
一个类只能继承一个类,类继承了Thread类就不能继承其他的类
实现Runnable接口,还可以继承其他的类,实现其他的接口
2.增强了程序的可扩展性,降低了程序的耦合性(解耦)
实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)
实现类中,重写了run方法:用来设置线程任务
创建Thread类对象,调用start方法,来开启新线程
25.匿名内部类实现线程的创建
package ln.javatest.day12.demo04; public class Demo01InnerClassThread { public static void main(String[] args) { //线程的父类是Thread //new MyThread().start new Thread(){ @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + i); } } }.start(); //线程接口 //Runnable r = new RunnableImpl();多态 //Runnable r = new Runnable //new Thread(r).start(); new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + i); } } }).start(); } }
26.线程安全问题
多线程访问了共享的数据,才会产生线程安全问题.
不是共享的数据,不会出现线程安全问题。
package ln.javatest.day12.demo04; public class Demo01Ticket { public static void main(String[] args) { //创建Runnable接口的实现类对象 RunnableImpl i = new RunnableImpl(); //创建Thread类对象,构造方法中传递Runnable接口的实现类对象 //共享同一个实现类的对象中的对象都调用同一个实现类对象 Thread t = new Thread(i); Thread t1 = new Thread(i); Thread t2 = new Thread(i); t.start(); t1.start(); t2.start(); } }
package ln.javatest.day12.demo04; public class RunnableImpl implements Runnable{ //定义一个多个线程共享的票源 private int ticket = 100; //设置线程任务 @Override public void run() { //先判断票是否存在 //用死循环让卖票 *** 作重复执行 while(true){ if(ticket > 0){ //票存在,卖票 System.out.println(Thread.currentThread().getName()+"--->正在卖第"+ ticket +"张票"); ticket--; } } } }
27.注意:
线程安全问题是不能产生的,我们可以让一个线程在访问共享数据的时候,无论是否失去了cpu的执行权限,让其他的线程只能等待,等待当前线程做完,其它线程再进行执行
28.同步代码块解决共享数据的安全问题
package ln.javatest.day12.demo04; public class RunnableImpl implements Runnable{ //定义一个多个线程共享的票源 private int ticket = 100; //创建一个锁对象 Object obj = new Object(); //设置线程任务 @Override public void run() { //先判断票是否存在 //用死循环让卖票 *** 作重复执行 while(true){ synchronized(obj){ if(ticket > 0){ //票存在,卖票 System.out.println(Thread.currentThread().getName()+"--->正在卖第"+ ticket +"张票"); ticket--; } } } } }
package ln.javatest.day12.demo04; public class Demo01Ticket { public static void main(String[] args) { //创建Runnable接口的实现类对象 RunnableImpl i = new RunnableImpl(); //创建Thread类对象,构造方法中传递Runnable接口的实现类对象 //共享同一个实现类的对象中的对象都调用同一个实现类对象 Thread t = new Thread(i); Thread t1 = new Thread(i); Thread t2 = new Thread(i); t.start(); t1.start(); t2.start(); } }
29.同步技术的原理:使用了一个锁对象,这个锁对象叫同步锁,也叫对象监视器
3个线程一起抢夺cpu,谁抢到就先执行run方法,遇到synchronized代码表,这时候当前线程会检查synchronized代码块是否有锁对象,发现有,这时候会获取到锁对象,进入到同步中执行
这时候第二个线程抢到了cpu,执行run方法,遇到synchronized代码表,检查synchronized代码块是否有锁对象,发现没有,第二个线程就进入到阻塞状态,等待上一个线程归还锁对象,第二个对象才能进入到同步中执行。
总结:同步中的线程,没有执行完毕不会释放锁,同步外的线程没有锁进不去同步内
同步保证了只能有一个线程在同步中执行共享数据
保证了安全
程序频繁的判断锁,获取锁,释放锁,程序的效率会降低
30..同步方法、静态同步方法来解决共享数据的安全问题
package ln.javatest.day12.demo04; public class RunnableImpl implements Runnable { //定义一个多个线程共享的票源 private static int ticket = 100; //创建一个锁对象 Object obj = new Object(); //设置线程任务 @Override public void run() { //先判断票是否存在 //用死循环让卖票 *** 作重复执行 while (true) { payTicketstatic(); } } //和上面效果一样 public static void payTicketstatic() { synchronized (RunnableImpl.class) { if (ticket > 0) { //票存在,卖票 System.out.println(Thread.currentThread().getName() + "--->正在卖第" + ticket + "张票"); ticket--; } } } }
package ln.javatest.day12.demo04; public class Demo01Ticket { public static void main(String[] args) { //创建Runnable接口的实现类对象 RunnableImpl i = new RunnableImpl(); //创建Thread类对象,构造方法中传递Runnable接口的实现类对象 //共享同一个实现类的对象中的对象都调用同一个实现类对象 Thread t = new Thread(i); Thread t1 = new Thread(i); Thread t2 = new Thread(i); t.start(); t1.start(); t2.start(); } }
31.Lock锁解决线程安全的问题
package ln.javatest.day12.demo04; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class RunnableImpl implements Runnable { //定义一个多个线程共享的票源 private static int ticket = 100; //1.在成员位置创建一个ReentrantLock对象 Lock l = new ReentrantLock(); //设置线程任务 //下面这个和上面那个实现效果一样,不同在于,下面这个无论程序是否出现异常,锁对象都会被释放掉,提高程序效率 @Override public void run() { //先判断票是否存在 //用死循环让卖票 *** 作重复执行 while (true) { //2.在可能会出现安全问题的代码前调用Lock接口中的方法locks获取锁 l.lock(); if (ticket > 0) { //提高安全问题出现的概率,让程序睡眠 try { Thread.sleep(10); //票存在,卖票 System.out.println(Thread.currentThread().getName() + "--->正在卖第" + ticket + "张票"); ticket--; } catch (InterruptedException e) { e.printStackTrace(); }finally { //3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁 l.unlock(); } } } } }
32.线程的状态
当线程被创建并启动之后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。
java.lang.Thread.State这个枚举中给出了六种线程状态:
NEW(新建),Runnable(可运行),Blocked(锁阻塞),Waiting(无限等待),Timed Waiting(计时等待),Teminated(死亡状态,运行期间出现问题)
33.等待缓唤醒案例
package ln.javatest.day12.demo04; public class Demo01Wait { public static void main(String[] args) { //创建锁对象,保证唯一 Object obj = new Object(); //创建一个顾客线程(消费者)使用匿名内部类 new Thread(){ @Override public void run() { //保证等待和唤醒的线程只能有一个执行,需要使用同步技术 synchronized (obj){ System.out.println("告知老板要的包子的种类和数量:"); //调用wait方法,放弃cpu的执行,进入到WAITING状态 try { obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } //唤醒之后执行的代码 System.out.println("准备吃包子啦!"); } } }.start(); //创建一个老板线程(生产者) new Thread(){ @Override public void run() { //花了5秒做包子 try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } //保证等待和唤醒的线程只能有一个执行,需要使用同步技术 synchronized (obj){ System.out.println("老板5秒钟做好了包子."); //唤醒顾客 obj.notify(); } } }.start(); } }
进入到TimeWaiting(计时等待)有两种方式:
1.使用sleep(long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态
2.使用wait(long m)方法,wait方法如果在毫秒值结束之后,还没有被notfity唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态唤醒的方法:
void notify() : 唤醒在此对象监视器上等待的单个线程。(如果有多个,则随机唤醒一个)
void notifyAll() : 唤醒在此对象监视器上等待的所有线程。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)