1. 基础回顾+面试
1.1 异常
- Java中异常继承的根类是:
Throwable
Exception异常的分类:
- 编译时异常:继承自
Exception
的异常或者其子类,编译阶段就会报错 运行时异常:继承自
RuntimeException
的异常或者其子类,编译阶段不报错,但是运行阶段报错1.2 常见的运行时异常
面试:写出几个运行时异常的例子!
- 数组索引越界异常:
ArrayIndexOutOfBoundsException
- 空指针异常:
NullPointerException
- 直接输出没有问题,但是调用空指针的变量的功能就会报错!
- 类型转换异常:
ClassCastException
- 迭代器遍历没有此元素异常:
NoSuchElementException
- 数学操作异常:
ArithmeticException
数学转换异常:
NumberFormatException
```java public class ExceptionDemo { public static void main(String[] args) {System.out.println("程序开始。。。。。。");
/** 1.数组索引越界异常: ArrayIndexOutOfBoundsException。*/
int[] arrs = {10 ,20 ,30};
System.out.println(arrs[2]);
// System.out.println(arrs[3]); // 此处出现了数组索引越界异常。代码在此处直接执行死亡!
/** 2.空指针异常 : NullPointerException。直接输出没有问题。但是调用空指针的变量的功能就会报错!! */
String name = null ;
System.out.println(name); // 直接输出没有问题
// System.out.println(name.length()); // 此处出现了空指针异常。代码在此处直接执行死亡!
/** 3.类型转换异常:ClassCastException。 */
Object o = "齐天大圣";
//Integer s = (Integer) o; // 此处出现了类型转换异常。代码在此处直接执行死亡!
/** 5.数学操作异常:ArithmeticException。 */
// int c = 10 / 0 ; // 此处出现了数学操作异常。代码在此处直接执行死亡!
/** 6.数字转换异常: NumberFormatException。 */
String num = "23aa";
Integer it = Integer.valueOf(num); // 此处出现了数字转换异常。代码在此处直接执行死亡!
System.out.println(it+1);
System.out.println("程序结束。。。。。。");
}
}
<a name="pGqXu"></a>
## 1.3 异常的产生处理默认机制
异常的产生默认的处理过程解析(了解即可):
1. 默认会在出现异常的代码那里自动的创建一个异常对象:ArithmeticException
1. 异常会从方法中出现的点这里抛出给调用者,调用者最终抛出给JVM虚拟机
1. 虚拟机接收到异常对象后,现在控制台直接输出异常栈信息数据。
1. 直接从当前执行的异常点干掉当前程序。
1. 后续代码没有机会执行了,因为程序已经死亡
默认的异常处理机制并不好,一旦真的出现异常,程序立即死亡
<a name="GDeoA"></a>
## 1.3编译时异常的处理方式
> 方式一:抛出异常
**格式**:
方法 throws Exception{
} • 1 • 2 • 3
- 在出现编译时异常的地方层层把异常抛出去给调用者,调用者最终抛出给JVM虚拟机,JVM虚拟机输出异常信息,直接干掉程序,这种方式与默认方式是一样的。虽然可以解决代码编译时的错误,但是一旦运行时真的出现异常,程序还是会立即死亡,这种方式并不好
> 方式二:捕获处理:在出现异常的地方自己处理,谁出现谁处理
格式:
try{ // 监视可能出现异常的代码 }catch{异常类型1 变量}{ // 处理异常 }catch{异常类型2 变量}{ // 处理异常 } • 1 • 2 • 3 • 4 • 5 • 6 • 7
- 第二中方式,可以处理异常,并且出现异常后代码也不会死亡,但是从理论上来说,这种方式不是最好的,上层调用者不能直接知道底层的执行情况!
> 方式三:在出现异常的地方把异常一层一层的抛出给最外层调用者,最外层调用者集中捕获处理
try{ // 可能出现异常的代码 }catch(Exception e){ e.printStackTrae(); //直接打印异常栈信息 } • 1 • 2 • 3 • 4 • 5
- 这种方案最外层调用者可以知道底层执行的情况,同时程序在出现异常后也不会立即死亡,这是理论上最好的方案。
- 虽然异常有三种处理方式,但是开发中只要能解决你的问题,每种方式都可能用到!
<a name="rI3ve"></a>
## 1.4finally关键字
- 用在捕获处理的异常格式中,放在最后面
try{ //可能出现异常的代码! }catch{Exception e}{ e.printStackTrace(); }finally{ // 无论代码是出现异常还是正常执行,最终一定要执行这里的代码!! } • 1 • 2 • 3 • 4 • 5 • 6 • 7
- `finally`的作用:可以在代码执行完毕后进行资源的释放操作
- 资源都是实现了`Closeable`接口的,都自带`close()`关闭方法
- try : 出现1次
- catch:出现0 - N 次(如果有finally那么 catch 可以没有)
- finally:出现0 - 1 次
<a name="DOVZ9"></a>
## 1.5自定义异常(了解)
Java已经为开发中可能出现的异常都设计了一个类来代表,但是在实际开发中,异常可能有无数中情况,Java无法为这个世界上所有的异常都定义了一个类。假如一个企业如果想为自己认为的某种业务问题定义成一个异常,就需要自己来自定义异常类。
<a name="35O5Y"></a>
## 1.4多线程(并发编程)
1. **什么是进程?**<br />答:程序是静止的,运行中的程序就是进程
1. **进程的三个特征**
- 独立性:进程与进程之间是相互独立的,彼此有自己独立内存区域
- 动态性:进程是运行中的程序,要动态的占用内存,CPU和网络等资源
- 并发性:CPU会分时轮询切换依次为每个进程服务,因为切换的速度非常快,给我们的感觉像是在同时执行,这就是并发性
- 并发:同一时刻同时有多个在执行
3. **什么是线程?**<br />答:线程是属于进程的,一个进程可以包含多个线程,这就是多线程
<a name="rWa8w"></a>
## 1.5线程的创建方式
多线程是很有用的,我们在进程中创建线程的方式有三种:
<a name="FDho7"></a>
### 1.5.1继承Thread类
继承Thread类的方式:
1. 定义一个线程类继承`Thread`类
1. 重写`run()`方法
1. 创建一个**新的线程对象**
Thread t = new MyThread(); • 1
1. 调用线程对象的`start()`方法启动线程
public class ThreadDemo { // 启动后的ThreadDemo当成一个进程。 // main方法是由主线程执行的,理解成main方法就是一个主线程 public static void main(String[] args) { // 3.创建一个线程对象 Thread t = new MyThread(); // 4.调用线程对象的start()方法启动线程,最终还是执行run()方法! t.start();
for(int i = 0 ; i < 100 ; i++ ){
System.out.println("main线程输出:"+i);
}
}
} // 1.定义一个线程类继承Thread类。 class MyThread extends Thread{ // 2.重写run()方法 @Override public void run() { // 线程的执行方法。 for(int i = 0 ; i < 100 ; i++ ){ System.out.println(“子线程输出:”+i); } } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17 • 18 • 19 • 20 • 21 • 22 • 23 • 24 • 25 • 26 • 27
<a name="w24en"></a>
### 1.5.2实现Runnable接口
1. 创建一个线程任务类实现`Runnable`接口
1. 重写`run()`方法
1. 创建一个**线程任务对象**(注意:线程任务对象不是线程对象,只是执行线程的任务的)
Runnable target = new MyRunnable(); • 1
1. 把线程任务对象包装成线程对象,且可以指定线程名称
// Thread t = new Thread(target); Thread t = new Thread(target,”1号线程”); • 1 • 2
1. 调用线程对象的`start()`方法启动线程
public class ThreadDemo { public static void main(String[] args) { // 3.创建一个线程任务对象(注意:线程任务对象不是线程对象,只是执行线程的任务的) Runnable target = new MyRunnable(); // 4.把线程任务对象包装成线程对象.且可以指定线程名称 // Thread t = new Thread(target); Thread t = new Thread(target,”1号线程”); // 5.调用线程对象的start()方法启动线程 t.start(); Thread t2 = new Thread(target); // 调用线程对象的start()方法启动线程 t2.start(); for(int i = 0 ; i < 10 ; i++ ){ System.out.println(Thread.currentThread().getName()+”==>”+i); } } } // 1.创建一个线程任务类实现Runnable接口。 class MyRunnable implements Runnable{ // 2.重写run()方法 @Override public void run() { for(int i = 0 ; i < 10 ; i++ ){ System.out.println(Thread.currentThread().getName()+”==>”+i); } } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17 • 18 • 19 • 20 • 21 • 22 • 23 • 24 • 25 • 26 • 27 • 28 • 29 • 30
<a name="K6DWD"></a>
#### 1.5.2.1Thread的构造器
- `public Thread(){}`
- `public Thread(String name){}`
- `public Thread(Runnable target){}`:分配一个新的Thread对象
- `public Thread(Runnable target,String name)`:分配一个新的Thread对象,且可以指定新的线程名称
<a name="Qoo9D"></a>
#### 1.5.2.1优缺点
缺点:代码复杂一点<br />优点:
- 线程任务类只是实现了`Runnable`接口,可以继续继承其他类,而且可以继续实现其他接口(避免乐单继承的局限性)
- 同一个线程任务对象可以被包装成多个线程对象
<a name="TyCrS"></a>
### 1.5.3实现Callable接口
1. 定义一个线程任务类实现`Callable`接口,申明线程返回的结果类型
1. 重写线程任务类的`call`方法,这个方法可以直接返回执行的结果
1. 创建一个`Callable`的线程任务对象
1. 把`Callable`的线程任务对象包装成一个未来任务对象
1. 把未来任务对象包装成线程对象
1. 调用线程的`start()`方法启动线程
public class ThreadDemo {
public static void main(String[] args) {
// 3.创建一个Callable的线程任务对象
Callable call = new MyCallable();
// 4.把Callable任务对象包装成一个未来任务对象
// — public FutureTask(Callable
<a name="InY9x"></a>
#### 1.5.4优劣点
优点:全是优点
<a name="CtKZ1"></a>
## 1.6线程的常用API
Thread 类的 API
1. `public void setName(String name)`: 给当前线程取名字
1. `public void getName()`: 获取当前线程的名字
- 线程存在默认名称,子线程的默认名称是:Thread - 索引
- 主线程的默认名称是:main
3. `public static Thread currentThread()`: 获取当前线程对象,这个代码在哪个线程中,就得到哪个线程对象
3. `public static void sleep(long time)`:让当前线程休眠多少毫秒再继续执行
3. `public Thread(String name)`:创建对象并取名字
---
public class ThreadDemo { // 启动后的ThreadDemo当成一个进程。 // main方法是由主线程执行的,理解成main方法就是一个主线程 public static void main(String[] args) { // 创建一个线程对象 Thread t1 = new MyThread(); t1.setName(“1号线程”); t1.start(); //System.out.println(t1.getName()); // 获取线程名称 Thread t2 = new MyThread(); t2.setName(“2号线程”); t2.start(); //System.out.println(t2.getName()); // 获取线程名称 // 主线程的名称如何获取呢? // 这个代码在哪个线程中,就得到哪个线程对象。 Thread m = Thread.currentThread(); m.setName(“最强线程main”); //System.out.println(m.getName()); // 获取线程名称 for(int i = 0 ; i < 10 ; i++ ){ System.out.println(m.getName()+”==>”+i); } } } // 1.定义一个线程类继承Thread类。 class MyThread extends Thread{ // 2.重写run()方法 @Override public void run() { // 线程的执行方法。 for(int i = 0 ; i < 10 ; i++ ){ System.out.println(Thread.currentThread().getName()+”==>”+i); } } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17 • 18 • 19 • 20 • 21 • 22 • 23 • 24 • 25 • 26 • 27 • 28 • 29 • 30 • 31 • 32 • 33 • 34 • 35 • 36 • 37 • 38
---
线程休眠api
public class ThreadDemo02 { public static void main(String[] args) { for(int i = 0 ; i < 10 ; i++ ) { System.out.println(i); try { // 项目经理让我加上这行代码 // 如果用户交钱了,我就去掉。 Thread.sleep(1000); // 让当前线程休眠1s. } catch (Exception e) { e.printStackTrace(); } } } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14
---
通过Thread类的有参构造器为当前线程对象取名字
public class ThreadDemo03 { // 启动这个类,这个类就是进程,它自带一个主线程, // 是main方法,main就是一个主线程的执行!! public static void main(String[] args) { Thread t1 = new MyThread02(“1号线程”); t1.start(); Thread t2 = new MyThread02(“2号线程”); t2.start(); Thread.currentThread().setName(“主线程”); for(int i = 0 ; i < 10 ; i++ ) { System.out.println(Thread.currentThread().getName()+” => “+i); } } } // 1.定义一个线程类继承Thread。线程类并不是线程对象,用来创建线程对象的。 class MyThread02 extends Thread{ public MyThread02(String name) { // public Thread(String name):父类的有参数构造器 super(name); // 调用父类的有参数构造器初始化当前线程对象的名称! } // 2.重写run()方法 @Override public void run() { for(int i = 0 ; i < 10 ; i++ ) { System.out.println(Thread.currentThread().getName()+” => “+i); } } } • 1 • 2 • 3 • 4 • 5 • 6 • 7 • 8 • 9 • 10 • 11 • 12 • 13 • 14 • 15 • 16 • 17 • 18 • 19 • 20 • 21 • 22 • 23 • 24 • 25 • 26 • 27 • 28 • 29 • 30 • 31 • 32 • 33
<a name="Qb4Ak"></a>
## 1.7线程安全
线程安全问题:多个线程同时操作同一个共享资源的时候可能会出现线程安全问题
<a name="wF3LG"></a>
## 1.7线程同步_同步代码块
- **线程同步的作用**:就是为了解决线程安全问题,让多个线程实现先后依次访问共享资源,这样就解决了安全问题
- **线程安全**:多个线程同时操作同一个共享资源的时候可能会出现线程安全问题
- **线程同步的做法**:加锁(就是把共享资源进行上锁,每次只能一个线程进入访问完毕以后,其他线程才能进来)
- **线程同步的方法**:
- 同步代码块
- 同步方法
- `lock` 显示锁
**同步代码块作用**:是把出现线程安全问题的核心代码给上锁,每次只能一个线程进入,执行完毕之后自动解锁,其他线程才可以进来执行
// 格式
synchronized(锁对象){
// 访问共享资源的核心代码
}
• 1
• 2
• 3
• 4
- 在实例方法中建议用`this`作为锁对象
- 在静态方法中建议用`类名.class`字节码作为锁对象
<a name="f9fds"></a>
## 1.8线程同步_同步方法
**作用**:把出现线程安全问题的核心方法给锁起来,每次只能一个线程进入访问,其他线程必须在方法外面等待<br />**用法**:直接给**方法**加上一个修饰符 `synchronized`
public synchronized void 方法名(){
} • 1 • 2 • 3
原理:同步方法的原理和同步代码块的底层原理其实是完全一样的,只是同步方法是把整个方法的代码都锁起来,同步方法其实底层也有锁对象的。
- 如果方法是实例方法:同步方法默认用 `this`作为锁对象
- 如果方法是静态方法:同步方法默认用`类名.class` 作为锁对象
<a name="uCAEd"></a>
## 1.9线程同步_lock显示锁
Lock锁也称同步锁,加锁与释放锁方法化了,如下
- `public void lock()`: 加同步锁
- `public void unlock()`: 释放同步锁
// 创建一把锁对象
private final Lock lock = new ReentrantLock();
// 上锁
lock.lock();
// 解锁
lock.unlock();
• 1
• 2
• 3
• 4
• 5
• 6
```
优点:线程安全,但是性能差
假如开发中不会存在多线程安全问题,建议使用线程不安全的设计类
1.10线程通信
线程通信一定是多个线程在操作同一个资源才需要通信,线程
线程通信方法:
public void wait()
: 让当前线程进入到等待状态,此方法必须锁对象调用public void notify()
:唤醒当前锁对象上等待状态的某个线程,此方法必须锁对象调用public void notifyAll()
:唤醒当前锁对象上等待状态的全部线程,此方法必须锁对象调用