深入 多线程

Java真的可以开启线程么?

不可以
java是运行在虚拟机上的,无法直接操作系统
java的start方法,调用的是一个native方法,这个方法时底层c++的方法,用于开启线程

并发与并行

并发:多个线程操作一个资源(单核cpu,模拟出来多个线程,操作一个资源)
并行:在cpu多核情况下才发生,多个线程可以同时执行(线程池)
并发编程的本质:充分利用CPU的资源!

wait和sleep区别

1.来自不同的类
wait来自Object
sleep来自Thread

2.锁的释放
sleep抱着锁睡
wait会释放锁

3.使用范围不同
sleep能在任何地方用
wait必须在同步代码块中用

4.是否捕获异常
wait不用捕获
sleep需要捕获

Synchornized和 Lock的区别

  1. Synchronized是一个java关键字,而lock是一个java类
  2. Synchronized无法判断锁的状态,lock通过lock unlock方法知晓是否获得锁
  3. SYnchronized是全自动,lock是需要手动获取和关闭的
  4. Synchronized和 lock都是可重入锁,但是lock有方法trylock,可以防止别的未获得锁的线程持续等待
  5. Synchronized是非公平的;lock可以设置为公平和非公平锁
  6. Synchronized适合少量代码的锁,lock适合大量代码,更安全

生产者消费者模式产生的问题

在这种模式中会存在虚假唤醒的情况。
也就是说,如果两个消费者线程,无论哪个线程抢到了资源,另一个线程的唤醒就没必要了,就称为虚假唤醒。
解决虚假唤醒的方法是 每次资源改变的时候都要重新判断,所以将if语句改成while就可解决

Synchronized的wait和notify对应lock的什么方法?

wait 和notify对应 Condition接口中的await方法和singal方法(signalAll)
Condition替代了Synchronized中同步监视器的方法
Condition的出现可以让线程更精准的执行和等待,具体方法是根据每个线程都创建各自的Condition,然后精准的执行等待和唤醒

锁的是谁???

synchronized 修饰方法时,锁的是调用该方法的对象
static synchronized修饰方法时,锁的是class类模板
Class类和对象互不相关,比如有两个对象,分别锁了对象和锁了class模板,那么两个线程互不相干(对象在堆,class模板在静态方法区)

如何解决List不安全的问题

  1. 用Vector代替ArrayList
  2. 用Collections.Synchronized(arraylist)集合方法让其线程安全
  3. JUC下的包有个 CopyOnWriteArrayList类,这个类的底层是一个 transient volatile 数组,保证其安全
    这种机制对应的就是安全失败机制

为什么用copyonwrite而 不用Vector呢?
前者是在读取的时候复制一份,然后再set回数组
而Vector用的是syn关键字,
并且前者的读取是没有锁的
所前者以效率更高

怎么解决HashMap线程不安全?

  1. Collections.syn方法转换
    (原理:synchronizedMap方法内维护了一个Map和一个mutx排斥锁,如果创建这个对象的时候,只传入map对象,那么将对象的排斥锁设置为this对象,即传入的map,若传入了map对象和排斥锁mutx,那么排斥锁设置为传入的mutx)

  2. 使用HashTable替代HashMap,线程安全,但是效率不高,因为所有的方法都加上了Synchronized关键字

  3. 用 new ConcurrentHashMap替代 HashMap

HashTable为什么不允许空的key-value,而HashMap允许有空的key-value?

因为HashTable设置了对于空值的处理,put的时候会直接报空指针异常。只要出现null值,就会报异常

底层原因是 HashTable使用的是安全失败机制,这样就会导致每次读到时数据都是未修改的原始数据,安全失败机制在改数据的时候会在复制的集合中改
所以 使用null值就无法判断key是存在还是 null,无法用contains判断是否key是存在的

安全失败

fail-safe
JUC包下的容器都是安全失败的,如CopyOnWrite和ConcurrentHashMap,原理就是在写入的时候,将集合的内容复制一份,然后在拷贝的集合上遍历,然后,遍历的情况是不会在原有集合所检查到的
所以不会触发 ConcurrentModifyException

快速失败

多线程下,在原有的集合上直接修改数据是 快速失败,非线程安全
多线程下会有 ConcurrentModifyException异常

说回ConcurrentHashMap的底层原理

1.7以前:和HashMap一样,都是用数组+链表实现(Segment+HashEntry)

并发量高的原因
Segment使用的是分段锁,Segment继承于ReentrantLock,一个线程访问一个Segment时,不会影响其他Segment的锁。
支持线程并发的数量 就是Segment分段锁的数量
get方法时全程不加锁的,HashEntry中的值都是被volatile修饰的,保证每次都是最新的可见的值。所以get方法很高效

1.8以后的数据结构

1会发现1.7之前 put方法还是要先找到数组,再遍历链表对应的值
效率不高
在1.8以后,摒弃了Segment分段式锁,转而采用了 CAS+synchronized确保并发的安全
把HashEntry改成了Node,把next采用volatile修饰。并且引入了红黑树

Callable

怎么调用?
新建类实现callable接口,然后在主线程中 new Thread,并把new的 FutureTask(新建类)放入Thread,并start开启
本质上:FutureTask是Runnable接口下的实现类,FutureTask内的构造方法有两种,一种是传入Callable,一种是传入Runnable,所以用FutureTask就是间接使用了Runnable

辅助工具(JUC下), AQS同步器的组件(AQS的实现类!)

1。减法计数器(CountDownLatch)
new CountDownLatch(n),传入的参数是计数器初始数量
每次
cd.countdown(); 计数器就减一
cd.await();就会等待计数器归零,再执行下面的操作
用于执行一些必须要执行完的任务

2.加法计数器(CyclicBarrier)
new Cyclic Barrier(7,()->{召唤神龙});
内部参数为需要运行的线程数,和满足条件后执行的线程
通过await方法,让计数器++;数量到达设定值时可以召唤神龙

3。 信号量Semaphore
new Semaphore(3) 参数表示 许可证的数量
acquire方法用于获取许可证 数量-1
release方法用于释放许可证 数量+1
当许可证为0时,其他线程需要等待获得许可证
让线程数量有限
在限流中会用到

读写锁

实现类:ReentrantReadWriteLock
可多个线程读,写的时候只能由有一个线程写。
更加细粒度的控制线程
在写的时候 lock.writelock().lock();进行加写锁
在读的时候 lock.readlock().lock(); 进行加读锁
解锁有unlock方法
ReentrantReadWriteLock中有读锁和写锁,读和写的时候分别操作不同的锁
这种方式能提高并发量

BolckingQueue

BlockingQueue继承Queue是Collection接口下的接口,与 Set List 一样。
实现类有ArrayListBlockingQueue 和 LinkListBlockingQueue
在这里插入图片描述

四组BolckingQueue的API

1.会抛出异常:add,remove
2. 不会抛出异常,有返回值(true,false/ true,null):offer,poll
在这里插入图片描述

SynchronousQueue(继承BlockingQueue)

进去一个元素,必须等待取出,才能继续
相当于只能存一个元素,put存 take取

线程池

线程池的好处

  1. 防止资源消耗
  2. 提高响应速度
  3. 方便管理
  4. 可以控制最大并发数

为什么不建议用Exectuors建立线程池

在这里插入图片描述

会造成OOM。
所以用ThreadPoolExecutor可以规避风险,防止资源耗尽

三大方法,七大参数,四种拒绝策略

三大方法
new SinglePoolThreadExector;
new FixedPoolThreadExector;
new CachedPoolThreadExector;

三大方法底层调用的都是ThreadPoolExecutor!!

七大参数
在这里插入图片描述
在这里插入图片描述

四大拒绝策略

在这里插入图片描述

最大线程数 如何定义?

1. CPU密集型 (CPU是几核,就写多少)
如何获取本机CPU的核数呢??
用 Runtime.getRuntime().availableProcessors 获取 并填入 最大线程数

2. IO密集型
先判断你的程序中 十分耗用IO的线程数,假设为15,那么就需要把 最大线程数 设置为 两倍的IO线程数(30)

四大函数式接口

函数式接口:只有一个方法的接口
简化编程

Function 函数型接口

在这里插入图片描述在这里插入图片描述

lamda表达式 可简化为
在这里插入图片描述

Predicate 断定型接口

输入一个固定的值,返回一个boolean类型的值
在这里插入图片描述

在这里插入图片描述

lambda表达式简化
在这里插入图片描述

Consumer 消费型接口

在这里插入图片描述在这里插入图片描述

Supplier 供给型接口

没有参数,只有返回值
在这里插入图片描述
在这里插入图片描述

Stream流式计算,链式编程,lamda表达式,函数式接口

在这里插入图片描述

ForkJoin(分支合并计算)

大数据量,并行执行数据会用
在这里插入图片描述ForkJoin的工作窃取
在这里插入图片描述维护的是一个双端队列

在这里插入图片描述使用步骤:

  1. new ForkJoinPool();
  2. 通过这个pool .execute();或 submit();
  3. 新建ForkJoinTask 一个需要执行的任务,并把其任务作为参数放入execute的方法中
  4. submit.get 拿到结果

并行流计算

在这里插入图片描述

Java线程中的异步调用

Future接口下的 CompletableFuture 实现线程异步通信
无返回值的异步回调
在这里插入图片描述有返回值的异步回调,编译成功和编译失败都返回结果
在这里插入图片描述

Volatile

说说你对Volatile的理解?
Volatile 是java虚拟机提供的轻量级的 同步机制
1. 保证可见性例如两个线程,如果主存中的数据不加Volatile。那么在其中一个线程改变主存中的数据时候,另一个线程中读到的数据就不是最新的值。所以加上Volatile就可以保证这种情况不会发生
2. 不保证原子性:原子性就是不可再分,所有的操作要么一起执行;要么都不执行。Volatile不能确保原子性。所以为了确保原子性,在不用Synchronized 和Lock的情况下,可以用Atom原子类包来代替基本的数据类型,来保证原子性。 Atom的顶层原理是CAS
3. 禁止指令重排
指令重排:指的是程序并不按照代码的顺序执行。源代码会经过编译器优化重排,内存系统重排,最后再执行。
Volatile 可以禁止指令重排
Volatile关键字被增加后,会在该条代码的执行前和执行后增加内存屏障,内存屏障用于保证固定的执行顺序,并且保证某些变量的可见性

什么是JMM

是java内存模型,不存在的东西,是一种约定
在这里插入图片描述在这里插入图片描述在这里插入图片描述

Atoms类

底层直接和操作系统挂钩,直接在内存中修改值

懒汉式单例模式 以及volatile怎么防止了指令重排

在这里插入图片描述
在这里插入图片描述

对于以上的饿汉式单例模式可以用反射的setAccessible破解在这里插入图片描述

所以为了防止被反射破解,可以更改构造器的代码,加锁
在这里插入图片描述在这里插入图片描述但是归根结底,还是能用反射破解单例模式,道高一尺,魔高一丈!
所以这个时候就需要枚举了,枚举是一个类,它自带单例模式!可以防止被反射破解

深入 CAS

CAS:Compare And Set(expect,update) 比较并交换,是原子类的一个方法。
比较的是工作内存中的值,和主内存中的值,如果相等,那么就执行更新主内存中的值为update
Atoms原子类底层的方法调用了Unsafe类,Unsafe类是java留的后门,Unsafe类帮助java直接进行系统操作。Unsafe类中大部分都为native方法,用于直接操作系统,unsafe中的方法有一个 Compare and SwapInt 是更底层的CPU的cas
但是Java层面的CAS是在 atoms类中的方法 CompareAndSet

在这里插入图片描述CPU层面的CAS方法是自旋锁,一直不停的比较并交换!!

CAS的 缺点

在这里插入图片描述

CAS的ABA问题

在这里插入图片描述使用原子类的时间戳类,防止ABA问题

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述自旋锁,不断地去旋转执行,直到成功

死锁怎么排查?

  1. 使用 jps-l 定位进程号
  2. 使用 jstack 进程号 找到死锁问题

AQS

在这里插入图片描述JUC下很多锁的 加锁和解锁操作都是来自于AQS下的组件
AQS内部维护了 一个state变量,用于上锁;还有加锁线程变量等,共同维护了一个锁的状态。
在这里插入图片描述
AQS内部还有一个等待队列,用于存放那些加锁失败的线程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值