JavaSE-基础篇学习笔记分享(三)

152 阅读13分钟

笔记整理自B站尚硅谷JavaSE视频,其中可能有记录内容不正确,望诸位指出

作者:Vison


23、异常

Error:

Java虚拟机无法解决的严重问题,

如:JVM系统内部错误,资源耗尽等严重情况,比如StackOverflowError和OOM。一般不编写针对性的代码进行处理

Exception:

其他因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如:
空指针访问
试图读取不存在的文件
网络连接中断
数组角标越界

try-catch-finally/try-catch

异常处理是通过tru-catch-finally语句实现的

try{  
   ....//可能产生异常的代码  
}catch(ExceptionName e ){ 
   ...//当前产生ExcptionName类型的异常时处置措施
}finally{
    //无论是否发生异常,都无条件执行的语句
}

try

捕获异常的第一步时用try{...}语句块选定捕获异常的范围,将可能出现异常的代码放在try语句块中

catch

在catch语句块中是对异常对象进行处理的代码,每个try语句块可以伴随一个或者多个catch语句,用于处理可能传声的不同类型的异常对象

捕获异常的有关信息

与其他对象一样,可以访问一个异常对象的成员变量或调用它的方法。

getMessage():获取异常信息,返回字符串
printStackTrace():获取异常类名和异常信息,以及一场出现在程序中的位置,返回void。

finally

捕获异常的最后一步是finally语句为异常处理提供一个统一的出口,使得在控制流转到程序的其他部分以前,能够对程序的状态作统一的管理

不论在try代码块中是否发生了异常事件,catch语句是否执行,catch语句是否有异常,catch语句是否有return,finally块的语句都会执行

finally语句和catch语句是任意的

throws

声明抛出异常是java中处理异常的第二种方式:

1.如果一个方法(中的语句执行时)可能生成某种异常,但是并不能确定如何处理这种异常,则此方法应显示地声明抛出异常,表明该方法将不对这些异常进行处理。而由该方法的调用者负责处理。
2.在方法声明中用throws语句可以声明抛出异常的列表,throws后面的异常类型口以是方法中产生的异常类型,也可以是它的父类。

自定义异常

一般地,用户自定义异常类都是RuntimeException的子类。

自定义异常类通常需要编写几个重载的构造器。
自定义异常需要提供serialVersionUID
自定义的异常通过throw抛出。
自定义异常最重要的是异常类的名字,当异常出现时,可以根据名字判断异常类型。

image.png


24、线程的通信

wait() 与 notify() 和 notifyAll()

wait(): 令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行。
notify(): 唤醒正在排队等待同步资源的线程中优先级最高者结束等待
notifyAll(): 唤醒正在排队等待资源的所有线程结束等待。
这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报java.lang.lllegalMonitorStateException异常。
因为这三个方法必须有锁对象调用,而任意对象都可以作为synchronized的同步锁,因此这三个方法只能在Object类中声明。

程序、进程、线程

程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。

进程(process)是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。--生命周期人如:运行中的QQ,运行中的MP3播放器人程序是静态的,进程是动态的进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域

线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。若一个进程同一时间并行执行多个线程,就是支持多线程,线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小

一个进程中的多个线程共享相同的内存单元/内存地址空间→它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患。

使⽤线程池

背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。好处:
提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次都创建)便于线程管理
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止

线程池相关API

JDK5.0起提供了线程池相关API:ExecutorService和 Executors
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor

void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
Future submit(Callabletask):执行任务,有返回值,一般又来执行Callable
void shutdown():关闭连接池

Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池 Executors.newCachedThreadPool:创建一个可根据需要创建新线程的线程池Executors.newFixedThreadPool(n);创建一个可重用固定线程数的线程池 Executors.newSingleThreadExecutor):创建一个只有一个线程的线程池 Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。 让天下没有难学的技本

实现Callable接⼝

  1. 与使用Runnable相比,Callable功能更强大些
  2. 相比run()方法,可以有返回值
  3. 方法可以抛出异常支持泛型的返回值
  4. 需要借助FutureTask类,比如获取返回结果

Future接口

  1. 可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
  2. FutrueTask是Futrue接口的唯一的实现类
  3. FutureTask 同时实现了Runnable.Future接口。它既可以作为
  4. Runnable被线程执行,又可以作为Future得到Callable的返回值

线程的创建⽅法⼀:Thread

构造器

Thread():创建新的Thread对象
Thread(String threadname):创建线程并指定线程实例名
Thread(Runnable target):指定创建线程的目标对象,它实现了Runnable接口中的run方法
Thread(Runnable target, String name):创建新的Thread对象

1.如果自己手动调用run()方法,那么就只是普通方法,没有启动多线程模式。
2.run()方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU调度决定。
3.想要启动多线程,必须调用start方法。
4、一个线程对象只能调用一次startO方法启动,如果重复调用了,则将抛出以上的异常“lllegalThreadStateException”。

Thread类的有关方法(1)

  • void start():启动线程,并执行对象的run()方法
  • run():线程在被调度时执行的操作 String getName():返回线程的名称
  • void setName(String name):设置该线程名称
  • static Thread currentThread(): 返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable实现类

Thread类的有关方法(2)

  • static void yield():线程让步 1、暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
    2、若队列中没有同优先级的线程,忽略此方法
  • join():当某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到 join()方法加入的 join 线程执行完为止 1、低优先级的线程也可以获得执行
  • static void sleep(long millis):(指定时间:毫秒)
    1、令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。
    2、抛出InterruptedException异常
  • stop():强制线程生命期结束,不推荐使用
  • boolean isAlive():返回boolean,判断线程是否还活着

线程的创建⽅法⼆:实现Runnable接⼝

1)定义子类,实现Runnable接口。
2)子类中重写Runnable接口中的run方法。3)通过Thread类含参构造器创建线程对象。
4)将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
5)调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法

继承方式和实现方式的联系与区别
public class Thread extends Object implements Runnable

区别:

继承Thread:线程代码存放Thread子类run方法中。
实现Runnable:线程代码存在接口的子类的run方法。 实现方式的好处: 避免了单继承的局限性
多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。

线程的优先级

线程的优先级等级

MAX PRIORITY: 10
MIN PRIORITY: 1
NORM PRIORITY:5
涉及的方法
getPriority():返回线程优先值
setPriority(int newPriority):改变线程的优先级 说明
线程创建时继承父线程的优先级
低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用

线程的⽣命周期

JDK中用ThreadState类定义了线程的几种状态
要想实现多线程,必须在主线程中创建新的线程对象。
Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:

新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态

就绪: 处于新建状态的线程被start0后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源

运行: 当就绪的线程被调度并获得CPU资源时.便进入运行状态,run()方法定义了线程的操作和功能

阻塞: 在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻寒状态

死亡: 线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束

线程的同步

synchronized的使用方法

1、同步代码块
synchronized(对象){
//需要被同步的代码
}

2synchronized还可以放在方法声明中,表示整个方法为同步方法,
public synchronized void show(String name){
  ....
}

同步锁机制:

在《Thinking in Java》中,是这么说的:对于并发工作,你需要某种方式来防止两个任务访问相同的资源(其实就是共享资源竞争)。 防止这种冲突的方法就是当资源被一个任务使用时,在其上加锁。第一个访问某项资源的任务必须锁定这项资源,使其他任务在其被解锁之前,就无法访问它了,而在其被解锁之时,另一个任务就可以锁定并使用它了。

synchronized的锁是什么?

任意对象都可以作为同步锁。所有对象都自动含有单一的锁(监视器)。
同步方法的锁:静态方法(类名.class)、非静态方法(this)
同步代码块:自己指定,很多时候也是指定为this或类名.class
注意:
必须确保使用同一个资源的多个线程共用一把锁,这个非常重要,否则就无法保证共享资源的安全 一个线程类中的所有静态方法共用同一把锁(类名class),所有非静态方 法共用同一把锁(this),同步代码块(指定需谨慎) 让天下没有难学的

同步的范围
1、如何找问题,即代码是否存在线程安全?(非常重要)

(1)明确哪些代码是多线程运行的代码
(2)明确多个线程是否有共享数据
(3)明确多线程运行代码中是否有多条语句操作共享数据

2、如何解决呢?(非常重要)

对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。
即所有操作共享数据的这些语句都要放在同步范围中

3、切记:

范围太小:没锁住所有有安全问题的代码
范围太大:没发挥多线程的功能。

释放锁的操作

  • 当前线程的同步方法、同步代码块执行结束。
  • 当前线程在同步代码块、同步方法中遇到break,retum终止了该代码块、该方法的继续执行。
  • 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束。
  • 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁,

不会释放锁的操作

线程执行同步代码块或同步方法时,
程序调用方法暂停当前线程的执行

Thread.sleep()
Thread.yield()

●线程执行同步代码块时,其他线程调用了该线程的suspend0方法将该线程挂起,该线程不会释放锁(同步监视器)。

应尽量避免使用suspend(和resume)来控制线程

Lock(锁)

  • 从JDK 5.0开始,Java提供了更强大的线程同步机制--通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
  • java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
  • ReentrantLock 类实现了 Lock,它拥有与synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
class A{
    private final ReentrantLock Lock = new ReenTrantLock();
    public void m(){
        lock.lock();
        try{
            //保证线程安全的代码
        }finally{
            lock.unlock();
        }
        
    }
}


注意:如果同步代码有异常,要将unlock()写入到finally语句块

synchronized 与Lock的对比

1.Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是 隐式锁,出了作用域自动释放
2.Lock只有代码块锁,synchronized有代码块锁和方法锁
3.使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

优先使用顺序:

Lock→ 同步代码块(已经进入了方法体,分配了相应资源)→同步方法(在方法体之外)