1. 基础回顾+面试

1.1 异常

  • Java中异常继承的根类是:Throwable

四、异常与线程 - 图1
Exception异常的分类:

  1. 编译时异常:继承自Exception的异常或者其子类,编译阶段就会报错
  2. 运行时异常:继承自RuntimeException的异常或者其子类,编译阶段不报错,但是运行阶段报错

    1.2 常见的运行时异常

  3. 面试:写出几个运行时异常的例子!

  • 数组索引越界异常ArrayIndexOutOfBoundsException
  • 空指针异常NullPointerException
    • 直接输出没有问题,但是调用空指针的变量的功能就会报错!
  • 类型转换异常ClassCastException
  • 迭代器遍历没有此元素异常NoSuchElementException
  • 数学操作异常ArithmeticException
  • 数学转换异常NumberFormatException ```java public class ExceptionDemo { public static void main(String[] args) {

    1. System.out.println("程序开始。。。。。。");
    2. /** 1.数组索引越界异常: ArrayIndexOutOfBoundsException。*/
    3. int[] arrs = {10 ,20 ,30};
    4. System.out.println(arrs[2]);
    5. // System.out.println(arrs[3]); // 此处出现了数组索引越界异常。代码在此处直接执行死亡!
    6. /** 2.空指针异常 : NullPointerException。直接输出没有问题。但是调用空指针的变量的功能就会报错!! */
    7. String name = null ;
    8. System.out.println(name); // 直接输出没有问题
    9. // System.out.println(name.length()); // 此处出现了空指针异常。代码在此处直接执行死亡!
    10. /** 3.类型转换异常:ClassCastException。 */
    11. Object o = "齐天大圣";
    12. //Integer s = (Integer) o; // 此处出现了类型转换异常。代码在此处直接执行死亡!
  1. /** 5.数学操作异常:ArithmeticException。 */
  2. // int c = 10 / 0 ; // 此处出现了数学操作异常。代码在此处直接执行死亡!
  3. /** 6.数字转换异常: NumberFormatException。 */
  4. String num = "23aa";
  5. Integer it = Integer.valueOf(num); // 此处出现了数字转换异常。代码在此处直接执行死亡!
  6. System.out.println(it+1);
  7. System.out.println("程序结束。。。。。。");
  8. }

}

  1. <a name="pGqXu"></a>
  2. ## 1.3 异常的产生处理默认机制
  3. 异常的产生默认的处理过程解析(了解即可):
  4. 1. 默认会在出现异常的代码那里自动的创建一个异常对象:ArithmeticException
  5. 1. 异常会从方法中出现的点这里抛出给调用者,调用者最终抛出给JVM虚拟机
  6. 1. 虚拟机接收到异常对象后,现在控制台直接输出异常栈信息数据。
  7. 1. 直接从当前执行的异常点干掉当前程序。
  8. 1. 后续代码没有机会执行了,因为程序已经死亡
  9. 默认的异常处理机制并不好,一旦真的出现异常,程序立即死亡
  10. <a name="GDeoA"></a>
  11. ## 1.3编译时异常的处理方式
  12. > 方式一:抛出异常
  13. **格式**:

方法 throws Exception{

} • 1 • 2 • 3

  1. - 在出现编译时异常的地方层层把异常抛出去给调用者,调用者最终抛出给JVM虚拟机,JVM虚拟机输出异常信息,直接干掉程序,这种方式与默认方式是一样的。虽然可以解决代码编译时的错误,但是一旦运行时真的出现异常,程序还是会立即死亡,这种方式并不好
  2. > 方式二:捕获处理:在出现异常的地方自己处理,谁出现谁处理
  3. 格式:

try{ // 监视可能出现异常的代码 }catch{异常类型1 变量}{ // 处理异常 }catch{异常类型2 变量}{ // 处理异常 } • 1 • 2 • 3 • 4 • 5 • 6 • 7

  1. - 第二中方式,可以处理异常,并且出现异常后代码也不会死亡,但是从理论上来说,这种方式不是最好的,上层调用者不能直接知道底层的执行情况!
  2. > 方式三:在出现异常的地方把异常一层一层的抛出给最外层调用者,最外层调用者集中捕获处理

try{ // 可能出现异常的代码 }catch(Exception e){ e.printStackTrae(); //直接打印异常栈信息 } • 1 • 2 • 3 • 4 • 5

  1. - 这种方案最外层调用者可以知道底层执行的情况,同时程序在出现异常后也不会立即死亡,这是理论上最好的方案。
  2. - 虽然异常有三种处理方式,但是开发中只要能解决你的问题,每种方式都可能用到!
  3. <a name="rI3ve"></a>
  4. ## 1.4finally关键字
  5. - 用在捕获处理的异常格式中,放在最后面

try{ //可能出现异常的代码! }catch{Exception e}{ e.printStackTrace(); }finally{ // 无论代码是出现异常还是正常执行,最终一定要执行这里的代码!! } • 1 • 2 • 3 • 4 • 5 • 6 • 7

  1. - `finally`的作用:可以在代码执行完毕后进行资源的释放操作
  2. - 资源都是实现了`Closeable`接口的,都自带`close()`关闭方法
  3. - try 出现1
  4. - catch:出现0 - N 次(如果有finally那么 catch 可以没有)
  5. - finally:出现0 - 1
  6. <a name="DOVZ9"></a>
  7. ## 1.5自定义异常(了解)
  8. Java已经为开发中可能出现的异常都设计了一个类来代表,但是在实际开发中,异常可能有无数中情况,Java无法为这个世界上所有的异常都定义了一个类。假如一个企业如果想为自己认为的某种业务问题定义成一个异常,就需要自己来自定义异常类。
  9. <a name="35O5Y"></a>
  10. ## 1.4多线程(并发编程)
  11. 1. **什么是进程?**<br />答:程序是静止的,运行中的程序就是进程
  12. 1. **进程的三个特征**
  13. - 独立性:进程与进程之间是相互独立的,彼此有自己独立内存区域
  14. - 动态性:进程是运行中的程序,要动态的占用内存,CPU和网络等资源
  15. - 并发性:CPU会分时轮询切换依次为每个进程服务,因为切换的速度非常快,给我们的感觉像是在同时执行,这就是并发性
  16. - 并发:同一时刻同时有多个在执行
  17. 3. **什么是线程?**<br />答:线程是属于进程的,一个进程可以包含多个线程,这就是多线程
  18. <a name="rWa8w"></a>
  19. ## 1.5线程的创建方式
  20. 多线程是很有用的,我们在进程中创建线程的方式有三种:
  21. <a name="FDho7"></a>
  22. ### 1.5.1继承Thread类
  23. 继承Thread类的方式:
  24. 1. 定义一个线程类继承`Thread`
  25. 1. 重写`run()`方法
  26. 1. 创建一个**新的线程对象**

Thread t = new MyThread(); • 1

  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();

  1. for(int i = 0 ; i < 100 ; i++ ){
  2. System.out.println("main线程输出:"+i);
  3. }
  4. }

} // 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

  1. <a name="w24en"></a>
  2. ### 1.5.2实现Runnable接口
  3. 1. 创建一个线程任务类实现`Runnable`接口
  4. 1. 重写`run()`方法
  5. 1. 创建一个**线程任务对象**(注意:线程任务对象不是线程对象,只是执行线程的任务的)

Runnable target = new MyRunnable(); • 1

  1. 1. 把线程任务对象包装成线程对象,且可以指定线程名称

// Thread t = new Thread(target); Thread t = new Thread(target,”1号线程”); • 1 • 2

  1. 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

  1. <a name="K6DWD"></a>
  2. #### 1.5.2.1Thread的构造器
  3. - `public Thread(){}`
  4. - `public Thread(String name){}`
  5. - `public Thread(Runnable target){}`:分配一个新的Thread对象
  6. - `public Thread(Runnable target,String name)`:分配一个新的Thread对象,且可以指定新的线程名称
  7. <a name="Qoo9D"></a>
  8. #### 1.5.2.1优缺点
  9. 缺点:代码复杂一点<br />优点:
  10. - 线程任务类只是实现了`Runnable`接口,可以继续继承其他类,而且可以继续实现其他接口(避免乐单继承的局限性)
  11. - 同一个线程任务对象可以被包装成多个线程对象
  12. <a name="TyCrS"></a>
  13. ### 1.5.3实现Callable接口
  14. 1. 定义一个线程任务类实现`Callable`接口,申明线程返回的结果类型
  15. 1. 重写线程任务类的`call`方法,这个方法可以直接返回执行的结果
  16. 1. 创建一个`Callable`的线程任务对象
  17. 1. 把`Callable`的线程任务对象包装成一个未来任务对象
  18. 1. 把未来任务对象包装成线程对象
  19. 1. 调用线程的`start()`方法启动线程

public class ThreadDemo { public static void main(String[] args) { // 3.创建一个Callable的线程任务对象 Callable call = new MyCallable(); // 4.把Callable任务对象包装成一个未来任务对象 // — public FutureTask(Callable callable) // 未来任务对象是啥,有啥用? // — 未来任务对象其实就是一个Runnable对象:这样就可以被包装成线程对象! // — 未来任务对象可以在线程执行完毕之后去得到线程执行的结果。 FutureTask task = new FutureTask<>(call); // 5.把未来任务对象包装成线程对象 Thread t = new Thread(task); // 6.启动线程对象 t.start(); for(int i = 1 ; i <= 10 ; i++ ){ System.out.println(Thread.currentThread().getName()+” => “ + i); } // 在最后去获取线程执行的结果,如果线程没有结果,让出CPU等线程执行完再来取结果 try { String rs = task.get(); // 获取call方法返回的结果(正常/异常结果) System.out.println(rs); } catch (Exception e) { e.printStackTrace(); } } } // 1.创建一个线程任务类实现Callable接口,申明线程返回的结果类型 class MyCallable implements Callable{ // 2.重写线程任务类的call方法! @Override public String call() throws Exception { // 需求:计算1-10的和返回 int sum = 0 ; for(int i = 1 ; i <= 10 ; i++ ){ System.out.println(Thread.currentThread().getName()+” => “ + i); sum+=i; } return Thread.currentThread().getName()+”执行的结果是:”+sum; } } • 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 • 39 • 40 • 41 • 42 • 43 • 44

  1. <a name="InY9x"></a>
  2. #### 1.5.4优劣点
  3. 优点:全是优点
  4. <a name="CtKZ1"></a>
  5. ## 1.6线程的常用API
  6. Thread 类的 API
  7. 1. `public void setName(String name)`: 给当前线程取名字
  8. 1. `public void getName()`: 获取当前线程的名字
  9. - 线程存在默认名称,子线程的默认名称是:Thread - 索引
  10. - 主线程的默认名称是:main
  11. 3. `public static Thread currentThread()`: 获取当前线程对象,这个代码在哪个线程中,就得到哪个线程对象
  12. 3. `public static void sleep(long time)`:让当前线程休眠多少毫秒再继续执行
  13. 3. `public Thread(String name)`:创建对象并取名字
  14. ---

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

  1. ---
  2. 线程休眠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

  1. ---
  2. 通过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

  1. <a name="Qb4Ak"></a>
  2. ## 1.7线程安全
  3. 线程安全问题:多个线程同时操作同一个共享资源的时候可能会出现线程安全问题
  4. <a name="wF3LG"></a>
  5. ## 1.7线程同步_同步代码块
  6. - **线程同步的作用**:就是为了解决线程安全问题,让多个线程实现先后依次访问共享资源,这样就解决了安全问题
  7. - **线程安全**:多个线程同时操作同一个共享资源的时候可能会出现线程安全问题
  8. - **线程同步的做法**:加锁(就是把共享资源进行上锁,每次只能一个线程进入访问完毕以后,其他线程才能进来)
  9. - **线程同步的方法**:
  10. - 同步代码块
  11. - 同步方法
  12. - `lock` 显示锁
  13. **同步代码块作用**:是把出现线程安全问题的核心代码给上锁,每次只能一个线程进入,执行完毕之后自动解锁,其他线程才可以进来执行

// 格式 synchronized(锁对象){ // 访问共享资源的核心代码
} • 1 • 2 • 3 • 4

  1. - 在实例方法中建议用`this`作为锁对象
  2. - 在静态方法中建议用`类名.class`字节码作为锁对象
  3. <a name="f9fds"></a>
  4. ## 1.8线程同步_同步方法
  5. **作用**:把出现线程安全问题的核心方法给锁起来,每次只能一个线程进入访问,其他线程必须在方法外面等待<br />**用法**:直接给**方法**加上一个修饰符 `synchronized`

public synchronized void 方法名(){

} • 1 • 2 • 3

  1. 原理:同步方法的原理和同步代码块的底层原理其实是完全一样的,只是同步方法是把整个方法的代码都锁起来,同步方法其实底层也有锁对象的。
  2. - 如果方法是实例方法:同步方法默认用 `this`作为锁对象
  3. - 如果方法是静态方法:同步方法默认用`类名.class` 作为锁对象
  4. <a name="uCAEd"></a>
  5. ## 1.9线程同步_lock显示锁
  6. Lock锁也称同步锁,加锁与释放锁方法化了,如下
  7. - `public void lock()`: 加同步锁
  8. - `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():唤醒当前锁对象上等待状态的全部线程,此方法必须锁对象调用