复习day5
1. 泛型Generic
定义
- Java在1.5之后加入了泛型的概念。泛型,即“参数化类型”。
- 泛型的本质是为了参数化类型(将类型参数化传递)(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,
- 这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
- 泛型只能是对象类型,比如User,Student,内置对象类型比如String,Integer[一定是包装类型]
泛型符号
- E 元素
- K,V - 键值对
- N - 数字
- T - 类型
- ? - 通配符
使用泛型的好处
Java语言引入泛型的好处是安全简单。泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。加入了泛型之后,可以保证代码的健壮性
把运行时期的问题提前到了编译期间(以下解释)
- 泛型是没有多态的
- 泛型只有编译期间的概念,泛型仅仅是在编译期间是有效的
- 在编译期间一旦确定了泛型,那么在编译期间就只能向这个容器中添加对应类型的数据,否则会报错
- 运行期间将会被擦除,泛型是不存在运行时类型的
2. Throwable
java.lang.Throwable - 异常和错误的顶级的类
两个分支:
- java.lang.Error - 错误 - 错误一旦发生,程序员是没有办法进行扭转的 - 不需要在代码中进行处理.
1-1. 子类VirtualMachineError虚拟机级别的错误
1-1-1. 子类java.lang.StackOverflowError 堆栈溢出 - 应用程序因为递归太深没有指定出口的时候.
1-1-2. 子类java.lang.OutOfMemoryError 内存泄漏 - [GC垃圾回收机制 - 后台自动回收垃圾对象]
- java.lang.Exception - 异常 - 程序在运行的过程中发生了不正常的情况
异常的处理方式
运行时异常不需要处理(也是可以处理的),只需要在编程的时候,注意一下验证/判断.稍微谨慎一点.
非运行时异常 - 编译期间就需要立即对其进行处理.处理的方式有俩种.一种是积极处理 - try..catch的方式
另外一种是消极处理
积极处理:
语法一: try….catch….catch….finally
推荐使用到的 - 因为针对每种不同的异常进行单独的日志记录,单独的异常处理.
语法二 - jdk7.0提供的新的写法
try{ //code... //code... }catch(异常类型1 | 异常类型2){ //... }
语法三 - 简单粗暴
try{ //.... }catch(异常总父类){ //... }
消极处理:
比如在某个方法中某些代码出现了非运行时异常,那么在自己方法的内部”不着急”去积极处理.而是把这个异常抛出去了.
为了自己不处理,而是抛出去? - 因为这个方法有可能会被反复在其他地方调用 - 原则:谁调用,谁负责最终处理.
原则谨记 - 不要把异常抛给main方法,等同于把异常抛给jvm,等同于一旦出现异常,程序就会崩溃.
3. 浮点类型的计算
常用方法
加:add()
减:subtract()
乘:multiply()
除:divide()
在java开发中如何处理小数精度丢失的问题
需要使用到的是BigDecimal(String val);
public class BigDecimalDemo {
public static void main(String[] args) {
//小数在进行计算的时候,会丢失精度
System.out.println(0.1+0.2);
BigDecimal d1 = new BigDecimal("0.1");
BigDecimal d2 = new BigDecimal(String.valueOf(0.2));
//用String类型构建BigDecimal对象--搭配--doubleValue()获取计算过的数据
//可以解决小数进行计算精度丢失的问题
System.out.println(d1.add(d2).doubleValue()); //0.3
System.out.println(d1.subtract(d2).doubleValue()); //-0.1
System.out.println(d1.multiply(d2).doubleValue()); //0.02
System.out.println(d1.divide(d2).doubleValue()); //0.5
}
}
两个构造
- BigDecimal(double val)
- BigDecimal(String val)
4.枚举类型
特点
- 枚举常量,多个枚举常量,使用,隔开.最后一个枚举常量不需要使用逗号了.
如果最后一个枚举常量下面还有代码的话,那么需要使用分号隔开
允许存在构造 - 访问修饰符不能是公开的,protected
枚举类型是不能够被实例化的
枚举类型中可以提供普通属性
每个枚举类型默认都会自动继承java.lang.Enum
枚举类型中是可以存在抽象方法的,但是每个枚举常量必须要实现这个抽象方法
枚举类型不支持再去extends另外一个枚举类型
枚举类型不支持再去手动extends另外一个类
- 实现过单例,防止序列化和反序列化产生多个实例
- 防止反射破坏单例(因为反射的newInstance方法中会判断是否枚举,如果是枚举则抛出异常)
public enum Singleton05 {
//public static final Singleton05 INSTANCE = new Singleton05();
INSTANCE;
Singleton05() {
System.out.println("比较繁琐的操作的事情,费时费力的事情!");
}
public static Singleton05 getInstance(){
return INSTANCE;
}
}
//防止序列化检测
class TestSingleton05{
public static void main(String[] args) {
Singleton05 s1 = Singleton05.getInstance();
Singleton05 s2 = Singleton05.getInstance();
System.out.println(s1 == s2); //true
//序列化
try(ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("src/stu/aistar/design/ss.txt"))){
//对单例进行序列化操作
out.writeObject(s1);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
//反序列化
try(ObjectInputStream in = new ObjectInputStream(new FileInputStream("src/stu/aistar/design/ss.txt"))) {
Singleton05 s3 = (Singleton05) in.readObject();
System.out.println(s3 == s1); //true
System.out.println(s3 == s2); //true
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
5.进程和线程
什么是进程和线程
一个程序至少一个进程,一个进程至少一个线程。线程不能单独执行运行,他一定是运行在进程的内部的
进程:是并发执行的程序,在执行过程中分配和管理资源的基本单位(是一个动态概念)
线程:是进程的一个执行单元,是进程内部的调度实体
进程和线程的关系
进程是资源分配的最小单元
线程是程序执行的最小单元
线程是进程的一个执行单元,是进程内部的调度实体
进程之间的资源是独立的,同一进程内的线程共享本进程的资源
进程和线程的区别
- 进程之间的资源是独立的,同一进程内的线程共享本进程的资源
- 一个进程崩溃之后,不会对其他进程产生影响,但是一个线程崩溃之后,会导致整个进程都死掉——多进程比多线程健壮
- 进程是程序过程中分配和管理资源的基本单位,线程是处理器调度的基本单位
- 两者均可并发执行
创建线程的方式
- 写一个类去继承java.lang.Thread类 - 重写里面的run方法
共享代码,不共享资源
只有将资源设置成静态的 - 也是进行一个资源的共享的
- 写一个类去实现java.lang.Runnable接口 - 重写里面的run方法
共享代码,共享资源
- 写一个类去实现Callable接口 -
- Callable可以通过Future来得到异步计算的结果 - 拿到线程执行之后的结果.
- Callable调用的是call方法,Runnable调用的是run方法.
- call方法是可以抛出一个异常列表的,但是run方法是不允许抛出异常列表
6. synchronized
- Java语言的关键字,
- 可用来给对象和方法或者代码块加锁
- 当它锁定一个方法[同步方法]或者一个代码块[同步代码块]的时候,同一时刻最多只有一个线程执行这段代码
- 当两个并发线程访问同一个对象object中的这个加锁同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
- 非公平锁
- 如果同步代码块中出现了异常,那么仍然还是会自动释放锁资源的
在java中,每个对象有且仅有一个同步锁,并且同步锁是依赖于对象存在的.当我们调用对象的synchronized修饰的同步方法时候,就是获取了该对象的同步锁.
- 修饰普通方法 - 对象锁 - 不同的对象拥有独立的”一把锁”,每个对象的”锁”是不冲突的 - “自助餐”
- **修饰静态方法 - “类锁” - **作用于这个类下的所有的对象 - 这个类实例化出来的所有的对象竞争的是”同一把锁” - 类锁 - “一个桌子上”
- 修饰代码块synchronized(this) - 对象锁
- 修饰代码块(XXX.class) - “类锁”
特点 :对象重新获得锁资源的时候,会先清空本地工作内存.强制从主存中去拷贝已经更新的变量.
synchronized特性
- 原子性
原子性代表一个操作或者多个操作,要么执行全部并且执行的过程中不能被任何因素打断.要么就不执行
- 可见性
原因:遇到synchronized之后,清空本地工作内存,重新从主存去拷贝最新的值
多个线程访问同一个资源时,这个资源的状态,信息等对于其他线程都是可见的
- 有序性
在同一时刻,只能由一个线程进入
- 可重入性
当一个线程申请到锁资源并且执行完毕之后[释放],仍然还有机会再去继续申请曾经申请过的锁资源
synchronized原理
每个对象都有一个监视器锁(monitor),当monitor被占用时,就会处于锁定状态[同步阻塞]-(都会不断尝试想要去成为monitor对象的拥有者-即获取锁资源)
7.lock
- lock是接口,synchronized它是一个关键字
- lock锁是一个显示锁(手动申请锁,手动释放锁),synchronized隐式锁(自动申请/释放锁)
- lock手动申请锁**(对象锁)**
- lock是锁代码块
- lock出现异常的时候,是不会主动释放资源的.
synchronized和Lock区别
synchronized和lock都是属于独占锁.
实现层面不一样。synchronized 是 Java 关键字,在JVM层面实现加锁和释放锁;Lock 是一个接口,在代码层面实现加锁和释放锁
是否自动释放锁。synchronized 在线程代码执行完或出现异常时自动释放锁;Lock 不会自动释放锁,需要在finally {} 代码块显式地中释放锁
是否一直等待。synchronized 会导致线程拿不到锁一直等待;Lock 可以设置尝试获取锁或者获取锁失败一定时间超时
获取锁成功是否可知。synchronized 无法得知是否获取锁成功;Lock 可以通过 tryLock 获得加锁是否成功
功能复杂性。synchronized 加锁可重入、不可中断、非公平;Lock 可重入、可中断、可公平和不公平、细分读写锁**提高效率
读锁 - java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock - 共享锁 - 允许多个线程去读. 写锁 - java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock - 互斥锁 - 一次只能由一个线程去写. 不可中断synchronized - A线程竞争到锁资源 - 顺利进入同步代码块执行 - 只要A资源不释放这个锁资源.B线程只能在外面等待. 可中断Lock -> A线程顺利执行同步代码,B线程如果tryLock方法话,那么B线程不断尝试获取锁资源.如果设置了超时等待时间.B线程等太久.已经超过了设置的时间.B线程将不会再傻乎乎继续等待下去[可中断],B线程就有可能去干别的事情了.
8. JMM - Java内存模型
JMM就是Java内存模型(java memory model - 不是JVM内存模型!!!
1.Java内存模型规定所有的变量都存储在主内存中
2.包括实例变量[类中的非静态属性],静态变量,但是不包括局部变量和方法参数。
3.每个线程都有自己的工作内存,线程的工作内存保存了该线程用到的变量和主内存的副本拷贝
4.线程对变量的操作都在工作内存中进行。线程不能直接读写主内存中的变量。
5.不同的线程之间也无法访问对方工作内存中的变量。线程之间变量值的传递均需要通过主内存来完成。
9. Volatile
- 保证可见性
- volatile是不会造成阻塞的
- 禁止指令重排
- 不能保证原子性
- 使用volatile来修饰实例变量
- 作用一:强制让程序遵守”缓存一致性”协议.如果主存中的变量一旦发生了改变.线程就会强制从主存中重新拷贝这个最新的数据到自己的本地工作内存中去
- 作用二:禁止指令重排
10. Synchronized和Volatile区别
- volatile只能作用于变量,而synchronized可以作用于变量、方法和代码块
- 多线程访问volatile不会发生阻塞,而synchronized关键字可能发生阻塞。
- volatile能够保证数据的可见性,不能保证原子性,而synchronized关键字都可以保证。
- volatile禁止jvm指令重排,synchronized不能
- volatile关键字主要解决的是多个线程之间的可见性,而synchronized关键字保证的是多个线程访问资源的同步性。