JAVA基础-叁-异常&多线程

JAVA基础-叁


异常机制

Exception:程序运行过程中产生的异常

StackOverflowError

栈溢出

public void a() {  b(); }
public void b() {  a(); }
public static void main(String[] args) {
   a();
}

ArithmeticException

运算错误

 public static void main(String[] args) {
    System.out.println(11/0);
}

异常可以分为三类

  • 检查性异常: 最具代表的检查性异常是用户错误或者问题引起的异常,例如打开一个不存在的文件,在编译时通常会有提示
  • 运行时异常: 运行时异常是可能被程序员避免的异常,可以在编译时被忽略。
  • 错误\color{red}{error}: 错误不是异常,脱离程序员控制的问题
    Throwable

Error

Error类对象由Java虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关。

VirtualMachineError

Java虚拟机运行错误
当JVM不再有继续执行操作所需的内存资源时,将出现OutOfMemoryError。这些错误发生时,JVM一般会选择线程终止

Others

还有一些虚拟机试图执行应用时,如NoClassDefFoundError,LinkageError。这些错误是不可查的,因为它们在应用程序的控制和处理能力之外。


Exception

RuntimeException

运行时异常

  • ArrayIndexOutOfBoundsException
  • NullPointerException
  • ArithmeticException
  • MissingResourceException
  • ClassNotFoundException

这些都是可以避免的,一般是由程序逻辑错误引起的。


异常处理机制

五个关键字

  1. try
  2. catch
  3. finally
  4. throw
  5. throws
    Example:
try {
    System.out.println(11 / 0);   //遇到Exception直接跳转catch代码块,不继续执行下面的程序
} catch (ArithmeticException e) {
    e.printStackTrace();    //打印错误的栈信息
    //...
} catch (StackOverflowError e) {
   //...
} finally {
    // 一定会执行,做一些相关的善后处理
}

try catch是必须的,finally是可选的,通常用于一些IO,资源的关闭。想要捕捉到所有的异常可以直接:

try { 
  //...
} catch (Throwable t) {
  //...
}

主动抛出异常

  • in try-catch block
int b = 0;
try {
  if (b == 0) {
    throw new ArithmeticException();  // or new ArithmeticException("divide by zero"), can get it by getMessage() method
  }
  //...
} catch (ArithmeticException e) {
  //...
}
  • in method
// 假设在这个方法中处理不了这个异常,则可以让这个方法抛出异常
public void test(int a, int b) throws ArithmeticException {  //"throws Exception" represents this method may throw Exception
  if (b == 0) {
    throw new ArithmeticException();
  }
  System.out.println(a / b);
}

自定义异常

我们可以自定义异常,需要继承自Exception。

public class MyException extends Exception {
    private int number;
    public MyException(int index) {
      this.number = index;
    }
    
    @Override
     public String toString() {
        return "MyException{ error number: " + number + "}";
     }
}

下图为捕获到MyException调用printStackTrace()的输出。


printStackTrace()

处理异常相关建议

  • 在多重catch块后面,可以加一个catch (Exception e) 来处理可能会被遗漏的异常
  • 对于不确定的代码,也可以加上try-catch处理潜在的异常
  • 尽量去处理异常,做一些兜底
  • 尽量添加finally语句快去释放占用的资源,eg:IO,Scanner...

多线程

线程简介

普通方法调用和多线程

进程

Process
在操作系统中运行的程序就是进程,一个进程可以有多个线程,如视频中同时听声音,看图像,看弹幕。程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。进程是执行程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位。

线程

Thread
线程是CPU调度和执行的单位,是独立的执行路径。在程序运行时,即使没有自己创建线程,后台也会有多个线程,比如主线程,gc线程。main()称之为主线程,为系统的入口,用于执行整个程序。
在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的。对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制。
线程会带来额外的开销,如cpu调度时间,并发控制开销。每个线程在自己的工作内存交互,内存控制不当会造成数据不一致


继承Thread类

线程创建

  • 继承Thread类
  • 实现Runnable接口
  • 实现Callable接口

Thread

线程是程序中执行的线程。Java虚拟机允许应用程序同时执行多个线程。创建一个线程有两个方法,一个是将一个类声明为Thread类的子类,重写它的run方法,然后启动它。线程开启不一定立即执行,由cpu调度。

实现Runnable

   public class MyThread implements Runnable {
       @Override
        public void run() { ... }
  }
  MyThread td = new MyThread();  //创建实现类对象
  Thread thread = new Thread(td);  // 创建代理类对象
  thread.start();                                 // 启动

实现Callable接口

实现Callable接口,需要返回值类型,重写call方法,需要抛出异常。

  • 创建目标对象
  • 创建执行服务
  ExecutorService service = Executors.newFixedThreadPool(1);
  • 提交执行
  Future<Boolean> result = service.submit(t1);
  • 获取结果
  boolean r1 = result.get();
  • 关闭服务
  service.shutdownNow();

Lamda表达式

Lamda是希腊字母表排序中第十一位的字母。Lamda表达式主要为了让代码更简洁,避免匿名内部类定义过多,它的实质属于函数式编程的概念。

(params) -> expression [表达式]
(params) -> statement [语句]
(params) -> { statements}

函数式接口

定义:

  • 任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口
  • 对于函数式接口,我们可以通过lamda表达式来创建该接口的对象

静态代理

(设计模式之一)


WeddingCompany

上面的例子中的WeddingCompany类与You类的代理关系其实和Thread类与Runnable类的关系是一样的。


线程状态

五大状态

五大状态

线程状态

线程方法

停止线程

  • 不推荐使用JDK提供的stop(), destroy()方法(已废弃)
  • 推荐线程自己停止下来,建议使用一个标志位作为终止变量,当flag=false,则终止线程运行
public class TestStop implements Runnable {
  private boolean flag = true;  // 1.线程中定义线程体使用的标识
  @Override
  public void run() {
    // 2.线程体使用该标识
    while (flag) { ... }
  }
  
  public void stop() { // 3.对外提供方法改变标识
    this.flag = false;
  }
}

线程休眠

  • sleep(time) 指定当前线程阻塞的毫秒数
  • sleep 存在异常InterruptedException
  • sleep时间达到后线程进入就绪状态
  • sleep可以模拟网络延时,倒计时等
  • 每一个对象都有一个锁,sleep不会释放锁
        try {
              Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } 

线程礼让

Thread.yield()
  • 礼让线程。让当前正在执行的线程暂停,但是不阻塞
  • 将线程从运行状态转为就绪状态
  • 让cpu重新调度,礼让不一定成功

线程强制执行

thread.join();
  • 合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞(可以想象成插队)

线程状态

Thread State
Thread.State state = thread.getState();

Ps: thread执行完成后不可以再次start

线程优先级

Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。

  • 线程的优先级用数字来表示,范围是 [1, 10]
    • Thread.MIN_PRIORITY = 1;
    • Thread.MAX_PRIORITY = 10;
    • Thread.NORM_PRIORITY = 5;
  • 使用以下方式改变或获取优先级
getPriority()
setPriority(int xxx); 
  • 低优先级只是代表获得CPU调度的概率低,具体还是要看CPU。所以仍然有可能出现性能倒置现象(优先级高的线程在等待,优先级低的线程在运行)

守护线程(daemon)

  • 线程分为用户线程和守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
  • 比如:后台记录操作日志,监控内存,垃圾回收等...
thread.setDaemon(true);  // 默认false表示是用户线程

线程同步

多个线程操作同一个资源

  • 并发: 同一个对象被多个线程同事操作
  • 处理多线程问题时,多个线程访问同一个对象,某些线程还想修改该对象,这时候我需要线程同步
  • 线程同步其实是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程才可以使用
  • 由于同一进程的多个线程共享同一块存储空间,这样会导致访问冲突问题,为了保证数据在方法中被访问时的正确性,我们需要使用锁机制synchronized。当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题
    • 一个线程持有锁会导致其他所有需要此锁的线程挂起
    • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
    • 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题。

同步方法与同步块

synchronized 关键字可以实现同步,共有两种用法: synchronized方法和synchronized块

  • 同步方法
public synchronized void method(int arg) {...}
  • synchronized方法控制对"对象”的访问,每个对象对应一把锁,每个synchronized方法都必须要活的调用该方法的对象的锁才能执行,否则线程会阻塞。
  • 方法一旦执行,就该独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。
    若将一个大的方法申明为synchronized将会影响效率。
  • 方法里需要修改的内容才需要锁,只是读取的话不需要锁。
  • 同步块
 synchronized (Obj) { ... }
  • Obj 称之为同步监视器,它可以是任何对象,但是推荐使用共享资源作为同步监视器。
  • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this, 即这个对象本身,或者是class [反射]。
  • 同步监视器的执行过程
    1. 第一个线程访问,锁定同步监视器,执行块中代码
    2. 第二个线程方法,发现同步监视器被锁定,无法访问
    3. 第一个线程访问完毕,解锁同步监视器
    4. 第二个线程访问,发现同步监视器没有锁,然后锁定并访问

CopyOnWriteArrayList

JUC<java.util.concurrent>包里的线程安全的集合类型中的一个类


CopyOnWriteArrayList
  • volatile 确保可见性
  • transient 确保有序性

死锁

多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行。
某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题。
产生死锁的四个必要条件如下。我们只要破坏其中一个或者多个条件就可以避免死锁的发生。

  • 互斥条件: 一个资源每次只能被一个进程使用
  • 请求与保持条件: 一个进程因请求资源而阻塞时,对已获得的资源保持不释放
  • 不剥夺条件: 进程已获得的资源,在未使用之前,不能强行剥夺
  • 循环等待条件: 若干进程之间形成一种头尾相接的循环等待资源关系。

Lock (锁)

  • 从JDK 5.0 开始,Java提供了更强大的线程同步机制---通过显式定义同步锁对象来实现同步,同步锁使用Lock对象充当。(synchronized为隐式定义同步锁,出了作用域自动释放)
  • java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只有一个线程对Lock对象加锁,线程开始访问共享资源之前应该先获得Lock对象。
  • ReentrantLock(可重用锁) 类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁,释放锁。


    ReentrantLock

对比

  • Lock需要手动开启和关闭,为显式锁。synchronized为隐式锁,出了作用域自动释放。
  • Lock只有代码块锁,synchronized有代码块锁和方法锁。
  • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
  • 优先使用顺序: Lock > 同步代码块(已经进入了方法体,分配了相应资源) > 同步方法(在方法体之外)

线程协作

生产者消费者问题

线程通信

Method1: 并发协作模型 --- 管程法

  • 生产者:负责生产数据的模块(可能是方法,对象,线程,进程)
  • 消费者:负责处理数据的模块(可能是方法,对象,线程,进程)
  • 缓冲区: 消费者不能直接使用生产者的数据,他们之间有个缓冲区,生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
    Method2: 信号灯法
    通过标志位进行判断

线程池

经常创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响很大。我们可以提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,以避免频繁创建销毁,实现重复利用。类似生活中的公共交通工具。

  • 好处
  • 提高响应速度(减少了创建新线程的时间)
  • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
  • 便于线程管理
    • corePoolSize:核心池的大小
    • maximumPoolSize:最大线程数
    • keepAliveTime:线程没有任务时最多保持多长时间会终止

JDK 5.0起提供了线程池相关API: ExecutorService和Executors.

  • ExecutorService: 真正的线程池接口, 常见子类ThreadPoolExecutor。
    • void execute(Runnable r):执行任务/命令, 没有返回值, 一般用来执行Runnable
    • <T> Future<T> submit(Callable<T> task): 执行任务,有返回值,一般用来执行Callable
    • void shutdown() 关闭线程池
  • Executors:工具类,线程池的工厂类,用于创建并返回不同类型的线程池。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,084评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,623评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,450评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,322评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,370评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,274评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,126评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,980评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,414评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,599评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,773评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,470评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,080评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,713评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,852评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,865评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,689评论 2 354

推荐阅读更多精彩内容

  • J2SE 基础 八种基本数据类型的大小,以及他们的封装类。 八种基本数据类型,int ,double ,long ...
    镜中无我阅读 122评论 0 0
  • J2SE 基础 八种基本数据类型的大小,以及他们的封装类。 八种基本数据类型,int ,double ,long ...
    第四风111阅读 1,247评论 3 23
  • 目录:一、Java 基础二、容器三、多线程四、反射五、对象拷贝六、异常七、设计模式八、网络编程 欢迎评论留言,文章...
    mumuxi_阅读 773评论 0 13
  • “简单不先于复杂,而是在复杂之后.” —— Alan Perlis Java异常 异常指不期而至的各种状况,如:文...
    白衬衫少年阅读 274评论 0 0
  • 在经过一次没有准备的面试后,发现自己虽然写了两年的android代码,基础知识却忘的差不多了。这是程序员的大忌,没...
    猿来如痴阅读 2,839评论 3 10