2021-06-01

51 阅读6分钟

多线程

常用的三种启动线程的方式
1 继承 Thread
2 实现Runnable 接口
3 实现Callable接口

第一种 继承Thread类之后,重写run方法,直接调用start方法启动线程
第二种,第三种都需要借助Thread类去调用start方法启动,实例化好之后,作为Thread的入参.
第二种 直接调用start启动
第三种 是有返回值的,实例化之后作为futuretask的入参,futuretask作为thread的入参,最终调用start启动线程,最终调用futuretask的get方法获得执行的结果

优雅停止线程
1 标记停止
2 根据标记执行停止

线程的停止分为两步,第一步标记 ,利用线程的实例调用 interrupt()方法标记停止. 第二步根据标记判断是否停止,为真,停止当前线程.
在第二步中判断线程是否停止有两个方法,分别为 实例化的线程调用 isInterrupted()方法,还有一个静态方法 Thread.interrupted(),都可以判断当前线程是否被标记,为真,做相应处理
其中isInterrupted()与Thread.interrupted()的区别在于,Thread.interrupted()可以清除 线程停止的标记.

线程执行权
Thread.yield();静态方法,可以让线程放弃执行权.

线程执行顺序
利用实例化的线程调用join()方法,意思是等待当前线程执行完毕,再执行其他的线程,即等待线程死亡.

后台线程
将一个线程设置为后台线程,这样的作用是,当主线程结束,这个后台线程也将随之结束
实例化的线程调用 thread.setDaemon(true);设置为后台线程
判断一个线程是否为后台线程调用 thread.isDaemon()为true 就是守护线程 为false就是非守护线程

判断线程是否还存活
实例化的线程调用isAlive()方法

同步锁 synchronized 可以加在类,方法,代码块上,静态方法,静态变量等等 分为 : 类锁与对象锁(实例锁)
类锁: 就是所有该类的实例化对象都共用一把锁
对象锁: 就是每个实例化都是一把锁,每个实例化之间互不影响(每个实例化之间不是同一把锁)

死锁: 线程相互等待,不释放其他线程需要的锁

线程等待,唤醒; 这里相当于线程之间的通信,是通过锁去调用方法,然后实现的
wait() 使当前线程等待,notify() 获取到某个锁下的线程去唤醒
notifyAll() 获取到某个锁下的所有线程去唤醒

lock 不是为了替代synchronized  而是为了弥补synchronized 的不足而提出的
lock 的优势在于获取锁lock() 和释放锁unlock() 更灵活,与synchronized 一样,都可以实现线程之间的同步
synchronized 只能实现阻塞式任务调度,lock可以实现阻塞,即lock 然后还可以通过tryLock实现非阻塞
阻塞就是线程发现锁被其他线程占用,只能处于等待状态; 而非阻塞可以实现,当发现锁被其他线程占用,他可以去做其他事情
其次tryLock还有重载方法,可以规定好,在某个时间返回获取到了锁,可以继续执行

可重入锁/不可重入锁
常见的锁 synchronize lock 都是可重入锁
不可重入锁需要自定义

公平锁与非公平锁
synchronized 就是非公平锁 lock 锁默认为非公平锁,构造的时候传入ture 那么则为公平锁
何为公平?何为非公平? 即 线程获取到锁的机会是否是公平的. 比如有三个线程抢一把锁,公平锁就会三个线程轮训执行,而非公平锁,则不会

读锁和写锁
这两把锁都是通过 ReentrantReadWriteLock 中的读写锁 readWriteLock 调用 读锁或者写锁方法获取到的
本质区别在于 读锁是共享锁(即同一时间多个线程都能拿到这把锁)  写锁是独占锁(即多个线程中只有一个会获取到该锁,需要等该锁释放后,其他线程才能继续使用,此时相当于synchronized )
ReentrantReadWriteLock 中的读锁和写锁会有三种 读读共享 写写互斥 读写互斥
![微信图片_20210621191158.png](https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/eef71b8f1eb940d4807ab5012932b7cd~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5a-C5a-e5peF6KGM:q75.awebp?rk3s=f64ab15b&x-expires=1773046915&x-signature=yAdyDHXiLxlXNUZX%2B8fObq%2FczLg%3D)

线程的唤醒与等待之 LockSupport
LockSupport.park() 和 LockSupport.unpark() 都是静态方法可以之间调用不过,唤醒需要传入唤醒的线程实例

线程饥饿是由于线程长期不执行造成的,线程长期不执行是因为被阻塞,别阻塞的原因是因为获取不到锁,获取不到锁是因为锁互斥

ThreadLocal与InheritableThreadLocal
ThreadLocal是本地线程操作工具类,用来存储变量(数据),特点是每个线程一个副本,线程之间互不影响
InheritableThreadLocal 继承了ThreadLocal 升级了下,弥补了上个ThreadLocal中的不足,即:可以共享线程中的变量
![image.png](https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/46ff67a2bf2b48a0ac406d0c56bddbca~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5a-C5a-e5peF6KGM:q75.awebp?rk3s=f64ab15b&x-expires=1773046915&x-signature=AUXiErvWm4DlfyQdqVF6bJusBFk%3D)

volatile关键字
内存可见性: 线程获取变量,去工作内存找,找不到的时候,回去住内存找,并备份至工作内存,多个现场就是多个备份,如果不用volatile关键字修饰,那么这个变量的更改,不会被其他线程的工作内存同步得知(但是会改变主内存的变量值),后果就是线程之间要想通过该变量传递信息变为不可能.
为什么不会被其他线程的得知呢? 因为其他线程获取变量的时候,会先去工作内存找,找到了就直接返回,不会去主内存取最新的值,而volatile会在一个线程改变变量值的的时候,通知其他线程,再下次再获取工作内存的变量的时候,强制它去主内存获取变量值,这也就得到了最新的值.
volatile不能解决原子一致性问题,同步可以解决. 还有另一种方法就是 atom* 原子类也可以解决.


原子类的一般用法:基于CAS思想
ABA问题使用原子类中的AtomicMarkableReference 标记(设置值成功就会将标记修改为ture) 或者AtomicStampedReference 版本号(每次设置成功版本号都会加1)来解决该问题

本文转自 jimolvxing.blog.csdn.net/article/det…,如有侵权,请联系删除。