目录
异常
线程
Timer类(定时器)
概念:Java中程序的一种错误
Java中异常机制:表示程序的某个错误,当错误发生的时候,将程序结束,提示在那个位置出现什么错误
Java中异常的体系结构:每种错误就是一个类
Throwable类:Java中的错误的父类
1)、Error:错误,不能用Java代码来处理错误
VirtualMachineError:虚拟机损坏的错误
StackOverflowError:堆栈溢出
2)、Exception:异常,需要使用Java代码来处理错误,异常又分为以下两种情况
编译时异常:编译之前必须处理的异常,强制处理的异常
ParseException:转换日期格式的异常
运行时异常:不强制处理的异常
NullPointerException:空指针异常(对象在堆里面还没有引用的时候,就直接调用方法) 注意:对象在调用方法或属性的时候需要做非空判断
StringIndexOutOfBoundsException:字符串下标越界的异常
注意:只要操作数组下标,必须要做合法性判断
ArrayIndexOutOfBoundsException:数组下标越界
注意:操作数组先做非空和空数组的判断
ArithmeticException:算术异常
NumberFormatException:数值的格式异常
ClassCastException:类型转化异常,对象向下转型的时候
IllegalArgumentException:非法参数的异常
区分编译时异常跟运行时异常
只要异常的父类是RuntimeException就是运行时异常,其他就是编译时异常
异常处理方式:
1)、甩锅式 直接在方法上抛出异常或者在方法里面直接抛出
throws是在方法的声明上面抛出异常 如:public static void main(String[] args) throws ParseException
在方法体内抛出异常,throw + 异常对象 (特点:可以自己设置条件判断,不满足则抛出异常) 等于是可以用来自定义异常
区别:如果是在方法声明上加s,方法体内不加s
提示:只要程序出现抛出的异常,JVM会创建相应对象,再去调用相应的方法来提示错误
2)、背锅式
语法:
try{
可能会出现的代码 例如:new SimpleDateFormat("").parse(""); // 这就是一个异常
}catch(异常类名 参数名) {
报这种异常错误信息的代码 例如:throw new RuntimeException(e);
注意:catch的顺序应该是先子后父
}finally {
处理异常的出口 作用:关闭资源 特点:无论如何都会执行它,除非前面写了System.exit(0); //终止程序
}
public class ExceptionDemo3 { // 声明一个方法 public static void m() throws ParseException { // 抛出异常,异常抛给调用者,thorws // 省略方法体 new SimpleDateFormat("yyyy-MM-dd").parse("2023/12/19"); } public static void main(String[] args) 方法1:throws ParseException { // 直接抛给JVM m(); // 调用方法就需要再次抛出异常 方法2: try { // 主动捕获 new SimpleDateFormat("").parse(""); }catch (ParseException e) { throw new RuntimeException(e); // 抛出异常 }public static void mmm(int i) { int length = String.valueOf(i).length(); // 将输入的数字转换成字符串,再求字符串长度 if(length != 3) { // 如果长度不等于3则抛出以下异常 throw new IllegalArgumentException("参数的位数不是一个三位数"); // IllegalArgumentException:非法参数的异常 } }
自定义异常:
作用:程序执行过程有不满条件情况都是异常,需要自己定义
语法:
1. 编译时异常:class 自定义的异常名字(Exception结尾) extends Exception { // 自定义编译时异常 书写两个构造方法 1. 无参 2. 一个有1个字符串参数的构造方法 } 2. 运行时异常:class 自定义异常的名字(Exception结尾) extends RuntimeException { // 自定义运行时异常 书写两个构造方法 1. 无参 2. 一个有1个字符串参数的构造方法 }
进程:一个应用软件就是一个进程,一大段程序(代码)每一个进程进行运行都有分配独立的内存空间
腾讯视频:进程A
爱奇艺:进程B
优酷:进程C
注意点:每个进程之间运行相互不干扰
线程:一个进程里面独立某个功能就是一个线程,一小段代码 (其实软件运行的就是线程)
一个进程里面至少有一个线程,又可以有多个线程
线程的运行也是独立的,每个线程也有独立的运行内存,这个内存是属于进程的内存
线程执行流程:
1)、启动线程
2)、线程进入待运行状态
3)、cpu会统计目前内存中有多少个待运行的线程
4)、将运行时间平均分配给每个待运行的线程
5)、哪个线程获得这个时间片,哪个线程就开始执行,直到时间片用完,线程就暂停
注意点:在cpu里面同一时间只能运行一个线程
为什么软件要设计多线程?
1)、提高性能
2)、提高响应性
3)、资源共享
4)、简化复杂性
5)、任务分解
注意点:多线程编程也引入了一些挑战,如竞态条件(Race Conditions)、死锁(Deadlocks)、数据共享与同步等问题,因此,在设计和实现多线程应用程序时,需要小心谨慎,使用适当的同步机制来确保线程安全性。
Java实现多线程的方式:
1)、继承Thread类,重写run( )方法
public class Play extends Thread{ // Thread的子类 @Override // 重写run()方法:线程会获得时间片结束后执行的代码 public void run() { System.out.println("播放") } } public class PlayTest { // 测试类 public static void main(String[] args) { // 1. 创建线程 Play p1 = new Play(); // 开启一个线程 Thread-0 Play p2 = new Play(); // 开启一个线程 Thread-1 Play p3 = new Play(); // 开启一个线程 Thread-2 // 现在就是多线程 // 2. 开启线程,调用Thread父类的 start()的方法 p1.start(); // 启动p1线程 p2.start(); // 启动p2线程 p3.start(); // 启动p3线程 } }
2)、实现Runnable接口,重写run( )方法
public class Down implements Runnable{ @Override // 重写run()方法:线程会获得时间片结束后执行的代码 public void run() { for (int i = 1; i <= 10 ; i++) { System.out.println("下载视频第" + i + "次"); } } public class DownTest { // 测试类 // 1、创建Runnable对象 由于Runnable接口不能够直接new,所以需要利用他的实现类来间接操作它,后面的Thread(d1)会将它向上转型 Down d1 = new Down(); // 2、创建线程对象 Thread t1 = new Thread(d1); // 创建第一个线程 // 这里使用了Thread(Runnable target) 根据传入Runnable对象创建线程,向上转型 Thread t2 = new Thread(d1); // 创建第二个线程 Thread t3 = new Thread(d1); // 创建第三个线程 // 3、启动线程 t1.start(); t2.start(); t3.start(); }
Thread类中常用的方法:
1)、常用字段(常量)
MAX_PRIORITY:线程可以拥有的最大优先级。
NORM_PRIORITY:分配给线程的默认优先级。
MIN_PRIORITY:线程可以拥有的最小优先级。
注意:1. 线程的优先级:1-10 2. 优先级越高获得时间片的概率越大 3. 线程默认优先级都是:5
System.out.println("最高优先级:"+ Thread.MAX_PRIORITY); // 打印10 System.out.println("默认优先级:"+ Thread.NORM_PRIORITY); // 打印5 System.out.println("最低优先级:"+ Thread.MIN_PRIORITY); // 打印1
2)、构造方法
Thread( ):创建默认名字的线程。
Thread(String name):创建一个指定名字的线程 // 后面用setName也可以修改
Thread(Runnable target) :根据传入Runnable对象创建线程,线程的名字是默认的
Thread(Runnable target, String name) :根据传入Runnable对象创建线程,线程的名字是自己来指定的
3)、常用方法
getName( ):获取线程名字
setName(String name) :设置线程名字 如果子类继承了thread,则可以直接使用方法名调用 这是一个非静态方法,jvm会自动生成一个this来表示子类对象
setPriority(int newPriority) :设置线程优先级
getPriority( ) :获取线程的优先级
setDaemon(boolean on):设置线程为守护线程 // 线程分为:前台线程,后台线程(守护线程)
isDaemon():判断线程是否位守护线程 // GC:垃圾回收机制 就是一个守护线程
setDaemon(boolean on) :将此线程标记为守护线程或用户线程。
sleep(long millis):设置线程休眠的时间
currentThread() :获取当前线程 // 类方法
线程的状态
1)、新建 new Play( );
2)、就绪 new Play( ).start( );
3)、运行 执行run方法的时候
4)、阻塞 sleep( )
5)、死亡(结束) run方法执行完毕
多线程的安全
线程的执行是靠cpu分配时间片
以下案例存在一票多卖:多个线程同时访问一个数据,同一个数据被访问多次,存在线程安全问题
线程安全问题是怎样发生:线程要执行的代码还没有全部执行完,另一个线程又开始执行
1)、处理解决这个线程安全问题:就是让线程排队 方法1:同步代码块
注意: 一个线程访问一个对象中的synchronized(获取锁的对象)同步代码块时,其它线程试图访问该对象的线程将被阻塞
synchronized (参数:引用类型,所有线程能共享的(static修饰的或者字节码对象)) { // 写在run方法里面的
这个位置是你写的代码
}
/* * 1.使用继承Thread类的方式来写这个卖票案例 */ public class Tickets extends Thread{ // 继承Thread类,继承是单继承 private static int count = 1; // 这里因为是继承关系,后面测试为了实现多线程就需要多创建对象,这样每个对象就都会有一个独立的count,所以必须要用static修饰,为了让每个对象共享一个count,防止一票多卖 结合后面测试类再合起来理解 public Tickets() { // 无参构造 } public Tickets(String name) { // 有参构造 super(name); // 调用父类的有参构造 } public void run() { // 重写run方法 for (int i = 0; i < 30; i++) { // 循环控制执行次数,共卖30次 synchronized (this.getClass()) { // 参数是获取对象的字节码对象,这样足以保证所有的对象都是共享一把锁,"获取锁的地方是唯一的" 这玩意儿不能写在循环外面了,否则会导致不同对象卖同一张票,原因是写在外面,当count=1的时候,有可能其他的线程已经进入了循环里面 if(count <= 30) { // 获取当前线程的名字 String name = Thread.currentThread().getName(); System.out.println(name + "窗口:卖出第" + count + "张票"); count++; try { Thread.sleep(100); // 设置线程休眠时间 } catch (InterruptedException e) { // 线程中断异常 系统强制抛出 throw new RuntimeException(e); } } } } } } // 测试类 public class TicketsTest { public static void main(String[] args) { // 这里创建了3个对象,如果上面的count不用static修饰,那么每个实例对象,都会有一个count,那就会造成一票多卖的情况 Tickets t1 = new Tickets("泷泽"); Tickets t2 = new Tickets("深田"); Tickets t3 = new Tickets("桃子"); // 开启线程 t1.start(); t2.start(); t3.start(); } } /* * 2. 使用实现接口的方式来完成卖票的案例 */ public class Tickets implements Runnable { // 实现Runable接口 private int count = 1; // 这里可以是实例变量,也可以是类变量,原因是后面只需要创建一个对象,他们共享这一个对象 public void run() { // 重写run方法 for (int i = 0; i < 30; i++) { // 循环控制执行次数,共卖30次 synchronized (this.getClass()) { if(count <= 30) { // 获取当前线程的名字 String name = Thread.currentThread().getName(); System.out.println(name + "窗口:卖出第" + count + "张票"); count++; try { Thread.sleep(100); // 设置线程休眠时间 } catch (InterruptedException e) { // 线程中断异常 系统强制抛出 throw new RuntimeException(e); } } } } } } // 测试类 public class TicketsTest { public static void main(String[] args) { // 1、创建Runnable对象 Tickets tickets = new Tickets(); // 2、创建线程 Thread t1 = new Thread(tickets, "泷泽"); // 将tickets对象传到thread的参数中,这里tickets对象的类型是Runable,所以会发生向上转型 Thread t2 = new Thread(tickets, "有菜"); // 因为tickets是Runable的实现类,相当于是利用了多态的特征 Thread t3 = new Thread(tickets, "一香"); // 如此一来,他们几个对象都属于了一个对象 // 3、启动线程 t1.start(); t2.start(); t3.start(); } }
方法2:同步方法
/* * 1.使用继承Thread类 2.使用实现接口的方式 */ // 使用同步方法 也就是将synchronized单独写在一个方法里,以下这个就是同步方法 public class Tickets implements Runnable { private int count = 1; // 可以是实例变量,也可以是类变量 public static synchronized void sale() { // 同步实例方法的锁默认是this,同步类方法的锁是当前类的字节码对象 if (count <= 30) { String name = Thread.currentThread().getName(); // 获取当前线程的名字 System.out.println(name + "窗口:卖出第" + count + "张票"); count++; } } @Override public void run() { // 重写run方法 for (int i = 0; i < 30; i++) { sale(); // 调用同步方法 try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } } } }
方法3:同步锁
/* 同步锁:ReentrantLock,lock():上锁,unlock():解锁 * 1.使用继承Thread类 2.使用实现接口的方式 都ok */ // 使用同步锁 public class Tickets3 extends Thread{ // 创建同步锁 private static ReentrantLock lock = new ReentrantLock(true); // 默认不带公平机制,加上true就带有公平机制,也就是大概率所以对象抢票的几率相同 这里由于是继承关系,后面测试的时候需要创建多个对象,所以为了防止锁不住,所以需要加static,private是为了防止外部类访问 private static int count = 1; public Tickets3() { } public Tickets3(String name) { super(name); } @Override public void run() { // 重写run方法 for (int i = 0; i < 30; i++) { // 上锁 lock.lock(); try { // 为了在后面写finally出口,因为finally必须跟try连用 if (count <= 30) { String name = Thread.currentThread().getName(); // 获取当前线程的名字 System.out.println(name + "窗口:卖出第" + count + "张票"); count++; } try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } }finally { // 解锁 lock.unlock(); } } } }
概念:在指定时间做指定事情,比如:闹钟
构造方法:
Timer( ) : 创建一个新的计时器
Timer( boolean isDaemon ) :创建一个新的定时器,其相关线程可以指定为 run as a daemon 。
Timer(String name):创建一个新的定时器,其相关线程具有指定的名称
Timer(String name, boolean isDaemon):创建一个新的定时器,其相关线程具有指定的名称,可以指定为 run as a daemon 。
常用方法:
schedule(TimerTask task, Date time) :在指定的时间安排指定的任务执行。
schedule(TimerTask task, long delay) 在指定的延迟之后安排指定的任务执行。
timer.schedule(sayHi,2000); // 2秒后执行定时任务,一次性任务
schedule(TimerTask task, Date firsttime, long period) :从指定的时间开始 ,对指定的任务执行重复的固定延迟执行 。 不支持过去的时间
timer.schedule(sayHi,date,3000); // 从当前的时间开始,每隔3秒执行一次
schedule(TimerTask task, long delay, long period) 在指定的延迟之后开始 ,重新执行固定延迟执行的指定任务。
timer.schedule(sayHi,2000,3000); // 周期性的任务
scheduleAtFixedRate(TimerTask task, Date firstTime, long period) 从指定的时间开始 ,对指定的任务执行重复的固定速率执行 。
timer.scheduleAtFixedRate(sayHi,date,3000);
scheduleAtFixedRate(TimerTask task, long delay, long period) 在指定的延迟之后开始 ,重新执行固定速率的指定任务。
作用:1、定时发邮件 2、定时备份 3、定时写日志等
TimerTask:定时任务,另外一个线程 (public abstract class TimerTask extends Object implements Runnable)