6.Java中的多线程与异常

1.异常

根类Throwable体系:java

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

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

(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是否为空,为空则抛出异常,信息为messageide

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

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

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

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

注意:ui

  • 必须写在方法声明处
  • 声明的异常必须是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 }
相关文章
相关标签/搜索