
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条)