Java语言引入泛型的好处是安全简单。泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。
好处
- 类型安全,保证了代码的健壮性
- 消除强制类型转换
- 优化了程序设计,代码变得更加简洁
- 把运行时期的问题提前到了编译期间(以下解释)
- 泛型是没有多态的
- 泛型只有编译期间的概念,泛型仅仅是在编译期间是有效的
- 在编译期间一旦确定了泛型,那么在编译期间就只能向这个容器中添加对应类型的数据,否则会报错
- 运行期间将会被擦除,泛型是不存在运行时类型的
四个常用API
- 加:add()
- 减:subtract()
- 乘:multiply()
- 除:divide()
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)
枚举的特点
- 每个枚举类型默认都会自动继承java.lang.Enum
- 多个枚举常量使用“,”如果最后一个枚举常量下面有代码需要用分号隔开
- 枚举可以有构造,但不允许使用public或protected修饰
- 枚举类型可以提供普通属性
- 枚举类型不能够被实例化
- 枚举类型不支持再去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();
}
}
}
四、进程和线程
什么是进程和线程
- 一个程序至少一个进程,一个进程至少一个线程。线程不能单独执行运行,他一定是运行在进程的内部的
- 进程:是并发执行的程序,在执行过程中分配和管理资源的基本单位(是一个动态概念)
- 线程:是进程的一个执行单元,是进程内部的调度实体
进程和线程的关系
- 进程是资源分配的最小单元
- 线程是程序执行的最小单元
- 线程是进程的一个执行单元,是进程内部的调度实体
- 进程之间的资源是独立的,同一进程内的线程共享本进程的资源
进程和线程的区别
- 进程之间的资源是独立的,同一进程内的线程共享本进程的资源
- 一个进程崩溃之后,不会对其他进程产生影响,但是一个线程崩溃之后,会导致整个进程都死掉——多进程比多线程健壮
- 进程是程序过程中分配和管理资源的基本单位,线程是处理器调度的基本单位
- 两者均可并发执行
-
extends Thread类,重写run方法【共享代码,不共享资源】
将资源设置为静态的 —— 也是可以资源共享的
-
implement Runnable接口,重写run方法【共享代码,共享资源】
-
implement Callable接口【推荐配合Future+线程池一起使用】
Callable和Runnable的区别
- Callable调用的是call方法,Runnable调用的是run方法
- call方法可以抛出一个异常列表,但是run方法是不允许抛出异常列表
- Callable可以通过Future来得到异步计算的结果 - 拿到线程执行之后的结果.
特点
- Java语言的关键字
- 可用来给对象和方法或者代码块加锁
- 当它锁定一个方法[同步方法]或者一个代码块[同步代码块]的时候,并发线程同一时刻最多只有一个线程执行这段代码,其余的线程必须进入阻塞状态
- 非公平锁
- 如果同步代码块中出现了异常,那么仍然还是会自动释放锁资源的
不同场景使用方式
- 修饰普通方法 —— 对象锁
- 修饰静态方法 —— 类锁
- 修饰代码块synchronized(this) —— 对象锁
- 修饰代码块synchronized(xxx.class) —— 类锁
- 对象锁:不同对象拥有独立的一把锁,每个对象的锁是不冲突的
- 类锁:作用于这个类下的所有对象,竞争的是同一把锁
特点 :对象重新获得锁资源的时候,会先清空本地工作内存.强制从主存中去拷贝已经更新的变量.
七、synchronized原理(悲观锁)每一个锁都对应一个monitor对象,在HotSpot虚拟机中它是由ObjectMonitor实现的(C++实现)。
-
原子性
一个或者多个操作,在执行的过程中不会被任何因素打断,要么不执行,要么就执行全部
有些不是原子性的操作:i++、i+=2、i=i+1、double x = 3.0d、long x1 = 20L
-
可见性
原因:遇到synchronized之后,清空本地工作内存,重新从主存去拷贝最新的值
换句话说 ———-> 多个线程访问同一个资源时,这个资源的状态,信息等同于其他线程都是可见的
-
有序性
在同一个时刻,只能由一个线程进入
-
可重入性
当一个线程申请到锁资源并且执行完毕之后[释放],仍然还有机会再去继续申请曾经申请过的锁资源.
Lock的用法
- lock是接口,synchronized它是一个关键字
- lock锁是一个显示锁(手动申请锁,手动释放锁),synchronized隐式锁(自动申请/释放锁)
- lock手动申请锁**(对象锁)**
- lock是锁代码块
- lock出现异常的时候,是不会主动释放资源的
Lock和synchronized有什么区别
- 实现层面不一样:synchronized 是 Java 关键字,在JVM层面实现加锁和释放锁。Lock 是一个接口,在代码层面实现加锁和释放锁
- 是否自动释放锁:synchronized 在线程代码执行完或出现异常时自动释放锁。Lock 不会自动释放锁,需要再 finally {} 代码块显式地中释放锁
- 是否一直等待:synchronized 会导致线程拿不到锁一直等待。Lock 可以设置尝试获取锁或者获取锁失败一定时间超时
- 获取锁成功是否可知:synchronized 无法得知是否获取锁成功。Lock 可以通过 tryLock 方法获得加锁是否成功
- 功能复杂性:synchronized 加锁可重入、不可中断、非公平。Lock 可重入、可中断、可公平和不公平、细分读写锁,提高效率
- Java内存模型规定了所有的变量都存储在主内存中,包括实例变量、静态变量,出了局部变量和方法参数
- 每个线程都有自己的本地工作内存,保存了该线程用到的变量和主内存的副本拷贝
- 线程对变量的操作都在工作内存中进行,线程是不能直接读写主内存中的变量的
- 不同的线程之间无法访问对方的工作内存的变量,线程之间变量值的传递是通过主内存来完成的
volatile
-
保证可见性
-
volatile不会造成阻塞
-
禁止指令重排
-
不能保证原子性
-
使用volatile来修饰实例变量
- 作用一:强制让程序遵守”缓存一致性”协议.如果主存中的变量一旦发生了改变.线程就会强制从主存中重新拷贝这个最新的数据到自己的本地工作内存中去
- 作用二:禁止指令重排
volatile 和 synchronized区别
- volatile只能作用于变量,而synchronized可以作用于变量、方法和代码块
- 多线程访问volatile不会发生阻塞,而synchronized关键字可能发生阻塞
- volatile能够保证数据的可见性,不能保证原子性,而synchronized关键字都可以保证
- volatile可以禁止Jvm指令重排,而synchronized不能
- volatile关键字主要解决的是多个线程之间的可见性,而synchronized关键字保证的是多个线程访问资源的同步性。
单线程
- 当前的单线程执行方法结束的时候
多线程
- 线程释放锁资源的时候
- 线程切换
- 新建状态(瞬态):当线程对象创建后,即进入新建状态。如:Thread t1 = new MyThread()
- 就绪状态:当线程对象调用start()方法(t1.start()),线程就进入就绪状态(处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待cpu调度执行,并不是说执行了t.start()此线程立即就会执行)
- 运行状态:当cpu开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态
- 死亡状态:线程执行完或者因异常退出了run()方法,该线程结束生命周期
- 阻塞状态:处于运行状态中的线程由于某种原因,暂时放弃对cpu的使用权,停止执行,此时进入阻塞状态(直到其进入到就绪状态,才有机会再次被cpu调用以进入到运行状态)
- 等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态
- 同步阻塞:线程在获取synchronized同步锁失败(因为锁被其他线程占用),它会进入同步阻塞状态
- 其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态.之前获取键盘输入
- start方法是线程内的方法,当线程调用start方法的时候,线程就会进入到就绪状态,随时等待CPU调度执行,并不是立即执行
- run方法只是thread或者Runnable的一个普通方法,等到线程拿到CPU限权的时候,JVM虚拟机会自动调用run方法执行
- wait()释放锁,释放CPU,sleep()不释放锁,释放CPU
- wait()是Object的实例方法,sleep()是Thread的静态方法
- wait()必须在synchronized同步代码块中,sleep()可以放在同步代码中也可以不放在同步代码块中
- sleep()达到时间后,自动恢复可运行状态, wait()需要其他线程调用notify()、notifyAll()方法唤醒
线程的礼让 - 两个线程在执行,尽最大可能把cpu让给同等级的[1-10,越高越容易被执行],但是不一定能让出去
让出CPU调度,Thread类的方法,类似sleep只是不能由用户指定暂停多长时间 ,并且yield()方法只能让同优先级的线程有执行的机会。 yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。调用yield方法只是一个建议,告诉线程调度器我的工作已经做的差不多了,可以让别的相同优先级的线程使用CPU了,没有任何机制保证采纳。
五、join调用其他线程的join方法,表示调用join结束以后才会执行.
一种特殊的wait,当前运行线程调用另一个线程的join方法,当前线程进入阻塞状态直到另一个线程运行结束等待该线程终止。 注意该方法也需要捕捉异常。等待调用join方法的线程结束,再继续执行。如:t.join();//主要用于等待t线程运行结束,若无此句,main则会执行完毕,导致结果不可预测。
六、死锁死锁产生的条件
- 互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
- 请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
- 不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
- 环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
PS
- 只要打破四个条件的一个,就可以防止死锁
- 死锁是不可避免,但是需要写程序的破坏四个条件中的一个
- 静态域容易产生死锁
手写死锁代码
public class DeadLockDemo extends Thread{
//临界资源
//每个对象对应一个监视器锁[monitor对象]
public static Object oo1 = new Object();
public static Object oo2 = new Object();
}
class D1 extends Thread{
private DeadLockDemo dd = new DeadLockDemo();
@Override
public void run() {
synchronized (dd.oo1){ //申请到了oo1对象的锁资源
System.out.println("======11======");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (dd.oo2){ //申请到了oo1对象的锁资源
System.out.println("=======22======");
}
}
}
}
class D2 extends Thread{
private DeadLockDemo dd = new DeadLockDemo();
@Override
public void run() {
synchronized (dd.oo2){
System.out.println("====33====");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (dd.oo1){
System.out.println("=====44====");
}
}
}
}
class TestDead{
public static void main(String[] args) {
Thread t1 = new D1();
Thread t2 = new D2();
t1.start();
t2.start();
}
}
七、生产者消费者
线程之间的通信问题
- 使用Object类中wait和notify方法
- wait:Object类的方法(notify()、notifyAll()),必须放在循环体和同步代码块中,执行该方法的线程会释放锁
- 进入线程等待池中等待被再次唤醒(notify随机唤醒,notifyAll全部唤醒,线程结束自动唤醒)即放入锁池中竞争同步锁
手写代码[两种写法]
public class ThreadTongXinDemo {
public static void main(String[] args) {
//典型的不共享代码,共享资源
Box box = new Box();
Thread t1 = new ProductThread(box);
Thread t2 = new CustomerThread(box);
t1.setName("生产者");
t2.setName("消费者");
t1.start();
t2.start();
}
}
class Box{
private int content;
boolean flag; //标记
public synchronized int getContent() {
if (!flag){ //首先保证不让消费者先消费 让他睡
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
flag = false;
notifyAll();
return content;
}
public synchronized void setContent(int content) {
if (flag){ //生产者不睡,因为他要第一个生产(box中没东西)
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
flag = true;
notifyAll();
this.content = content;
}
}
class ProductThread extends Thread{
private Box box;
private int i = 0;
public ProductThread(Box box){
this.box = box;
}
@Override
public void run() {
while (true){
System.out.println(Thread.currentThread().getName()+"->放"+(++i)+"个");
box.setContent(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class CustomerThread extends Thread{
private Box box;
public CustomerThread(Box box) {
this.box = box;
}
@Override
public void run() {
while (true){
System.out.println(Thread.currentThread().getName()+"->取"+box.getContent()+"个");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ProducerAndCustomer {
private Lock lock = new ReentrantLock();
// 有几个线程就创建几个Condition
private Condition producer = lock.newCondition();//生产者
private Condition consumer = lock.newCondition();//消费者
private int content; //盒子里的产品个数
// 生产方法
public void produce(){
while (true){
try {
//对象锁
lock.lock();
// 生产者只有在盒子中产品数目等于0时进行生产
// 如果盒子里的数目不为0,那么让生产者进入阻塞状态
if(content!=0){
producer.await();
}
// 否则进行产品的生产
content = (int) (Math.random()*10 + 1);
System.out.println("生产者生产了"+content+"个产品");
Thread.sleep(1000);
// 唤醒消费者线程
consumer.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
// 消费方法
public void consume(){
while (true){
try {
//对象锁
lock.lock();
// 如果盒子里的数目小于等于0表示盒子空了,消费者不能再消费啦
if(content==0){
consumer.await();//等待阻塞
}
// 消费
System.out.println("消费者消费了"+content+"个产品");
content = 0;
Thread.sleep(1000);
// 唤醒生产者线程
producer.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
class TestProducerAndCustomer{
public static void main(String[] args) {
ProducerAndCustomer pc = new ProducerAndCustomer();
//共享资源的方式
Thread t1 = new Thread(()->{
pc.produce();
});
Thread t2 = new Thread(()->{
pc.consume();
});
t1.setName("生产者");
t2.setName("消费者");
t1.start();
t2.start();
}
}
手写打印abcABC123[理解]
public class LockTongxinDemo {
Lock lock = new ReentrantLock();
//有几个线程,就构建几个队列
private Condition c1 = lock.newCondition(); //给小写用
private Condition c2 = lock.newCondition(); //给大写用
private Condition c3 = lock.newCondition(); //给数字用
private int count = 0; //标记
//count = 0,打印小写字母的执行
//count = 1,打印大写字母的执行
//count = 2,打印数字的执行
//1. void await();//当前线程会进入到阻塞状态.必须要等其他线程唤醒的
//2. void signal();//哪个线程的Conition对象去调用signal()方法,哪个线程就会被唤醒
public void printLower(){
while (true){
lock.lock();
if (count!=0){
try {
c1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("abc");
System.out.println();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count = 1;
c2.signal();
lock.unlock();
}
}
public void printUpper(){
while (true){
lock.lock();
if (count!=1){
try {
c2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("ABC");
System.out.println();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count = 2;
c3.signal();
lock.unlock();
}
}
public void printNum(){
while (true){
lock.lock();
if (count!=2){
try {
c3.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("123");
System.out.println();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count = 0;
c1.signal();
lock.unlock();
}
}
public static void main(String[] args) {
LockTongxinDemo Tx = new LockTongxinDemo();
Thread t1 = new Thread(()->Tx.printLower());
Thread t2 = new Thread(()->Tx.printUpper());
Thread t3 = new Thread(()->Tx.printNum());
t1.start();
t2.start();
t3.start();
}
}
八、四大线程池
线程池的返回值ExecutorService简介
ExecutorService是Java提供的用于管理线程池的类。该类的两个作用:控制线程数量和重用线程
- Executors.newCacheThreadPool():可缓存线程池,先查看池中有没有以前建立的线程,如果有,就直接使用。如果没有,就建一个新的线程加入池中,缓存型池子通常用于执行一些生存期很短的异步型任务
- Executors.newFixedThreadPool(int n):创建一个可重用固定个数的线程池,以共享的无界队列方式来运行这些线程。
- Executors.newScheduledThreadPool(int n):创建一个定长线程池,支持定时及周期性任务执行
- Executors.newSingleThreadExecutor():创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
项目中如何处理高并发 - 多个线程执行update语句[悲观锁.乐观锁]
分布式环境下 - redis的分布式锁.
十、守护线程- GC - 运行在后台的 - 负责回收垃圾对象的.
- 核心:线程结束的时候不需要关心后台的守护线程是否也运行结束.线程是不会等后台的守护线程全部运行结束才结束.
- 当后台只有守护线程在执行的时候,就可以认为线程可以结束了.
多线程优点:
- 资源利用率更好
- 程序设计在某些情况下更简单
- 程序响应更快
多线程程序也有一些弊端,如设计复杂上升,数据共享使数据唯一性难以控制,增加资源消耗



