今日内容

  • 冒泡排序
  • 选择排序
  • 二分查找
  • 异常处理
  • 多线程基础

教学目标

  • 能够理解冒泡排序的执行原理
  • 能够理解选择排序的执行原理
  • 能够理解二分查找的执行原理
  • 能够辨别程序中异常和错误的区别
  • 说出异常的分类
  • 列举出常见的三个运行期异常
  • 能够使用try…catch关键字处理异常
  • 能够使用throws关键字处理异常
  • 能够自定义并使用异常类
  • 说出进程和线程的概念
  • 能够理解并发与并行的区别
  • 能够描述Java中多线程运行原理
  • 能够使用继承类的方式创建多线程
  • 能够使用实现接口的方式创建多线程
  • 能够说出实现接口方式的好处

第一章 冒泡排序

知识点— 冒泡排序

目标

  • 能够理解冒泡排序的执行原理

路径

  • 冒泡排序概述
  • 冒泡排序图解
  • 冒泡排序代码实现

讲解

冒泡排序概述

  • 一种排序的方式,对要进行排序的数据, 相邻的数据进行两两比较,将较大的数据放在后面,依次对所有的数据进行操作,直至所有数据按要求完成排序
  • 如果有n个数据进行排序,总共需要比较n-1轮
    每一轮比较完毕,下一轮的比较就会少一个数据参与

冒泡排序图解

day09【排序算法、异常、多线程基础】 - 图1

day09【排序算法、异常、多线程基础】 - 图2

冒泡排序代码实现

  1. /*
  2. 冒泡排序:
  3. 一种排序的方式,对要进行排序的数据中相邻的数据进行两两比较,将较大的数据放在后面,
  4. 依次对所有的数据进行操作,直至所有数据按要求完成排序
  5. */
  6. public class ArrayDemo {
  7. public static void main(String[] args) {
  8. //定义一个数组
  9. int[] arr = {7, 6, 5, 4, 3};
  10. System.out.println("排序前:" + Arrays.toString(arr));
  11. // 这里减1,是控制每轮比较的次数
  12. for (int x = 0; x < arr.length - 1; x++) {
  13. // -1是为了避免索引越界,-x是为了调高比较效率
  14. for (int i = 0; i < arr.length - 1 - x; i++) {
  15. if (arr[i] > arr[i + 1]) {
  16. int temp = arr[i];
  17. arr[i] = arr[i + 1];
  18. arr[i + 1] = temp;
  19. }
  20. }
  21. }
  22. System.out.println("排序后:" + Arrays.toString(arr));
  23. }
  24. }

小结

第二章 选择排序

知识点— 选择排序

目标

  • 能够理解选择排序的执行原理

路径

  • 选择排序概述
  • 选择排序图解
  • 选择排序代码实现

讲解

选择排序概述

  • 另外一种排序的方式,从头开始选中数组元素,与其后面的元素依次进行两两比较,将较大的数据放在后面,依次从前到后选中每个元素,直至所有数据按要求完成排序
  • 如果有n个数据进行排序,总共需要比较n-1轮
  • 每一轮比较完毕,下一轮的比较就会少一个数据参与

选择排序图解

day09【排序算法、异常、多线程基础】 - 图3

选择排序代码实现

  1. public class Test {
  2. public static void main(String[] args) {
  3. //定义一个数组
  4. int[] arr = {7, 6,18, 5, 4, 3};
  5. System.out.println("排序前:" + Arrays.toString(arr));
  6. // 选择排序----每比较一次就交换
  7. /*for (int i = 0;i<arr.length-1;i++){
  8. for (int j = i+1;j<arr.length;j++){
  9. // 比较
  10. if (arr[i] > arr[j]){
  11. // 交换
  12. int temp = arr[i];
  13. arr[i] = arr[j];
  14. arr[j] = temp;
  15. }
  16. }
  17. }*/
  18. // 选择排序-----优化 每比较一次,记录一下索引,比较完之后找到最小值索引,然后交换
  19. for (int i = 0;i<arr.length-1;i++){
  20. // 定义一个存储最小值索引的变量
  21. int min = i;
  22. // 找最小值的索引
  23. for (int j = i+1;j<arr.length;j++){
  24. // 比较
  25. if (arr[min] > arr[j]){
  26. // 记录最小值的索引
  27. min = j;
  28. }
  29. }
  30. // 最小值和指定位置进行交换
  31. if (min != i) {
  32. // 交换
  33. int temp = arr[i];
  34. arr[i] = arr[min];
  35. arr[min] = temp;
  36. }
  37. }
  38. System.out.println("排序后:" + Arrays.toString(arr));
  39. }
  40. }

小结

第三章 二分查找

知识点— 二分查找

目标

  • 能够理解二分查找的执行原理

路径

  • 普通查找和二分查找
  • 二分查找图解
  • 二分查找代码实现

讲解

普通查找和二分查找

普通查找

原理:遍历数组,获取每一个元素,然后判断当前遍历的元素是否和要查找的元素相同,如果相同就返回该元素的索引。如果没有找到,就返回一个负数作为标识(一般是-1)

二分查找

原理: 每一次都去获取数组的中间索引所对应的元素,然后和要查找的元素进行比对,如果相同就返回索引;

如果不相同,就比较中间元素和要查找的元素的值;

如果中间元素的值大于要查找的元素,说明要查找的元素在左侧,那么就从左侧按照上述思想继续查询(忽略右侧数据);

如果中间元素的值小于要查找的元素,说明要查找的元素在右侧,那么就从右侧按照上述思想继续查询(忽略左侧数据);

二分查找对数组是有要求的,数组必须已经排好序

二分查找图解

假设有一个给定有序数组(10,14,21,38,45,47,53,81,87,99),要查找50出现的索引

则查询过程如下图所示:

day09【排序算法、异常、多线程基础】 - 图4

二分查找代码实现

  1. public static void main(String[] args) {
  2. int[] arr = {10, 14, 21, 38, 45, 47, 53, 81, 87, 99};
  3. int index = binarySerach(arr, 38);
  4. System.out.println(index);
  5. }
  6. /**
  7. * 二分查找方法
  8. * @param arr 查找的目标数组
  9. * @param number 查找的目标值
  10. * @return 找到的索引,如果没有找到返回-1
  11. */
  12. public static int binarySerach(int[] arr, int number) {
  13. int start = 0;
  14. int end = arr.length - 1;
  15. while (start <= end) {
  16. int mid = (start + end) / 2;
  17. if (number == arr[mid]) {
  18. return mid ;
  19. } else if (number < arr[mid]) {
  20. end = mid - 1;
  21. } else if (number > arr[mid]) {
  22. start = mid + 1;
  23. }
  24. }
  25. return -1; //如果数组中有这个元素,则返回
  26. }

小结

第四章 异常

知识点— 异常

目标

  • 能够辨别程序中异常和错误的区别,并且说出异常的分类

路径

  • 异常概念
  • 异常体系
  • 异常分类
  • 异常的产生过程解析

讲解

异常概念

异常,就是不正常的意思。在生活中:医生说,你的身体某个部位有异常,该部位和正常相比有点不同,该部位的功能将受影响.在程序中的意思就是:

  • 异常 :指的是程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止。

注意: 在Java等面向对象的编程语言中,异常本身是一个类,产生异常就是创建异常对象并抛出了一个异常对象。Java处理异常的方式是中断处理。

异常指的并不是语法错误,语法错了,编译不通过,不会产生字节码文件,根本不能运行.

异常体系

异常机制其实是帮助我们找到程序中的问题,异常的根类是java.lang.Throwable,其下有两个子类:java.lang.Errorjava.lang.Exception,平常所说的异常指java.lang.Exception

day09【排序算法、异常、多线程基础】 - 图5

Throwable体系:

  • Error:严重错误Error,无法通过处理的错误,只能事先避免,好比绝症。
  • Exception:表示异常,异常产生后程序员可以通过代码的方式纠正,使程序继续运行,是必须要处理的。好比感冒、阑尾炎。

异常分类

我们平常说的异常就是指Exception,因为这类异常一旦出现,我们就要对代码进行更正,修复程序。

异常(Exception)的分类:根据在编译时期还是运行时期去检查异常?

  • 编译时期异常:checked异常。在编译时期,就会检查,如果没有处理异常,则编译失败。(如日期格式化异常)
  • 运行时期异常:runtime异常。在运行时期,检查异常.在编译时期,运行异常不会编译器检测(不报错)。(如数学异常)

    day09【排序算法、异常、多线程基础】 - 图6

  1. public class Test {
  2. public static void main(String[] args) {
  3. /*
  4. 异常的概述:
  5. - 异常概念: 程序运行期间,出现的不正常情况,导致jvm终止程序运行
  6. 注意:
  7. java是面向对象语言,异常本身也是一个类,当出现异常的时候,就会创建该异常类的对象并抛出该异常对象
  8. 创建异常对象,该对象就会包装异常的类型,异常的信息,异常的位置等信息
  9. - 异常体系
  10. Throwable类:是 Java 语言中所有错误或异常的超类\父类
  11. Error类: 表示错误,无法通过代码进行纠正,只能事先避免,相当于:绝症
  12. 例如: 栈内存溢出错误,服务器宕机,数据库奔溃...
  13. Exception类:表示异常,可以通过代码进行纠正,相当于: 感冒...
  14. - 异常分类
  15. 编译异常:在编译期间,出现的异常,导致程序无法通过编译,这就是编译异常
  16. 除了RuntimeException及其子类都是编译异常
  17. 运行异常:在运行期间,才出现的异常,编译期间不处理,编译可以通过,这就是运行异常
  18. RuntimeException及其子类都是运行异常
  19. */
  20. // 异常和错误
  21. System.out.println("开始");
  22. //System.out.println(1/0);// 异常
  23. //method();// StackOverflowError 错误
  24. System.out.println("结束");
  25. // 例如: 编译异常
  26. //SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
  27. //Date date = sdf.parse("1999-10-10");
  28. // 例如: 运行异常
  29. //System.out.println(1/0);// 异常
  30. }
  31. public static void method(){
  32. System.out.println("1");
  33. method();
  34. }
  35. }

异常的产生过程解析

先运行下面的程序,程序会产生一个数组索引越界异常ArrayIndexOfBoundsException。我们通过图解来解析下异常产生的过程。

测试类

  1. public class Test {
  2. public static void main(String[] args) {
  3. int[] arr = { 34, 12, 67 };
  4. int num = getElement(arr, 4);
  5. System.out.println("num=" + num);
  6. System.out.println("over");
  7. }
  8. // 对给定的数组通过给定的角标获取元素。
  9. public static int getElement(int[] arr, int index) {
  10. int element = arr[index];
  11. return element;
  12. }
  13. }

上述程序执行过程图解:

day09【排序算法、异常、多线程基础】 - 图7

小结

第五章 异常的产生和处理

知识点— 异常的产生

目标

  • 能够理解使用throw关键字产生异常

路径

  • throw关键字的作用
  • throw关键字的使用格式
  • 案例演示

讲解

throw关键字的作用

在java中,提供了一个throw关键字,它用来抛出一个指定的异常对象。throw用在方法内,用来抛出一个异常对象,将这个异常对象传递到调用者处,并结束当前方法的执行。

throw关键字的使用格式

  1. throw new 异常类名(参数);

例如:

  1. throw new NullPointerException("要访问的arr数组不存在");
  2. throw new ArrayIndexOutOfBoundsException("该索引在数组中不存在,已超出范围");

案例演示

  1. public class Student {
  2. //姓名
  3. private String name;
  4. //年龄
  5. private int age;
  6. //构造方法
  7. public Student() {
  8. }
  9. public Student(String name, int age) {
  10. this.name = name;
  11. //对年龄的判断
  12. if(age >= 0) {
  13. this.age = age;
  14. }else{
  15. //如果年龄是负数,就让他报错
  16. throw new RuntimeException("年龄不能是" + age);
  17. }
  18. }
  19. //set get
  20. public String getName() {
  21. return name;
  22. }
  23. public void setName(String name) {
  24. this.name = name;
  25. }
  26. public int getAge() {
  27. return age;
  28. }
  29. public void setAge(int age) {
  30. //对年龄的判断
  31. if(age >= 0) {
  32. this.age = age;
  33. }else{
  34. //如果年龄是负数,就让他报错
  35. throw new RuntimeException("年龄不能是" + age);
  36. }
  37. }
  38. //toString
  39. @Override
  40. public String toString() {
  41. return "Student{" +
  42. "name='" + name + '\'' +
  43. ", age=" + age +
  44. '}';
  45. }
  46. }
  47. public class Demo02_异常的产生 {
  48. public static void main(String[] args) {
  49. //正常情况下,年龄不可能是一个负数
  50. //要求:赋值正数正常赋值,赋值负数就让代码报错!
  51. //创建对象
  52. Student s = new Student("柳岩",36);
  53. System.out.println(s);
  54. //创建对象
  55. Student s2 = new Student();
  56. s2.setName("美美");
  57. s2.setAge(-20);
  58. System.out.println(s2);
  59. }
  60. }

小结

知识点—声明处理异常

目标

  • 掌握声明处理异常

路径

  • 声明处理异常的概述
  • 声明处理异常格式

讲解

声明处理异常的概述

声明处理异常:使用throws关键字将异常标识出来, 表示当前方法不处理异常,而是提醒给调用者, 让调用者来处理….最终会到虚拟机,虚拟机直接结束程序,打印异常信息。

声明处理异常格式

  1. 修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2…{ // 可以抛出一个,也可以多个
  2. }

案例演示

  1. public class Demo {
  2. public static void main(String[] args) throws IOException{
  3. method1();
  4. }
  5. public static void method3(int num) throws Exception {
  6. if(num == 1) {
  7. throw new IOException("IO异常");// 创建了一个编译异常,并通过throw抛出这个编译异常
  8. }else{
  9. throw new ParseException("解析异常",1);
  10. }
  11. }
  12. public static void method2(int num) throws IOException,ParseException{
  13. if(num == 1) {
  14. throw new IOException("IO异常");// 创建了一个编译异常,并通过throw抛出这个编译异常
  15. }else{
  16. throw new ParseException("解析异常",1);
  17. }
  18. }
  19. public static void method1() throws IOException{
  20. throw new IOException("IO异常");// 创建了一个编译异常,并通过throw抛出这个编译异常
  21. }
  22. }

小结

知识点—捕获处理异常try…catch

目标

  • 掌握捕获处理异常

路径

  • 捕获处理异常的概述
  • 捕获处理异常格式
  • 获取异常信息

讲解

捕获处理异常的概述

  • 捕获处理异常:对异常进行捕获处理 , 处理完后程序可以正常向下执行。

捕获处理异常格式

  1. try{
  2. 编写可能会出现异常的代码
  3. }catch(异常类型 e){
  4. 处理异常的代码
  5. //记录日志/打印异常信息/继续抛出异常
  6. }
  7. 执行步骤:
  8. 1.首先执行try中的代码,如果try中的代码出现了异常,那么就直接执行catch()里面的代码,执行完后,程序继续往下执行
  9. 2.如果try中的代码没有出现异常,那么就不会执行catch()里面的代码,而是继续往下执行

注意:

  1. try和catch都不能单独使用,必须连用。
  2. try中的代码出现了异常,那么出现异常位置后面的代码就不会再执行了
  3. 捕获处理异常,如果程序出现了异常,程序会继续往下执行

    1. 声明处理异常,如果程序出现了异常,程序就不会继续往下执行

演示如下:

  1. public class Demo {
  2. public static void main(String[] args) {
  3. try {
  4. SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
  5. Date date = sdf.parse("1999年10月12日");
  6. System.out.println(date);
  7. }catch (Exception e){
  8. System.out.println("出现了异常....");
  9. }
  10. System.out.println("结束");
  11. }
  12. }

获取异常信息

Throwable类中定义了一些查看方法:

  • public String getMessage():获取异常的描述信息,原因(提示给用户的时候,就提示错误原因。

  • public String toString():获取异常的类型和异常描述信息(不用)。

  • public void printStackTrace():打印异常的跟踪栈信息并输出到控制台。

  1. _包含了异常的类型,异常的原因,还包括异常出现的位置,在开发和调试阶段,都得使用printStackTrace_

在开发中呢也可以在catch将编译期异常转换成运行期异常处理。

小结

知识点—finally 代码块

目标

  • 掌握finally代码块的格式和执行流程

路径

  • finally代码块的概述
  • finally代码块的语法格式
  • 案例演示

讲解

finally代码块的概述

finally:有一些特定的代码无论异常是否发生,都需要执行。另外,因为异常会引发程序跳转,导致有些语句执行不到。而finally就是解决这个问题的,在finally代码块中存放的代码都是一定会被执行的。

finally代码块的语法格式

  1. try{
  2. 可能会出现异常的代码
  3. }catch(异常的类型 变量名){
  4. 处理异常的代码或者打印异常的信息
  5. }finally{
  6. 无论异常是否发生,都会执行这里的代码(正常情况,都会执行finally中的代码,一般用来释放资源)
  7. }
  8. 执行步骤:
  9. 1.首先执行try中的代码,如果try中的代码出现了异常,那么就直接执行catch()里面的代码,执行完后会执行finally中的代码,然后程序继续往下执行
  10. 2.如果try中的代码没有出现异常,那么就不会执行catch()里面的代码,但是还是会执行finally中的代码,然后程序继续往下执行

注意:finally不能单独使用。

案例演示

  1. public class Test {
  2. public static void main(String[] args) {
  3. /*
  4. 注意:
  5. 即使catch中有return,finally中的代码还是会执行
  6. */
  7. Scanner sc = null;
  8. try{
  9. sc = new Scanner(System.in);
  10. String s = sc.next();
  11. System.out.println(1/0);// 出现异常,throw new ArithmeticException("");
  12. }catch (Exception e){
  13. System.out.println("出现了异常");
  14. //return;// 结束方法 finally会执行
  15. // System.exit(0);// 系统退出 finally不会执行
  16. }finally{
  17. sc.close();// 不会执行
  18. System.out.println("关闭...");
  19. }
  20. System.out.println("======================");
  21. /* Scanner sc2 = null;
  22. try{
  23. sc2 = new Scanner(System.in);
  24. String s = sc2.next();
  25. System.out.println(1/1);// 1 没有异常
  26. }catch (Exception e){
  27. System.out.println("出现了异常");
  28. }finally{
  29. sc2.close();// 不会执行
  30. System.out.println("关闭...");
  31. }*/
  32. System.out.println("结束");
  33. }
  34. }

当只有在try或者catch中调用退出JVM的相关方法,此时finally才不会执行,否则finally永远会执行。

day09【排序算法、异常、多线程基础】 - 图8

小结

知识点—异常注意事项

目标

  • 理解异常注意事项

路径

  • 异常注意事项

讲解

  • 运行时异常被抛出可以不处理。即不捕获也不声明抛出。

  • 如果父类的方法抛出了多个异常,子类覆盖(重写)父类方法时,只能抛出相同的异常或者是他的子集。

  • 父类方法没有抛出异常,子类覆盖父类该方法时也不可抛出异常。此时子类产生该异常,只能捕获处理,不能声明抛出

  • 当多异常分别处理时,捕获处理,前边的类不能是后边类的父类

  • 在try/catch后可以追加finally代码块,其中的代码一定会被执行,通常用于资源回收。

  • 多个异常使用捕获又该如何处理呢?

    1. 多个异常分别处理。
    2. 多个异常一次捕获,多次处理。
    3. 多个异常一次捕获一次处理。
  1. try{
  2. 编写可能会出现异常的代码
  3. }catch(异常类型A e){ try中出现A类型异常,就用该catch来捕获.
  4. 处理异常的代码
  5. //记录日志/打印异常信息/继续抛出异常
  6. }catch(异常类型B e){ try中出现B类型异常,就用该catch来捕获.
  7. 处理异常的代码
  8. //记录日志/打印异常信息/继续抛出异常
  9. }

注意:这种异常处理方式,要求多个catch中的异常不能相同,并且若catch中的多个异常之间有子父类异常的关系,那么子类异常要求在上面的catch处理,父类异常在下面的catch处理。

代码如下:

  1. public class Demo {
  2. public static void main(String[] args) {
  3. System.out.println(1/0);
  4. }
  5. /**
  6. * 多个异常一次捕获一次处理
  7. * @param num
  8. */
  9. public static void method3(int num) {
  10. try {
  11. if(num == 1) {
  12. throw new IOException("IO异常");
  13. }else{
  14. throw new ParseException("解析异常",1);
  15. }
  16. } catch (Exception e) {
  17. e.printStackTrace();
  18. }
  19. }
  20. /**
  21. * 多个异常一次捕获,多次处理。
  22. * @param num
  23. */
  24. public static void method2(int num) {
  25. try {
  26. if(num == 1) {
  27. throw new IOException("IO异常");
  28. }else{
  29. throw new ParseException("解析异常",1);
  30. }
  31. } catch (IOException e) {
  32. e.printStackTrace();
  33. } catch (ParseException e) {
  34. e.printStackTrace();
  35. }
  36. }
  37. /**
  38. * 多个异常分别处理。
  39. * @param num
  40. */
  41. public static void method1(int num) {
  42. if(num == 1) {
  43. try {
  44. throw new IOException("IO异常");
  45. } catch (IOException e) {
  46. e.printStackTrace();
  47. }
  48. }else{
  49. try {
  50. throw new ParseException("解析异常",1);
  51. } catch (ParseException e) {
  52. e.printStackTrace();
  53. }
  54. }
  55. }
  56. }

小结

第六章 自定义异常

知识点— 自定义异常

目标

  • 能够自定义并使用异常类

路径

  • 自定义异常概述
  • 自定义异常练习

讲解

自定义异常概述

为什么需要自定义异常类:

我们说了Java中不同的异常类,分别表示着某一种具体的异常情况,那么在开发中总是有些异常情况是SUN没有定义好的,例如年龄负数问题,考试成绩负数问题.这些异常在JDK中没有定义过,此时我们根据自己业务的异常情况来定义异常类。

什么是自定义异常类:

在开发中根据自己业务的异常情况来定义异常类.

自定义一个业务逻辑异常: RegisterException。一个注册异常类。

异常类如何定义:

  1. 自定义一个编译期异常: 自定义类 并继承于java.lang.Exception
  2. 自定义一个运行时期的异常类:自定义类 并继承于java.lang.RuntimeException

自定义异常的练习

要求:我们模拟注册操作,如果用户名已存在,则抛出异常并提示:亲,该用户名已经被注册。

首先定义一个注册异常类RegisterException:

  1. // 业务逻辑异常
  2. public class RegisterException extends Exception {
  3. /**
  4. * 空参构造
  5. */
  6. public RegisterException() {
  7. }
  8. /**
  9. *
  10. * @param message 表示异常提示
  11. */
  12. public RegisterException(String message) {
  13. super(message);
  14. }
  15. }

模拟登陆操作,使用数组模拟数据库中存储的数据,并提供当前注册账号是否存在方法用于判断。

  1. public class Demo {
  2. // 模拟数据库中已存在账号
  3. private static String[] names = {"bill","hill","jill"};
  4. public static void main(String[] args) {
  5. //调用方法
  6. try{
  7. // 可能出现异常的代码
  8. checkUsername("nill");
  9. System.out.println("注册成功");//如果没有异常就是注册成功
  10. }catch(LoginException e){
  11. //处理异常
  12. e.printStackTrace();
  13. }
  14. }
  15. //判断当前注册账号是否存在
  16. //因为是编译期异常,又想调用者去处理 所以声明该异常
  17. public static boolean checkUsername(String uname) throws LoginException{
  18. for (String name : names) {
  19. if(name.equals(uname)){//如果名字在这里面 就抛出登陆异常
  20. throw new LoginException("亲"+name+"已经被注册了!");
  21. }
  22. }
  23. return true;
  24. }
  25. }

小结

第七章 多线程

我们在之前,学习的程序在没有跳转语句的前提下,都是由上至下依次执行,那现在想要设计一个程序,边打游戏边听歌,怎么设计?

要解决上述问题,咱们得使用多进程或者多线程来解决.

知识点—并发与并行

目标

  • 能够理解什么是并发和并行

路径

  • 并行的概述
  • 并发的概述

讲解

  • 并行:指两个或多个事件在同一时刻发生(同时执行)。
  • 并发:指两个或多个事件在同一个时间段内发生(交替执行)。

day09【排序算法、异常、多线程基础】 - 图9

在操作系统中,安装了多个程序,并发指的是在一段时间内宏观上有多个程序同时运行,这在单 CPU 系统中,每一时刻只能有一道程序执行,即微观上这些程序是分时的交替运行,只不过是给人的感觉是同时运行,那是因为分时交替运行的时间是非常短的。

而在多个 CPU 系统中,则这些可以并发执行的程序便可以分配到多个处理器上(CPU),实现多任务并行执行,即利用每个处理器来处理一个可以并发执行的程序,这样多个程序便可以同时执行。目前电脑市场上说的多核 CPU,便是多核处理器,核越多,并行处理的程序越多,能大大的提高电脑运行的效率。

注意:单核处理器的计算机肯定是不能并行的处理多个任务的,只能是多个任务在单个CPU上并发运行。同理,线程也是一样的,从宏观角度上理解线程是并行运行的,但是从微观角度上分析却是串行运行的,即一个线程一个线程的去运行,当系统只有一个CPU时,线程会以某种顺序执行多个线程,我们把这种情况称之为线程调度。

小结

知识点— 线程与进程

目标

  • 能够理解什么是线程与进程

路径

  • 线程的概述
  • 进程的概述

讲解

  • 进程:进程是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;

    • 概述: 进程其实就是应用程序的可执行单元,
    • 特点:

      • 1.每个进程都有一个独立的内存空间
      • 2.一个应用程序可以有多个进程
  • 线程:是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

    • 概述:线程是进程中的一个执行单元
    • 特点:

      • 每个线程都有一个独立的内存空间
      • 一个进程可以有多条线程
  • 一个java程序其实就是一个进程,而一个进程一次只能执行一条线程,所以java只有高并发

进程

day09【排序算法、异常、多线程基础】 - 图10

线程

day09【排序算法、异常、多线程基础】 - 图11

进程与线程的区别

  • 进程:有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少有一个线程。
  • 线程:堆空间是共享的,栈空间是独立的,线程消耗的资源比进程小的多。

注意:下面内容为了解知识点

1:因为一个进程中的多个线程是并发运行的,那么从微观角度看也是有先后顺序的,哪个线程执行完全取决于 CPU 的调度,程序员是干涉不了的。而这也就造成的多线程的随机性。

2:Java 程序的进程里面至少包含两个线程,主进程也就是 main()方法线程,另外一个是垃圾回收机制线程。每当使用 java 命令执行一个类时,实际上都会启动一个 JVM,每一个 JVM 实际上就是在操作系统中启动了一个线程,java 本身具备了垃圾的收集机制,所以在 Java 运行时至少会启动两个线程。

3:由于创建一个线程的开销比创建一个进程的开销小的多,那么我们在开发多任务运行的时候,通常考虑创建多线程,而不是创建多进程。

线程调度:

  • 分时调度
    所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。

  • 抢占式调度
    优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
    day09【排序算法、异常、多线程基础】 - 图12

小结

  • java中线程的调度是抢占式调度
  • 一个应用程序可以有多个进程
  • 一个进程可以有多条线程

知识点— Thread类

目标

  • 会使用Thread类的构造方法和常用方法

路径

  • Thread类的构造方法
  • Thread类的常用方法

讲解

Thread类的构造方法

线程开启我们需要用到了java.lang.Thread类,API中该类中定义了有关线程的一些方法,具体如下:

  • public Thread():分配一个新的线程对象,线程名由系统默认给出。

  • public Thread(String name):分配一个指定名字的新的线程对象。

  • public Thread(Runnable target):分配一个带有指定目标新的线程对象。 Runnable任务接口,线程名由系统默认给出

  • public Thread(Runnable target,String name):分配一个带有指定目标新的线程对象并指定名字。

  • ```java

  • 创建Thread类对象,其实就是创建线程对象
  • 创建线程的作用:为了执行代码(任务)
  • 创建线程的方式2种:
    1. 继承Thread类的方式
    2. 实现Runnable接口的方式 ```

Thread类的常用方法

  • public void start():导致此线程开始执行; Java虚拟机调用此线程的run方法。
  • public void run():此线程要执行的任务在此处定义代码。
  • public String getName():获取当前线程名称。
  • public static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
  • public static Thread currentThread():返回对当前正在执行的线程对象的引用。

翻阅API后得知创建线程的方式总共有两种,一种是继承Thread类方式,一种是实现Runnable接口方式,方式一我们上一天已经完成,接下来讲解方式二实现的方式。

小结

知识点—创建线程方式一_继承方式

目标

  • 能够掌握创建线程方式一

路径

  • 创建线程方式一_继承方式

讲解

Java使用java.lang.Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。Java中通过继承Thread类来创建启动多线程的步骤如下:

  1. 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。
  2. 创建Thread子类的实例,即创建了线程对象
  3. 调用线程对象的start()方法来启动该线程

代码如下:

测试类:

  1. public class Demo01 {
  2. public static void main(String[] args) {
  3. //创建自定义线程对象
  4. MyThread mt = new MyThread("新的线程!");
  5. //开启新线程
  6. mt.start();
  7. //在主方法中执行for循环
  8. for (int i = 0; i < 200; i++) {
  9. System.out.println("main线程!"+i);
  10. }
  11. }
  12. }

自定义线程类:

  1. public class MyThread extends Thread {
  2. //定义指定线程名称的构造方法
  3. public MyThread(String name) {
  4. //调用父类的String参数的构造方法,指定线程的名称
  5. super(name);
  6. }
  7. public MyThread() {
  8. //不指定线程的名字,线程有默认的名字Thread-0
  9. }
  10. /**
  11. * 重写run方法,完成该线程执行的逻辑
  12. */
  13. @Override
  14. public void run() {
  15. for (int i = 0; i < 200; i++) {
  16. System.out.println(getName()+":正在执行!"+i);
  17. }
  18. }
  19. }

小结

知识点- 线程执行原理

day09【排序算法、异常、多线程基础】 - 图13

知识点—创建线程的方式二_实现方式

目标

  • 能够掌握创建线程方式二

路径

  • 创建线程方式二_实现方式

讲解

采用java.lang.Runnable也是非常常见的一种,我们只需要重写run方法即可。

步骤如下:

  1. 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
  2. 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
  3. 调用线程对象的start()方法来启动线程。

代码如下:

  1. public class MyRunnable implements Runnable{
  2. @Override
  3. public void run() {
  4. for (int i = 0; i < 20; i++) {
  5. System.out.println(Thread.currentThread().getName()+" "+i);
  6. }
  7. }
  8. }
  1. public class Demo {
  2. public static void main(String[] args) {
  3. //创建自定义类对象 线程任务对象
  4. MyRunnable mr = new MyRunnable();
  5. //创建线程对象
  6. Thread t = new Thread(mr, "小强");
  7. t.start();
  8. for (int i = 0; i < 20; i++) {
  9. System.out.println("旺财 " + i);
  10. }
  11. }
  12. }

通过实现Runnable接口,使得该类有了多线程类的特征。run()方法是多线程程序的一个执行目标。所有的多线程代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。

在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread对象的start()方法来运行多线程代码。

实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是继承Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。

tips:Runnable对象仅仅作为Thread对象的target,Runnable实现类里包含的run()方法仅作为线程执行体。而实际的线程对象依然是Thread实例,只是该Thread线程负责执行其target的run()方法。

Thread和Runnable的区别

如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。

总结:

实现Runnable接口比继承Thread类所具有的优势:

  1. 适合多个相同的程序代码的线程去共享同一个资源。
  2. 可以避免java中的单继承的局限性。
  3. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
  4. 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。

小结

知识点—匿名内部类方式

目标

  • 能够掌握匿名内部类方式

路径

  • 创建匿名内部类方式

讲解

使用线程的内匿名内部类方式,可以方便的实现每个线程执行不同的线程任务操作。

使用匿名内部类的方式实现Runnable接口,重新Runnable接口中的run方法:

  1. public class NoNameInnerClassThread {
  2. public static void main(String[] args) {
  3. // new Runnable(){
  4. // public void run(){
  5. // for (int i = 0; i < 20; i++) {
  6. // System.out.println("张宇:"+i);
  7. // }
  8. // }
  9. // }; //---这个整体 相当于new MyRunnable()
  10. Runnable r = new Runnable(){
  11. public void run(){
  12. for (int i = 0; i < 20; i++) {
  13. System.out.println("张宇:"+i);
  14. }
  15. }
  16. };
  17. new Thread(r).start();
  18. for (int i = 0; i < 20; i++) {
  19. System.out.println("费玉清:"+i);
  20. }
  21. }
  22. }

小结

知识点—扩展

  • Thread类API的介绍 ```java public class MyThread extends Thread { public MyThread() { }

    public MyThread(String name) {

    1. super(name);

    }

    @Override public void run() {

    1. for (int i = 0; i < 100; i++) {
    2. System.out.println(getName()+": i = "+ i);
    3. // 执行1次循环,暂停1秒
    4. try {
    5. Thread.sleep(1000);
    6. } catch (InterruptedException e) {
    7. e.printStackTrace();
    8. }
    9. }

    }

}

public class Test { public static void main(String[] args) throws InterruptedException { / 结论: 1.线程默认名的格式为: Thread-数字 2.主线程的名称为:main / // 继承的方式创建多条线程 /*MyThread mt = new MyThread(“线程1”); mt.start();

  1. MyThread mt2 = new MyThread("线程2");
  2. mt2.start();*/
  3. // 以上2条线程执行的任务是一样的
  4. // 实现的方式创建多条线程
  5. Thread t1 = new Thread(new Runnable() {
  6. @Override
  7. public void run() {
  8. for (int i = 0; i < 100; i++) {
  9. // 获取当前线程对象,再调用getName方法得到线程的名称
  10. System.out.println(Thread.currentThread().getName()+": i = "+ i);
  11. // 执行1次循环,暂停1秒
  12. try {
  13. Thread.sleep(1000);
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. }
  18. }
  19. },"线程A");
  20. t1.start();
  21. for (int j = 0; j < 100; j++) {
  22. // 获取当前线程对象,再调用getName方法得到线程的名称
  23. System.out.println(Thread.currentThread().getName()+": j = "+ j);
  24. Thread.sleep(1000);
  25. }
  26. }

}

  1. -
  2. 继承和实现类的区别
  3. ```java
  4. public class MyRunnable implements Runnable {
  5. @Override
  6. public void run() {
  7. // 线程执行的任务代码
  8. for (int i = 0; i < 100; i++) {
  9. System.out.println(Thread.currentThread().getName()+": i = :"+i);
  10. }
  11. }
  12. }
  13. public class Test2 {
  14. public static void main(String[] args) {
  15. /*
  16. 继承和实现的方式创建线程的区别:
  17. 1.继承只能单继承,实现可以多实现,也可以继承的同时实现,所以可扩展性比较强,解决单继承缺点
  18. 2.适合多个相同的程序代码的线程去共享同一个任务对象。
  19. 3.继承的方式:任务和线程在同一个对象, 实现: 线程和任务是分开
  20.  增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
  21. 4.线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。
  22. */
  23. // 实现
  24. // 创建实现类对象(任务对象
  25. /*MyRunnable mr = new MyRunnable();
  26. // 创建并启动线程
  27. new Thread(mr,"线程1").start();
  28. new Thread(mr,"线程2").start();
  29. new Thread(mr,"线程3").start();
  30. new Thread(mr,"线程4").start();*/
  31. // 继承:
  32. new MyThread("线程A").start();
  33. new MyThread("线程B").start();
  34. new MyThread("线程C").start();
  35. new MyThread("线程D").start();
  36. }
  37. }

总结

  1. - 能够理解冒泡排序的执行原理
  2. 相邻的2个元素进行比较,较大的数据放在后面
  3. - 能够理解选择排序的执行原理
  4. 选中数组的某个位置,拿这个位置上的元素与后面的所有元素进行一一比较,选中元素从头开始选中
  5. - 能够理解二分查找的执行原理
  6. 折半查找: 寻找中间索引对应的元素与要查找的元素进行比较
  7. 如果中间索引对应的元素 == 要查找的元素,就直接返回中间元素的索引
  8. 如果中间索引对应的元素 > 要查找的元素,说明要找的元素在左边,记录右边元素的索引=中间元素索引-1
  9. 如果中间索引对应的元素 < 要查找的元素,说明要找的元素在右边,记录左边元素的索引=中间元素索引+1
  10. - 能够辨别程序中异常和错误的区别
  11. 异常: 可以通过代码进行纠正,纠正后程序可以继续执行
  12. 错误: 无法通过代码进行纠正,只能事先避免
  13. - 说出异常的分类
  14. 编译异常,运行异常
  15. - 列举出常见的三个运行期异常
  16. ArrayIndexOutOfBoundsException
  17. NullPointerException
  18. ArithmeticException
  19. .....
  20. - 能够使用try...catch关键字处理异常
  21. try{
  22. 可能出现异常的代码
  23. }catch(异常类型 变量名){
  24. 处理异常代码,打印信息
  25. }
  26. 如果try中的代码出现异常,就会执行catch中的代码,执行完后程序继续向下执行
  27. 如果try中的代码没有出现异常,就不会执行catch中的代码,执行完后程序继续向下执行
  28. - 能够使用throws关键字处理异常
  29. 声明处理异常,表示当前方法不处理异常,告诉调用者处理异常
  30. - 能够自定义并使用异常类
  31. 创建类继承Exception(编译异常),继承RuntimeException(运行异常)
  32. - 说出进程和线程的概念
  33. 进程:应用程序的可执行单元
  34. 线程:进程的可执行单元
  35. - 能够理解并发与并行的区别
  36. 并发:在一个时间段,交替发生多件事情
  37. 并行:在同一个时刻,同时发生多件事情
  38. - 能够描述Java中多线程运行原理
  39. 抢占式
  40. - 能够使用继承类的方式创建多线程
  41. 1.创建一个类继承Thread
  42. 2.重写Run方法,把线程需要执行的任务代码放在run方法中
  43. 3.创建子类线程对象
  44. 4.调用start方法,启动线程,执行任务
  45. - 能够使用实现接口的方式创建多线程
  46. 1.创建一个实现类实现Runnable接口
  47. 2.重写Run方法,把线程需要执行的任务代码放在run方法中
  48. 3.创建实现类对象
  49. 4.创建Thread线程对象,把实现类对象作为参数传入
  50. 5.调用start方法,启动线程,执行任务
  51. - 能够说出实现接口方式的好处
  52. 1.任务对象可以重复利用
  53. 2.任务和线程是分开的
  54. 3.解决单继承的弊端
  55. 4.线程池中只能存放实现接口方式的线程对象(Runnable,Callable)