6.Java中的多线程与异常

1.异常

根类Throwable体系:

  • Error:严重错误,程序自身已经不能处理的问题,出现的严重错误程序终止运行
  • Exception:编译期异常,这种异常是强制我们使用catch捕获处理或throws抛出给调用者。你遇到这种异常必须进行catch或throws,如果不处理,编译器会报错。
    • RuntimeExeption:Exception的子类,运行时异常,这种异常我们不需要处理,完全由虚拟机接管。
      • NullPointerException:RuntimeExeption的子类,空指针异常

异常处理的五个关键字:try,catch,finally,throw,throws

注:

Throwable Error VirtulMachineError StatckOverFlowError
      OutOfMemoryError
       
  Exception IOException EORException
      FileNotFoundException
    SQLException  
   

ReflectiveOperationException

 ClassNotFoundException
       ......
     ......  
    RuntimeException ArrayIndexOutOfBoundsException
      NullPointerException
      MissingResourceException
      IllegalArgumentException
      UnkownTypeException
      ArrithmeticException
      ClassCastException
      ......

 

  • 对于Error是不需要检查异常的。
  • 对于RuntimeException是可以检查异常也可以不检查异常,是非受检查异常。一般由于程序逻辑错误导致,建议不要检查异常,不检查异常编译也能通过。
  • 对于IOException和SQLException等非RuntimeException异常是必须检查异常的,为受检查异常,不检查异常编译不能通过。

(1)抛出异常 throw:

语法:throw new 异常类名(参数)

例子:throw new NullPointerException(“要访问的数组不存在”)

注意:

  • throw关键字必须放在方法的内部
  • throw关键字后边new的对象必须是Exception或Exception子类对象
  • throw关键字抛出指定的异常对象,我们就必须处理这个异常对象
    • throw关键字后边创建的是RuntimeException或RuntimeException的子类对象,我们可以不处理默认交给JVM处理(打印异常对象,中断程序)
    • throw关键字后边创建的是编译异常,我们处理这个异常,要么throws,要么try...catch

Objects的非空判断:

  Objects.requireNonNull(obj,message),用来判断obj是否为空,为空则抛出异常,信息为message

(2)throws关键字:异常处理的第一种方式,交给别人处理

作用:将异常抛出给方法的调用者处理,最终交给JVM处理-->中断处理

使用格式:在方法声明时使用

 修饰符 返回值类型 方法名(参数列表) throws 异常1,异常2...{
    throw new 异常1("xxx");
    throw new 异常2("xxx");
    ...
 }

注意:

  • 必须写在方法声明处
  • 声明的异常必须是Exception或Exception子类对象
  • 方法内部抛出多个异常,声明处必须也要声明多个异常,如果抛出父异常也抛出子异常则只要声明父异常即可
  • 我们必须处理声明的异常
    • 要么交给方法调用者处理,最终交给JVM
    • 要么使用try...catch自己处理异常

(3)如何获取异常信息

Throwable中定义了一些查看异常的方法:

  • public String getMessage():获取异常描述信息
  • public string toString():获取异常的类型和异常描述信息
  • public void printStackTrace():打印异常的跟踪栈信息并输出到控制台

(4)try,catch,finally

语法:

 try{
    可能出现异常的代码
 }catch(异常类1 变量名){
    异常处理的逻辑  
 }
 ...
 catch(异常类n 变量名){
 
 }finally{
    无论是否出现异常都会执行
 }

注意:

  • finally不能单独使用,必须和try一起使用
  • finally一般用于资源释放
  • 如果finally中有return则永远返回finally中的结果,我们需要避免出现return。
  • 当一个catch捕获来处理异常后就不会调用其他catch处理异常了
  • 多个catch捕获异常,我们先进行子异常捕获处理,如果没有子异常我们就进行父异常捕获处理

(5)异常注意事项

  • 多个异常分别处理:多个try..catch 处理
  • 多个异常一次捕获,多次处理:一个try多个catch处理,子异常在前,父异常在后
  • 多个异常一次捕获,一次处理:一个try...catch,异常为所有异常的父类或本身
  • 子父类异常:
    • 如果父类抛出多个异常,子类重写父类方法时,抛出和父类相同的异常或者是父类异常的子类或者不抛出异常
    • 父类方法没有抛出异常,子类重写父类方法时也可不抛出异常,此时子类产生异常,只能捕获异常,不能声明抛出
 1 class Father{
 2     public void show01() throws NullPointerException,ClassCastException{}
 3     public void show02() throws IndexOutOfBoundsException{}
 4     public void show03() throws IndexOutOfBoundsException{}
 5     public void show04() {}
 6 }
 7 
 8 class Son extends  Father{
 9     //子类重写父类方法时,抛出和父类相同的异常
10     public void show01() throws NullPointerException,ClassCastException{}
11     //子类重写父类方法时,抛出父类异常的子类
12     public void show02() throws IndexOutOfBoundsException{}
13     //子类重写父类方法时,不抛出异常
14     public void show03() throws IndexOutOfBoundsException{}
15     //子类重写父类方法时,父类没有抛出异常,子类自己处理异常
16     public void show04() {
17         try {
18             throw new Exception("出现异常");
19         } catch (Exception e) {
20             e.printStackTrace();
21         }
22     }
23 }

2.自定义异常类

  • 继承Exception处理方式:
    • 第一种:抛出异常给调用者,需要声明
    • 第二种:自己处理
  • 继承RuntimeException处理方式:
    • 直接抛出,不用声明直接交给JVM处理
 1 public class demo02 {
 2 
 3 
 4     //抛出异常给调用者处理,需要声明
 5     public static void testException1() throws TestException {
 6         System.out.println("这是testException1");
 7         throw new TestException("testException1");
 8     }
 9 
10 
11     //抛出异常自己处理
12     public static void testException2() {
13         System.out.println("这是testException2");
14         try {
15             throw new TestException("testException1");
16         } catch (TestException e) {
17             e.printStackTrace();
18             return ;    //用于结束方法
19         }
20 
21     }
22 
23     //运行时异常不用处理和声明,交给JVM处理,最终中断处理
24     public static void testRuntimeException(){
25         System.out.println("这是testRuntimeException");
26         throw new TestRuntimeException("testRuntimeException");
27     }
28 
29     public static void main(String[] args) throws TestException {
30 
31         demo02.testException1();
32         demo02.testException2();
33         demo02.testRuntimeException();
34      
35     }
36 }

TestException

 1 public class TestException extends Exception {
 2 
 3     public TestException() {
 4         super();
 5     }
 6 
 7     public TestException(String message) {
 8         super(message);
 9     }
10 }

TestRuntimeException

 1 public class TestRuntimeException extends RuntimeException{
 2 
 3     public TestRuntimeException() {
 4         super();
 5     }  
 6 
 7     public TestRuntimeException(String message) {
 8         super(message);
 9     }
10 }

补充:Java常见异常

1. RuntimeException子类:

序号异常名称异常描述
1 java.lang.ArrayIndexOutOfBoundsException 数组索引越界异常。当对数组的索引值为负数或大于等于数组大小时抛出。
2 java.lang.ArithmeticException  算术条件异常。譬如:整数除零等。
3 java.lang.SecurityException  安全性异常
4 java.lang.IllegalArgumentException 非法参数异常
5 java.lang.ArrayStoreException  数组中包含不兼容的值抛出的异常 
6 java.lang.NegativeArraySizeException 数组长度为负异常 
7 java.lang.NullPointerException 空指针异常。当应用试图在要求使用对象的地方使用了null时,抛出该异常。譬如:调用null对象的实例方法、访问null对象的属性、计算null对象的长度、使用throw语句抛出null等等。

 2.IOException

序号异常名称异常描述
1 IOException 操作输入流和输出流时可能出现的异常
2 EOFException 文件已结束异常
3 FileNotFoundException 文件未找到异常

 3. 其他

序号异常名称异常描述
1 ClassCastException 类型转换异常类
2 ArrayStoreException 数组中包含不兼容的值抛出的异常
3 SQLException 操作数据库异常类
4 NoSuchFieldException 字段未找到异常
5 NoSuchMethodException 方法未找到抛出的异常
6 NumberFormatException 字符串转换为数字抛出的异常
7 StringIndexOutOfBoundsException 字符串索引超出范围抛出的异常
8 IllegalAccessException 不允许访问某类异常
9 InstantiationException

 当应用程序试图使用Class类中的newInstance()方法创建

一个类的实例,而指定的类对象无法被实例化时,抛出该异常

10 java.lang.ClassNotFoundException 找不到类异常。当应用试图根据字符串形式的类名构造类,而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常。


3.多线程

(1)Thread类:

构造方法:

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

常用方法:

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

MyThread类:

 1 public class MyThread extends Thread {
 2 
 3     public MyThread() {
 4     }
 5 
 6     public MyThread(String name) {
 7         super(name);
 8     }
 9 
10     @Override
11     public void run() {
12 
13         System.out.println(Thread.currentThread().getName());
14     }
15 }

demo01类:

 1 public class demo01 {
 2 
 3 
 4     public static void main(String[] args) {
 5 
 6         //1.修改线程名称方法一
 7         MyThread thread1 = new MyThread();
 8         thread1.setName("thread1");
 9         thread1.start();  //thread1
10 
11         //修改线程名称方法二
12         MyThread thread2 = new MyThread("thread2");
13         thread2.start();    //thread2
14 
15 
16         for (int i = 0; i < 60; i++) {
17             System.out.println(i);
18 
19             //2.使当前线程睡眠1秒执行一次
20             try {
21                 Thread.sleep(1000);
22             } catch (InterruptedException e) {
23                 e.printStackTrace();
24             }
25         }
26 
27 
28     }
29 }

(2)Runnable接口:

实现步骤:

  1. 创建一个Runnable接口的实现类
  2. 在实现类中重写Runnable接口的run方法,设置线程任务
  3. 创建一个Runnable接口的实现类对象
  4. 创建Thread类对象,构造方法中传递Runnable接口实现类对象
  5. 调用start方法,开启新线程执行run方法

RunnableImpl类:

1 public class RunnableImpl implements Runnable {
2     @Override
3     public void run() {
4 
5         for (int i = 0; i < 60; i++) {
6             System.out.println(Thread.currentThread().getName() + ":" + i);
7         }
8     }
9 }

demo02类:

 1 public class demo02 {
 2 
 3     public static void main(String[] args) {
 4 
 5         RunnableImpl runnable = new RunnableImpl();
 6         new Thread(runnable).start();
 7 
 8         for (int i = 0; i < 60; i++) {
 9 
10             System.out.println(Thread.currentThread().getName() + ":" + i);
11 
12         }
13     }
14 }

(3)实现Runnable接口和继承Thread类比较

实现Runnable接口优点:

  • 避免了单继承的局限性
  • 增强了程序的扩展性,降低了程序的耦合性:把设置线程任务和开启线程分离

(4)匿名内部类实现线程的创建

优点:简化代码

 1 public class demo03 {
 2 
 3     public static void main(String[] args) {
 4 
 5         //匿名Thread
 6         new Thread() {
 7             @Override
 8             public void run() {
 9 
10                 for (int i = 0; i < 20; i++) {
11                     System.out.println("Thread:" + i);
12                 }
13             }
14         }.start();
15 
16         //匿名Runnable
17         new Thread(new Runnable() {
18             @Override
19             public void run() {
20                 for (int i = 0; i < 20; i++) {
21                     System.out.println("Runnable:" + i);
22                 }
23             }
24         }).start();
25     }
26 }

4.并发和并行

并发:多个任务在同一时间段执行,任务交替执行

并行:多个任务在同一时刻执行,任务同时执行

线程:进程中的一个执行单元

进程:内存中运行的一个应用程序

(1)线程安全

1.同步代码块:

synchronized (同步锁){
    需要同步操作的代码(访问了共享数据的代码)
 }

保证了线程安全,但是频繁的判断锁,释放锁和获取锁导致程序的效率降低

RunableImpl类:this指RunnableImpl创建的对象

 1 public class RunnableImpl implements Runnable {
 2 
 3     private int num = 100;
 4 
 5     @Override
 6     public void run() {
 7 
 8         while (true){
 9 
10             synchronized (this){
11                 if (num > 0){
12                     try {
13                         Thread.sleep(10);
14                     } catch (InterruptedException e) {
15                         e.printStackTrace();
16                     }
17                     System.out.println(Thread.currentThread().getName() + "获得当前数字为:" + num);
18                     num--;
19                 }
20             }
21         }
22 
23     }
24 }

demo04类:

 1 public class demo04 {
 2 
 3     public static void main(String[] args) {
 4 
 5         RunnableImpl runnable = new RunnableImpl();
 6         new Thread(runnable).start();
 7         new Thread(runnable).start();
 8         new Thread(runnable).start();
 9 
10 
11     }
12 }

2.同步方法:

 public synchronized void method(){
    可能会产生线程安全的代码
 }

保证了线程安全,但是频繁的判断锁,释放锁和获取锁导致程序的效率降低

RunnableImpl类:this指RunnableImpl创建的对象

 1 public class RunnableImpl implements Runnable {
 2 
 3     private int num = 100;
 4 
 5     @Override
 6     public void run() {
 7 
 8         while (true){
 9 
10             fun();
11         }
12 
13     }
14 
15     public synchronized void fun(){
16         if (num > 0){
17             try {
18                 Thread.sleep(10);
19             } catch (InterruptedException e) {
20                 e.printStackTrace();
21             }
22             System.out.println(Thread.currentThread().getName() + "获得当前数字为:" + num);
23             num--;
24         }
25     }
26 }

demo05:

 1 public class demo05 {
 2 
 3     public static void main(String[] args) {
 4 
 5         RunnableImpl runnable = new RunnableImpl();
 6         new Thread(runnable).start();
 7         new Thread(runnable).start();
 8         new Thread(runnable).start();
 9 
10     }
11 }

3.静态同步方法:

 public static synchronized void method(){
    可能会产生线程安全的代码,只能调用静态属性
 }

RunnableImpl:静态方法的锁对象是本类的class属性-->class文件对象

 1 public class RunnableImpl implements Runnable {
 2 
 3     private static int num = 100; //静态属性
 4 
 5     @Override
 6     public void run() {
 7 
 8         while (true){
 9 
10             fun();
11         }
12 
13     }
14 
15     //静态方法
16     public static synchronized void fun(){
17         if (num > 0){
18             try {
19                 Thread.sleep(10);
20             } catch (InterruptedException e) {
21                 e.printStackTrace();
22             }
23             System.out.println(Thread.currentThread().getName() + "获得当前数字为:" + num);
24             num--;
25         }
26     }
27 }

另一种写法:

 1     //静态方法
 2     public static void fun() {
 3         synchronized (RunnableImpl.class) {
 4             if (num > 0) {
 5                 try {
 6                     Thread.sleep(10);
 7                 } catch (InterruptedException e) {
 8                     e.printStackTrace();
 9                 }
10                 System.out.println(Thread.currentThread().getName() + "获得当前数字为:" + num);
11                 num--;
12             }
13         }
14 
15     }

demo06:

 1 public class demo06 {
 2 
 3     public static void main(String[] args) {
 4 
 5         RunnableImpl runnable = new RunnableImpl();
 6         new Thread(runnable).start();
 7         new Thread(runnable).start();
 8         new Thread(runnable).start();
 9 
10     }
11 }

(2)Lock锁

 java.util.concurrent.locks.Lock 机制提供了比 synchronized 代码块和方法更加广泛的锁定操作。

Lock锁也称为同步锁

  • Lock是一个接口在1.5版本后出现
  • public void lock():加同步锁
  • public void unlock():释放同步锁
  • ReentrantLock实现了Lock接口
 1 public class RunnableImpl implements Runnable {
 2 
 3     private int num = 100; //静态属性
 4 
 5     private final ReentrantLock lock = new ReentrantLock();
 6 
 7     @Override
 8     public void run() {
 9 
10         while (true) {
11 
12             //加锁
13             lock.lock();
14 
15             if (num > 0) {
16                 try {
17                     Thread.sleep(10);
18                     System.out.println(Thread.currentThread().getName() + "获得当前数字为:" + num);
19                     num--;
20                 } catch (InterruptedException e) {
21                     e.printStackTrace();
22                 } finally {
23 
24                     //释放锁
25                     lock.unlock();
26                 }
27             }
28         }
29     }
30 }

(3)线程的状态

  • 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
  • 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
  • 阻塞(BLOCKED):表示线程阻塞于锁。
  • 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
  • 计时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
  • 死亡(TERMINATED):表示该线程已经执行完毕。

(4)线程常用方法:

  • void wait():当前线程进入等待状态
  • void wait(long timeout):当前线程进入等待状态后,等待指定时间后自动唤醒
  • void notify():唤醒等待的单个线程,随机唤醒一个
  • void notifyAll():唤醒等待的所有线程

(5)线程通信案例:单生产者和单消费者问题

产品类:

 1 public class Product {
 2 
 3     private String name;
 4     private Deque<String> deque;
 5 
 6 
 7     public Product(String name, Deque<String> deque) {
 8         this.name = name;
 9         this.deque = deque;
10     }
11 
12     public Deque<String> getDeque() {
13         return deque;
14     }
15 
16     public void setDeque(Deque<String> deque) {
17         this.deque = deque;
18     }
19 
20     public String getName() {
21         return name;
22     }
23 
24     public void setName(String name) {
25         this.name = name;
26     }
27 
28 
29     @Override
30     public String toString() {
31         return "Product{" +
32                 "name='" + name + '\'' +
33                 ", deque=" + deque +
34                 '}';
35     }
36 }

单生产者Producer类:

 1 public class Producer implements Runnable {
 2 
 3     private Product product;
 4     private int productID = 1;
 5 
 6     public Producer(Product product, int productID) {
 7         this.product = product;
 8         this.productID = productID;
 9     }
10 
11     public Producer(Product product) {
12         this.product = product;
13     }
14 
15     @Override
16     public void run() {
17 
18         while (true) {
19             synchronized (product) {
20 
21                 //当前产品数量
22                 int num = product.getDeque().size();
23 
24                 if (num >= 10) {
25 
26                     try {
27                         product.wait();
28                     } catch (InterruptedException e) {
29                         e.printStackTrace();
30                     }
31                 }
32 
33                 num = product.getDeque().size();
34 
35                 String name =  productID + "号产品";
36                 product.getDeque().add(name);
37                 productID++;
38 
39                 System.out.println("+++++当前库存" + num + "件产品,正在生成产品名为" + name + "的产品");
40 
41 
42                 try {
43                     Thread.sleep(100);
44                 } catch (InterruptedException e) {
45                     e.printStackTrace();
46                 }
47 
48 
49 
50                 System.out.println("++++++++++++++++已经生产好了产品名为" + name + "的产品,当前库存" + (num + 1) + "件产品");
51                 product.notify();
52 
53             }
54         }
55     }
56 }

单消费者Consumer类:

 1 public class Consumer implements Runnable {
 2 
 3     private String name;
 4     private Product product;
 5 
 6     public Consumer(String name, Product product) {
 7         this.name = name;
 8         this.product = product;
 9     }
10 
11     @Override
12     public void run() {
13 
14         while (true) {
15 
16             synchronized (product) {
17 
18 
19                 int num = product.getDeque().size();
20 
21                 if (num <= 0) {
22                     try {
23                         product.wait();
24                     } catch (InterruptedException e) {
25                         e.printStackTrace();
26                     }
27                 }
28 
29                 num = product.getDeque().size();
30 
31                 String name = product.getDeque().getFirst();
32 
33                 System.out.println("-----消费者" + this.name + "正在消费产品名为" + name + "的产品");
34 
35                 product.getDeque().remove();
36 
37                 try {
38                     Thread.sleep(50);
39                 } catch (InterruptedException e) {
40                     e.printStackTrace();
41                 }
42 
43                 System.out.println("--------------消费者" + this.name + "已经消费了产品名为" + name + "的产品,剩余库存" + (num - 1));
44                 product.notify();
45 
46 
47             }
48         }
49     }
50 }

测试代码:

 1 public class day01 {
 2 
 3     public static void main(String[] args) {
 4 
 5         Deque<String> deque = new ArrayDeque<>();
 6         Product product = new Product("产品类别1",deque);
 7 
 8         new Thread(new Producer(product)).start();
 9         new Thread(new Consumer("Consumer1", product)).start();
10 
11 
12     }
13 }

5.线程池

  • JDK1.5之后提供的线程池
  • 顶层接口java.util.concurrent.Executor作为执行线程的工具而不是真正的线程池。
  • java.util.concurrent.ExecutorService为线程池接口继承Executor接口。
  • java.util.concurrent.Executors为线程池的工厂类。

(1)线程池常用方法

  • Executors工厂类中:
    • public static ExecutorService new FixedThreadPool(int nThreads):创建一个可以重用的固定线程数的线程池
  • ExecutorService线程池接口:
    • Future<?> submit(Runnable task):提交一个Runnable任务执行。

    • void shutdown():销毁线程池。

(2)执行步骤:

  1. 使用Executors工厂类调用FixedThreadPool方法来创建一个线程池。
  2. 编写一个实现Runnable接口的实现类,重写run方法。
  3. 调用ExecutorService类方法submit传入Runnable的实现类并执行。
  4. 最终销毁线程池ExecutorService类方法shutdown,现实中不一定需要。

 RunnableImpl:

 1 public class RunnableImpl implements Runnable {
 2 
 3     private int num = 2;
 4     @Override
 5     public void run() {
 6         while (num-- > 0) {
 7 
 8             System.out.println(Thread.currentThread().getName());
 9             try {
10                 Thread.sleep(100);
11             } catch (InterruptedException e) {
12                 e.printStackTrace();
13             }
14         }
15     }
16 }

main:

 1 public class demo01 {
 2 
 3     public static void main(String[] args) {
 4 
 5         //创建
 6         ExecutorService es = Executors.newFixedThreadPool(10);
 7 
 8         //执行
 9         es.submit(new RunnableImpl());  //pool-1-thread-1
10         es.submit(new RunnableImpl());  //pool-1-thread-2
11         es.submit(new RunnableImpl());  //pool-1-thread-3
12         es.submit(new RunnableImpl());  //pool-1-thread-4
13 
14         //销毁
15         es.shutdown();
16 
17     }
18 }

 

posted @ 2019-12-30 00:35  All_just_for_fun  阅读(429)  评论(0)    收藏  举报