java并发这块学习了也有几个月了,整体感觉效率不高,总是感觉还差一点,有些东西时间久了也容易忘记,如何让自己高效的学习就显得很重要了。这段时间也断断续续学习,也好久没有进行总结了(快2个多月了),今天思索再三决定还是先总结下,一味的去看,感觉这些知识还是不属于自己的,把之间的内容融合下,思考下。(按照大纲分章节的来叙述)
一、线程的意义及用途
1、什么是线程?
说到线程 就需要了解下进程了,我们其实可以这么理解,一个进程就是一个应用程序(如:QQ音乐)。每个进程执行时
都会分配一个独立的内存地址空间。是进程独享的,而线程就是进程中的一个实体(如:音乐下载,播放音乐等),
一个进程是由一个或多个的线程组成的,而且同一个进程中的所有线程资源是共享的。
2、线程的实际作用?即为什么要用到线程?
我的理解是由于为了提升资源的利用率,使得处理任务的能力更加强,因此需要用到线程。同一个进程中的线程是可以
并发执行的
3、如何并发或并行?
并行就是:是指两个或者多个事件在同一时刻发生。并发:是指两个或多个事件在同一时间间隔内发生
多线程的运行,通过CPU的时间片的分配来实现线程之间的额切换,如果时间够快的话,那么用户应该感受不到卡顿的
4、如何创建多线程?
1. Runable 实现接口
2. Thread 继承
3. Callable/Future 带返回值的
5、线程状态(6个状态)
准备(创建线程),就绪(start),执行(running),等待(waiting),阻塞(bolck),结束(end)
通过这6种状态切换展示线程各个时刻
6、线程死锁问题
1.互斥,共享资源 X 和 Y 只能被一个线程占用;
2.占有且等待,线程 T1 已经取得共享资源 X,在等待共享资源 Y 的时候,不释放共享资源 X;
3.不可抢占,其他线程不能强行抢占线程 T1 占有的资源;
4.循环等待,线程 T1 等待线程 T2 占有的资源,线程 T2 等待线程 T1 占有的资源,就是循环等待
7、线程隔离
这里介绍下ThreadLocal 线程独享的(多线程环境下保证数据的完整性,不受其他线程的影响)
原理:后续补上
8、线程的应用场景
1、导出,上传
2、短信发送
二、并发编程引发的思考 (原子性)
如果实现一个程序自增(a++),如果是但线程的情况下,那么这个肯定是不有问题的。因为不存在其他人去修改导致该赋值并不是组新的
想必大家应该都知道为什么这个a++会导致安全性问题呢? 首先a++ 在实际的执行中其实是分为三步的 赋值,a=a+1(加1),更新。在多线程
的情况下,那么久可能存在其中一个线程还在执行第二部,另外一个线程一个获取到了值(还是历史值)。因此导致数据的安全性。
1、那么我们该如何解决这个问题呢?(原子性,有序性,可见性)
方法有很多,加锁(乐观锁,悲观锁等),Synchronized(互斥锁,代码块层面,修饰层面)修饰的粒度不一样,锁的粒度也就不一样了。
Synchronized - > a:修饰实例的时候,那就只针对当前实例加锁. b:就是静态方法,以及代码块的的时候,即针对有当前对象加锁
2、关于锁升级
无锁:没有竞争状态下,即不需要加锁
偏向锁:有一线程竞争,当有一个线程竞争到了锁,那么就保存该线程ID,如果再有一个线程进入同一同步块,或者退出该同步块时,
那么久不需要再去获得锁和释放锁了,只需要判断该线程ID是否一样就可以。
轻量级锁:如果当前偏向锁已被其他线获取到了,说明当前情况下存锁竞争,因此需要将原来的锁升级为轻量级锁 (cas)
重量级锁:如果由多个线程共同去竞争该锁时,都处于阻塞或等待状态时,那么由原来的轻量级锁升级为重量级锁了。(通过线程的阻塞
和唤醒,来实现多线程之间锁的竞争)
线程之间的通信 :wait/notify
三、线程安全性的本质
个人认为线程的安全性就是以为可见性(即对其他线程可见)导致的,即当多个线程进程时,由于线程之间的数据时共享的,都存内存中取数时,导致每个线程取
的数可能都不一样,因此导致了数据的不一致性。因为存在数据修改完之后写入内存时,也会存在时间的差,如果没有特殊的机制去控制时,
那么多个线程并发访问时,各自取到的数很难保证可见性。
解释下可见性 即:如果有对该线程都取内存上获取某一个值,那么其中有一个线程修改了该值,那么还在写入内存上时,其他线程还没等其写入
内存,就取读取值时此刻则不能读取到已经修改后的值了,这就是可见性。是不是强刷一下然后再去获取就可以了呢?
如何解决可见性问题?
volatile关键字可以很好的从可见性上去解决这个问题。
那么可以从以下2个方面来思考这问题
a: 硬件层面
1.CPU的高速缓存
由于有CPU的高速缓存在,那么就存在数据缓存一致性问题
2.总线锁 缓存锁
很明显就是在共享内存上加锁,那么就可以理解为并发访问的,在获取共享内存的数据时变成了串行了,这样就会导致CPU利用率下降
而且会出现阻塞的情况,显然这样的机制是不可取的。那么该如何处理呢?就是只有降低锁的粒度了。只要保证对多个CPU缓存的数据一
致性。那么在降低锁粒度的情况下,如何保证缓存的一致性。这样通过MESI(缓存一致性协议)来保证
3.什么是MESI :这里简单介绍下,MESI就是缓存中的4种状态 M:修改,E:独占,S:共享 I:失效
4.MESI在数据同步其他CPU时,需要等待,这样也占了时间,这里引入了一个优化。storeBuffer(缓冲)。将数据通过MESI协议同步到其他CPU
时不需要等待,通过异步处理。主线程还是继续其他指令的执行,这样地方又会导致指令的重排序
如: CPU0 (a=1,b=1) CPU1(if(b==1){a=2}) 当CPU0 执行a=1时,需要异步更新其他CPU,因此在a=1异步还未处理完,就紧接着执行b=1的
指令。那么这样就导致了CPU0的指令重排序了。
5、内存屏障 因此这样的方式下,不论怎么处理都会存在一定的差异,还有最后一招就只能强刷了(即:内存屏障)我是这样理解的:就是在获取某一个值
时,都通过内存屏障去刷一次,这样不管怎么样都能取到最新值了,也无需担心指令重排序问题了。
内存屏障是通过在硬件层面,将数据刷到内存上。
内存屏障有:写屏障(Store Memory Barrier )读屏障(Load Memory Barrier)全屏障(Full Memory Barrier)
b:软件层面
显然硬件层面已经做了些铺垫,那么软件层面就可以利用这些方式来处理这一列类问题了
那么在硬件层面有一套内存模型,因此在软件层面也模拟出来了类似的模型JMM(java meomery model)即也定义了一套规范来处理这类问题
之前硬件层面不是说了有总线锁,缓存锁,缓存锁(基于MESI)保证数据缓存一致性,对指令重排序,在硬件层面就有内存屏障来保证。因此
同样的在软件层面在JMM的基础上也提供了volatile,final等关键字来保证,使得开发者可以在合适的时候增加相应相应的关键字来禁止高速缓存和禁止
指令重排序来解决可见性和有序性问题,还有happens-before模型
在JMM模型中有哪些保证了happens-before模型
1、程序的顺序执行 2、程序的传递性 3、join 4、vlolatile 等
四、了解JUC工具下常见组件的用法及原理
JUC工具包 - >java.until.concurrent
主要锁,线程安全的集合,各种阻塞队列等
1、LOCK 说到锁,很容易想到synchronized (可重入锁),JU提供的ReentractLock 也是一个可重入锁
在Lock 出来之前都是synchronized来处理多线程安全的,但是synchronized有些不足,如不够灵活,并非适用于所有的并发场景。
2、CHM 即:concurrentHashMap HashMap
J.U.C 包里面提供的一个线程安全并且高效的 HashMap
3、Condition
Condition 是一个多线程协调通信的工具类,可以让某些线程一起等待某个条件(condition),只有满足条件时,线程 才会被唤醒
4、CountDownLatch
countdownlatch 是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完毕再执行。从命名可以解读到
countdown 是倒数的意思,类似于我们倒计时的概念等等
五、线程池的使用以及原理
1、什么是线程池?
如果每次请求,都去创建和销毁线程,这个是会很消耗资源的而且在JVM中过度的创建会导致系统资源不足,因此未来解决一问题,提前在
创建好一些线程放在容器中。这就叫做线程池
2、如何使用?
static ExecutorService service=Executors.newFixedThreadPool(5);
public static void main(String[] args) {
for(int i=0;i<100;i++) {
service.execute(new Test());
}
service.shutdown();
}
3、原理
待更新
感谢Gper学院的Mic老师细心的讲解