异常:
1、什么是异常
I.程序在运行过程中出现的特殊情况。
II.异常处理的必要性:任何程序都可能存在大量的未知问题、错误;如果不对这些问题进行正确处理,则可能导致程序的中断,造成不必要的损失。
2、异常的分类:
I-Throwable:可抛出的,一切错误或异常的父类。位于java.lang包中。
|-Error:JVM、硬件、执行逻辑错误,不能手动处理。
|-Exception:程序在运行和配置过程中产生的问题,可处理。
|-RuntimeException:运行时异常,可处理,可不处理。
|-CheckedException:受查异常,必须处理。
3、异常的产生:
I.自动抛出异常:当程序在运行时遇到不符合规范的代码或结果时,会产生异常。
II.手动抛出异常:throw new 异常类型(“实际参数”);
III.一旦产生异常结果:相当于执行return语句,导致程序因异常而终止。
4、异常的传递:
I.按照方法的调用链反向传递,如果最终都没有处理异常,最终交由我们的JVM进行默认异常处理(打印堆栈跟踪信息)
II.受查异常:throws 声明异常,声明位置:修饰在方法参数列表的后端。(这个方法内的异常交给上一级处理假如这个方法没有上一级了就由JVM处理,而且上一级没有声明也没有处理的话就会编译不通过) 这个throws主要用来修饰非运行时的异常,也可以修饰运行时异常,但是没用。
III.运行时异常:因其可处理,可不处理,无需声明。
5、异常的处理
try里面的内容是可能存在异常的逻辑代码,catch是异常处理,finally是不管代码有无异常都要在try-catch-finally结构结束前执行finally里面的代码
try-catch的集中写法:
try{} catch(异常类型){}
try{} catch(异常类型){} catch(异常类型){} …
try{} catch(异常类型){} catch(异常类型){} finally{}
try{} finally{}
注意:多重catch下,遵循从子到父的顺序,父类异常在最后捕获
6、自定义异常
I.继承Exception(受查异常)或Exception的子类。常用RuntimeException.(运行时异常)
II.必要提供的内容
(1).无参构造方法
(2)String message参数的构造方法。定义异常原因信息
7、异常方法覆盖
I.方法名、参数列表、返回值类型必须和父类相同
II.子类的访问修饰符和父类相同或比父类更宽泛
III.子类中的方法,不能抛出比父类更宽泛的异常。
IV.子类中的方法的异常声明可以声明多个异常,但是声明的异常必须要是父类或者接口的异常的本身或者其子类
8、扩充:方法执行中字节码操作指令
I.反编译:javap -verbose 文件名称(是.class) > 自定义文件名称.bytecode
多线程
1、进程
I.运行时的程序,称为进程。
II.单核CPU在任一时间点上,只能运行一个进程。
III.宏观并行、微观串行
IV. cpu get NumberOfCores 获得核心数
2、线程
I.轻量级进程
II.程序中的一个顺序控制流程,也是CPU的基本调度单位。
III.进程可以由单个或多个线程组成,彼此间完成不同的工作,交替执行,称为多线程
IV.JVM虚拟机是一个进程,默认包含主线程(Main函数),可以通过代码创建多个独立线程,与Main线程并发执行。
3.线程的组成
任何一个线程都具有基本的组成部分:
(1)CPU时间片:操作系统(OS)会为每个线程分配执行时间。
(2)运行数据:
①堆空间:存储线程需使用的对象,多个线程可以共享堆中的对象。
②栈空间:存储线程需使用的局部变量,每个线程都拥有独立的栈。
(3) 线程的逻辑代码
4.创建线程
(1).第一种方法
I.创建一个类继承Thread
II.根据需求覆盖run方法
III.创建继承Thread类的对象
IV.调用start()方法
(2).第二种方法
I.创建一个类实现Runnable接口
II.根据需求覆盖run方法
III.创建实现Runnable接口类的对象
IV.创建Thread将上一步创建的对象传入Thread有参构造
V.调用start()方法
4.线程的基本状态
①第一种状态(基本)
②第二种状态(等待)
③第三种状态(阻塞)
5.线程的常见方法
休眠:
public static void sleep(long millis)
当前线程主动休眠 millis 毫秒。 线程执行到sleep方法是,该线程如果还有剩余时间片,会释放剩余的时间片
sleep(0)表示立即放弃当前线程的时间片
放弃:
public static void yield()
当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片。
结合:
public final void join()
允许其他线程加入到当前线程中。
例如:t1.join();这个语句加入是在main线程中书写的,意思为:在执行到这个语句后,t1线程不结束,main线程不能竞争时间片
将t1加入到main线程执行流程中,等待t1线程执行结束后!main再进行竞争时间片!
无限期等待!等待条件为调用join方法的线程执行完毕后!再进入就绪状态,竞争时间片
6、线程安全的问题
I. 当多线程并发访问临界资源时,如果破坏了原子操作,可能会导致数据不一致。
II. 临界资源:共享资源(同一对象、堆空间),一次仅允许一个线程使用,才可保证其正确性
III.原子操作:不可分割的多步操作,被视作为一个整体,其顺序和步骤不能打乱或缺省。
7、synchronized 同步锁
I.每个对象都有一个互斥锁标记,用来分配给线程。
II.只有持有对象互斥锁标记的线程,才能进入对该对象加锁的同步操作中(同步方法、同步代码块)。
III.只有线程退出同步操作时,才会释放相应的锁标记
8、同步方式
I.同步代码块
(1). synchronized(临界资源对象){
//原子操作
}
I.同步方法
(1). synchronized 返回值类型 方法名成(参数列表){
//原子操作
}
9.同步规则
I.只有在调用包含同步代码块的方法或者是同步方法时,才需要对象的锁标记
II.如果调用的是不包含同步代码块的方法或普通方法时,则不需要锁标记,直接调用即可。
III.已知线程安全的内容:StringBuffer、Vector、Hashtable
10.死锁、生产者与消费者(详情见E盘千峰学习笔记.第六周.day4.com.qf.day4.t2.questions下面的代码)
11.线程通信
I.等待
(1)wait();
(2) 必须在对obj(对象)加锁的同步代码块(或同步方法)中,在一个线程执行期间,调用了obj.wait(),该线程会释放所拥有的锁标记。同时,进入到obj的等待队列中。等待唤醒
II. 通知(唤醒)
(1).notify();、notifyAll();
(2).必须在对obj加锁的同步代码块(或同步方法)中,从obj的Waiting(等待队列)中随机唤醒一个等待的线程,去竞争锁
高级多线程
1.线程池的概念:
现有问题:
线程是宝贵的内存资源、单个线程约占1MB空间,过多分配易造成内存溢出。
频繁的创建及销毁线程会增加虚拟机回收频率、资源开销,造成程序性能下降。
线程池:
线程容器,可设定线程分配的数量上限。
将预先创建的线程对象存入池中,并重用线程池中的线程对象。
2.线程池的原理及获取(创建)
(1)原理:
线程池就是相当于之前字符串池,我们先创建好固定数量(也有动态线程池)线程的线程池,将任务提交给线程池,由线程池分配线程、运行任务,并在当前任务结束后复用线程
(2)获取(创建):
常用的线程池接口和类(所在包java.util.concurrent):
Executor:线程池的顶级接口。
ExecutorService:线程池接口,可通过submit(Runnable task) 提交任务代码。
Executors工厂类:通过此类可以获得一个线程池。
ExecutorService ex = Executors.newCachedThreadPool();//获取(创建)动态数量的线程池,如不够则创建新的,没有上限
ExecutorService ex = Executors.newFixedThreadPool(3); 获取固定数量的线程池。参数:指定线程池中 线程的 数量
Tack1 tack1 = new Tack1();任务对象
3.Callable接口
public interface Callable{
public V call() throws Exception;
}
JDK5加入,与Runnable接口类似,实现之后代表一个线程任务。
Callable具有泛型返回值、可以声明异常。
4.Future接口
概念:异步接收ExecutorService.submit()所返回的状态结果,当中包含了 call()的返回值
方法:V get()以阻塞形式等待Future中的异步处理结果(call()的返回值)
5.线程的同步与异步
①同步
②异步
6.Lock接口(重入锁)
JDK5加入,与synchronized比较,显示定义,结构更灵活。
提供更多实用性方法,功能更强大、性能更优越。
常用方法: void lock() //获取锁,如锁被占用,则等待。
boolean tryLock() //尝试获取锁(成功返回true。失败返回false,不阻塞),
void unlock() //释放锁
ReentrantLock:Lock接口的实现类,与synchronized一样具有互斥锁功能
7.读写锁(ReentrantReadWrietLock)
ReentrantReadWriteLock:
一种支持一写多读的同步锁,读写分离,可分别分配读锁、写锁。
支持多次分配读锁,使多个读操作可以并发执行。
互斥规则:
写-写:互斥,阻塞。
读-写:互斥,读阻塞写、写阻塞读。
读-读:不互斥、不阻塞。
在读操作远远高于写操作的环境中,可在保障线程安全的情况下,提高运行效率
创建方式:
ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
ReadLock read = rwl.readLock();获取读锁
WriteLock write = rwl.writeLock();获取写锁
8.Collections中的工具方法
Collections工具类中提供了多个可以获得线程安全集合的方法。
public static Collection synchronizedCollection(Collection c)
public static List synchronizedList(List list)
public static Set synchronizedSet(Set s)
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)
public static SortedSet synchronizedSortedSet(SortedSet s)
public static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m)
创建方式:
List list = new ArrayList();
List<String> safeList = Collections.synchronizedList(list);
它的使用和ArrayList一样,他只是在内部加了锁,synchronizedList他的add方法是用synchronized同步锁把ArrayList的add方法锁上了
其他方法追一下源码大同小异都是
9.CopyOnWriteArrayList
线程安全的ArrayList,加强版读写分离。
写有锁,读无锁,读写之间不阻塞,优于读写锁。
写入时,先copy一个容器副本、再添加新元素,最后替换引用。
使用方式与ArrayList无异(包括创建方式也一样)
add的主要实现代码及描述:
我们创建它的对象时,会调用一个无参构造方法,方法内部是调用的setArray(new Object[0]);创建一个长度为0的数组,然后是我们调用他的add方法具体代码如下:
public boolean add(E e) {
final ReentrantLock lock = this.lock;//我们定义一个Lock对象,以便下面获取锁
lock.lock();//获取锁
try {
Object[] elements = getArray();//获取当前的数组
int len = elements.length;//获取当前数组长度
Object[] newElements = Arrays.copyOf(elements, len + 1);//将当前数组复制给比当前数组长度大1的新建数组,相当于给原来的数组扩容了1个位
newElements[len] = e;//将我们输入的对象放入当前数组的末尾(新数组最后一个元素下标就是len)
setArray(newElements);//将我们新的数组newElements覆盖以前的数组
return true;
} finally {
lock.unlock();//如果try代码出现异常,他都会执行finally内的代码,释放锁
}
}
10.CopyOnWriteArraySet
线程安全的Set,底层使用CopyOnWriteArrayList实现。
唯一不同在于,使用addIfAbsent()添加元素,会遍历数组,
如存在元素,则不添加(扔掉副本)。
add的实现方式:首先我们创建CopyOnWriteArraySet它的对象是,调用无参构造方法,方法内创建了一个CopyOnWriteArrayList对象,并赋值给了CopyOnWriteArraySet他的属性al。调用CopyOnWriteArraySet的add方法时他会调用用CopyOnWriteArrayList内部的addIfAbsent方法。主要实现代码如下:
//e是我们add内的参数
public boolean add(E e) {//CopyOnWriteArraySet下的add方法
return al.addIfAbsent(e);//调用的CopyOnWriteArrayList内部的addIfAbsent方法
}
//进入方法后
public boolean addIfAbsent(E e) {
Object[] snapshot = getArray();//获取了一个数组
//下面代码的主要作用是去重,我们传入的e跟snapshot获取的数组内从下标0开始循环长度为数组长度,如果有跟我们传入的e相同的元素,则返回下标,如果没有返回-1(indexOf方法内部代码),然后根据返回的信息,重复的话就抛弃了传入的e不重复的话就吧e添加到了数组末尾(多追源码,清晰)
return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
addIfAbsent(e, snapshot);
}
11.ComcurrentHashMap
初始容量默认为16段(Segment),使用分段锁设计。
不对整个Map加锁,而是为每个Segment加锁。
当多个对象存入同一个Segment时,才需要互斥。
最理想状态为16个对象分别存入16个Segment,并行数量16。
使用方式与HashMap无异
版本差异:
JDK1.7版本:他是对HashMap分的16段(Segment),每一段都加上锁了,put方法添加的键值对,会先用tryLock()方法尝试获取锁,如果获取失败,则有当前线程拿到锁正在运行,下一次获取不等待,如果获取成功,要先进入自己的段(根据hash算的16个段中的一个)中,先判断段是不是空的,如果是空的就把传入的键值对放在头部(第一个),如果不为空,则遍历段中的key值和当前传入key值比较相等不,不相等就吧当前传入的键值对放在最后一个键值对的next上,如果相等,就把相等的键值对给覆盖。
CAS交换算法:V要更新的变量 E:期望值 N:新值 当V = E则V = N
JDK1.8版本:采用的是CAS交换算法和同步锁,实现方法主要为:我们put传入的键值对,先算一下自己的段,然后我们根据CAS交换算法,这时候期望值为NUll,如果段中有对象,则线程获取段的表头的锁,拿到锁的对象要先做节点遍历。查看有没有相同的key,相同覆盖,不同挂在节点的next上。同步锁锁的是表头对象,只要表头锁上了其余的线程也就进不来了。如果段中没有对象则我们自己new一个节点对象放在段的头部,代码如下( if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null))))判断段是否为空。注意:这里我说的段就是Hash表
12.Queue接口(队列)
Collection的子接口,表示队列FIFO(First In First Out)
常用方法:
抛出异常:
boolean add(E e) //顺序添加一个元素(到达上限后,再添加则会抛出异常)
E remove() //获得第一个元素并移除(如果队列没有元素时,则抛异常)
E element() //获得第一个元素但不移除(如果队列没有元素时,则抛异常)
返回特殊值:推荐使用
boolean offer(E e) //顺序添加一个元素 (到达上限后,再添加则会返回false)
E poll() //获得第一个元素并移除 (如果队列没有元素时,则返回null)
E peek()//获取但不移除此队列的头,如果队列为空返回null;
13.ConcurrentLinkedQueue(Queue的实现类)
线程安全、可高效读写的队列,高并发下性能最好的队列。
无锁、CAS比较交换算法,修改的方法包含三个核心参数(V,E,N)
V:要更新的变量、E:预期值、N:新值。
只有当V==E时,V=N;否则表示已被更新过,则取消当前操作
13.BlockingQueue接口(阻塞队列)
Queue的子接口,阻塞的队列,增加了两个线程状态为无限期等待的方法。
方法:
void put(E e) //将指定元素插入此队列中,如果没有可用空间,则等待。
E take() //获取并移除此队列头部元素,如果没有可用元素,则等待。
可用于解决生产生、消费者问题。仔细品一品。
接口的实现类: