chaoz的杂货铺

生命有息、学无止境、折腾不止

0%

java-多线程

笔记

Thread和Object类中的重要方法详解

sleep方法详解
作用:我只想让线程在预期的时间执行,其他时候不要占用CPU资源
不释放锁:
包括synchronized和lock
和wait不同

sleep方法响应中断
1.抛出InterruptedException
2.清除中断状态

sleep方法可以让线程进入Waiting状态,并且不占用CPU资源,但是不释放锁,直到规定时间后再执行,休眠期间如果被中断,会抛出异常并清除中断状态

join方法作用、用法
作用:因为新的线程加入了我们,所以我们要等他执行完再出发
用法:main等待thread1执行完毕,注意谁等谁

join原理
源码
分析
等价

yield方法详解
作用:释放我的CPU时间片
定位:JVM不保证遵循
yield和sleep区别:是否随时可能再次被调度

线程的各个属性

mark

1、线程
作用:给用户线程提供服务

2、守护线程的3个特性:
线程类型默认继承自父线程
被谁启动
不影响JVM退出

3、守护线程和普通线程的区别:
整体无区别
唯一区别在于JVM的离开

4、线程优先级
10个级别,默认5
程序设计不应依赖于优先级
◆不同操作系统不一样
windows 中有优先级推进器的功能。
◆优先级会被操作系统改变

线程异常

1、Java异常体系图

2、实际工作中,如何全局处理异常?为什么要全局处理?不处理行不行?

3、线程的未捕获异常UncaughtException应该如何处理?

4、为什么需要UncaughtExceptionHandler?

5、为什么需要UncaughtExceptionHandler?

6、主线程可以轻松发现异常,子线程却不行;子线程异常无法用传统方法捕获。

7、两种解决方案
方案一(不推荐):手动在每个run方法里进行try catch
方案二(推荐):利用UncaughtExceptionHandler
UncaughtExceptionHandler接口
void uncaughtException(Thread t,Throwable e);
异常处理器的调用策略

线程安全

1、什么情况下会出现线程安全问题,怎么避免?

运行结果错误:a++多线程下出现消失的请求现象
mark
活跃性问题:死锁、活锁、饥饿
对象发布和初始化的时候的安全问题

2、对象发布和初始化的时候的安全问题
什么是发布
什么是逸出
◆1.方法返回一个private对象(private的本意是不让外部访问)
◆2.还未完成初始化(构造函数没完全执行完毕)就把对象提供给外界,比如:
◆在构造函数中未初始化完毕就this赋值
◆隐式逸出——注册监听事件
◆构造函数中运行线程

3、各种需要考虑线程安全的情况
访问共享的变量或资源,会有并发风险,比如对象的属性、静态变量、共享缓存、数据库等
所有依赖时序的操作,即使每一步操作都是线程安全的,还是存在并发问题:read-modify-write、check-then-act
不同的数据之间存在捆绑关系的时候
我们使用其他类的时候,如果对方没有声明自己是线程安全的

4、多线程会导致的问题
性能问题有哪些体现、什么是性能问题

调度:
上下文切换
什么是上下文?
保存现场
缓存开销
缓存失效
何时会导致密集的上下文切换:
抢锁、IO
协作:
内存同步

为什么多线程会带来性能问题

解决:
返回副本
工厂模式

什么是逸出?
方法返回一个private对象(private的本意是不让外部访问)
还未完成初始化(构造函数没完全执行完毕)就把对象提供给外界,比如:
在构造函数中未初始化完毕就this赋值
隐式逸出——注册监听事件
构造函数中运行线程

各种需要考虑线程安全的情况
访问共享的变量或资源,会有并发风险,比如对象的属性、静态变量、共享缓存、数据库等
所有依赖时序的操作,即使每一步操作都是线程安全的,还是存在并发
问题:read-modify-write、check-then-act
不同的数据之间存在捆绑关系的时候
我们使用其他类的时候,如果对方没有声明自己是线程安全的

java 内存模型

JVM实现会带来不同的“翻译”,不同的CPU平台的机器指令又干差万别,无法保证并发安全的效果一致

1、JVM 内存结构 VS Java 内存模型 VS Java 对象模型

整体方向:
JVM内存结构,Java虚拟机的运行时区域有关。
Java内存模型,和Java的并发编程有关。
Java对象模型,和Java对象在虚拟机中的表现形式有关。

2、Java对象模型
Java对象自身的存储模型
JVM会给这个类创建一个instanceKlass,保存在方法区,用来在JVM层表示该Java类。
当我们在Java代码中,使用new创建一个对象的时候,JVM会创建一个instanceOopDesc对象,这个对象中包含了对象头以及实例数据。

3、为什么需要JMM
C语言不存在内存模型的概念
依赖处理器,不同处理器结果不一样
无法保证并发安全
需要一个标准,让多线程运行的结果可预期

4、JMM是规范
Java Memory Model
是一组规范,需要各个JVM的实现来遵守JMM规范,以便于开发者可以利用这些规范,更方便地开发多线程程序。
如果没有这样的一个JMM内存模型来规范,那么很可能经过了不同JVM的不同规则的重排序之后,导致不同的虚拟机上运行的结果不一样,那是很大的问题。

5、JMM是工具类和关键字的原理
volatile、synchronized、Lock等的原理都是JMM
如果没有JMM,那就需要我们自己指定什么时候用内存栅栏等,那是相当麻烦的,幸好有了JMM,让我们只需要用同步工具和关键字就可以开发并发程序。

6、重排序

什么是重排序:
在线程1内部的两行代码的实际执行顺序和代码在Java文件中的顺序不一致,代码指令并不是严格按照代码语句顺序执行的,它们的顺序被改变了,这就是重排序,这里被颠倒的是y=a和b=1这两行语句。

重排序的好处:提高处理速度

重排序的3种情况:编译器优化、CPU指令重排、内存的“重排序”
编译器优化:包括JVM,IT编译器等
CPU指令重排:就算编译器不发生重排,CPU也可能对指令进行重排
内存的“重排序”:线程A的修改线程B却看不到,引出可见性问题

对象发布和初始化的时候的安全问题
什么是发布?

7、可见性

为什么会有可见性问题?

寄存器 缓存
CPU有多级缓存,导致读的数据过期
·高速缓存的容量比主内存小,但是速度仅次于寄存器,所以在CPU和主内存之间就多了Cache层
·线程间的对于共享变量的可见性问题不是直接由多核引起的,而是由多缓存引起的。
·如果所有个核心都只用一个缓存,那么也就不存在内存可见性问题了。
·每个核心都会将自己需要的数据读到独占缓存中,数据修改后也是写入到缓存中,然后等待刷入到主存中。所以会导致有些核心读取的值是一个过期的值。

解释下什么是可见性问题?

8、JMM的抽象:主内存和本地内存

Java作为高级语言,屏蔽了这些底层细节,用JMM定义了一套读写内存数据的规范,虽然我们不再需要关心一级缓存和二级缓存的问题,但是,JMM抽象了主内存和本地内存的概念。

这里说的本地内存并不是真的是一块给每个线程分配的内存,而是JMM的一个抽象,是对于寄存器、一级缓存、二级缓存等的抽象。

mark
mark
mark

9、主内存和本地内存的关系

JMM有以下规定:
所有的变量都存储在主内存中,同时每个线程也有自己独立的工作内存,工作内存中的变量内容是主内存中的拷贝
线程不能直接读写主内存中的变量,而是只能操作自己工作内存中的变量然后再同步到主内存中
主内存是多个线程共享的,但线程间不共享工作内存,如果线程间需要通信,必须借助主内存中转来完成
所有的共享变量存在于主内存中,每个线程有自己的本地内存,而且线程读写共享数据也是通过本地内存交换的,所以才导致了可见性问题。

10、Happens-Before原则
什么是 Happens-Before ?
happens-before规则是用来解决可见性问题的:在时间上,动作A发生在动作B之前,B保证能看见A,这就是happens-before。
两个操作可以用happens-before来确定它们的执行顺序:如果一个操作happens-before于另一个操作,那么我们说第一个操作对于第二个操作是可见的。

什么不是happens-before?
两个线程没有相互配合的机制,所以代码X和Y的执行结果并不能保证总被对方看到的,这就不具备happens-before。

有哪些规则?
1.单线程规则
2.锁操作
3.volatile变量
4.线程启动
5.线程join
6.传递性
7.中断
8.构造方法(过时了)
9.工具类
1.线程安全的容器get一定能看到在此之前的put等存入动作
2.CountDownLatch
3.Semaphore
4.Future
5.线程池
6.CyclicBarrier

总结:
不仅可以影响自己的可见性,还可以影响附近的变量的可见性。

11、volatile是什么?

volatile是一种同步机制,比synchronized或者Lock相关类更轻量,因为使用volatile并不会发生上下文切换等开销很大的行为。
如果一个变量别修饰成volatile,那么JVM就知道了这个变量可能会被并发修改。
但是开销小,相应的能力也小,虽然说volatile是用来同步的保证线程安全的,但是volatile做不到synchronized那样的原子保护,volatile仅在很有限的场景下才能发挥作用。

12、volatile的适用场合

不适用:a++
适用场合1:boolean flag,如果一个共享变量自始至终只被各个线程赋值,而没有其他的操作,那么就可以用volatile来代替synchronized或者代替原子变量,因为赋值自身是有原子性的,而volatile又保证了可见性,所以就足以保证线程安全。

13、原子性

什么是原子性?
一系列的操作,要么全部执行成功,要么全部不执行,不会出现执行一半的情况,是不可分割的。

课程问题

1.实现多线程的方法到底有1种还是2种还是4种?
2.怎样才是正确的线程启动方式?
3.上山容易下山难——如何正确停止线程?(难点)
4.线程的一生——6个状态(生命周期)

1.为什么线程通信的方法wait),notify)和notifyAll0被定义在Object类里?而sleep定义在Thread类里?
2.用3种方式实现生产者模式
3.Java SE 8Java 1.8JDK8是什么关系,是同一个东西吗?
4.Join和sleep和wait期间线程的状态分别是什么?为什么?

5.yield方法
6.获取当前执行线程的引用:Thread.currentThread0方法
7.start和run方法
8.stop,suspend,resume方法
9.面试常见问题

mark

死锁问题与企业解决方案

死锁

1、什么是死锁?怎么发生的?
发生在并发中
互不相让:当两个(或更多)线程(或进程)相互持有对方所需要的资源,又不主动释放,导致所有人都无法继续前进,导致程序陷入无尽的阻塞,这就是死锁。
mark

2、死锁的影响是个啥?数据库与jvm种都有啥影响?

数据库会自我检测,然后去放弃事务,问题来了:会丢失事务吗?
JVM中:无法自动处理。

3、死锁会有啥危害?高并发场景,多系统场景等等?
虽然有些事情不会发生,但是随着使劲按推移,它一定会发生
一旦发生,多是高并发场景,影响用户多
整个系统崩溃、子系统崩溃、性能降低
压力测试无法找出所有潜在的死锁

4、发生死锁的例子(退出的信号都素hi啥意思?)(发生死锁的条件):
互斥、请求与保持条件、不剥夺条件、循环等待条件

5、如何定位死锁:
jstack,ThreadMXBeen 都是啥?怎么用?
检测算法:
允许发生死锁
每次调用锁都记录
定期检查“锁的调用链路图”中是否存在环路
一旦发生死锁,就用死锁恢复机制进行恢复

6、修复死锁的策略:

会根据系统情况来探究死锁的问题。

1.进程终止
逐个终止线程,直到死锁消除。
终止顺序:
1.优先级(是前台交互还是后台处理)
2.已占用资源、还需要的资源
3.已经运行时间
2.资源抢占
把已经分发出去的锁给收回来
让线程回退几步,这样就不用结束整个线程,成本比较低
缺点:可能同一个线程一直被抢占,那就造成饥饿

7、常见修复策略
避免策略:哲学家就餐的换手方案、转账换序方案
检测与恢复策略:一段时间检测是否有死锁,如果有就剥夺某一个资源,来打开死锁
蛇鸟策略:鸵鸟这种动物在遇到危险的时候,通常就会把头埋在地上,这样一来它就看不到危险了。而鸵鸟策略的意思就是说,如果我们发生死锁的概率极其低,那么我们就直接忽略它,直到死锁发生的时候,再人工修复。

8、哲学家就餐问题:

9、如何避免死锁
1.设置超时时间
Lock的tryLock(long timeout,TimeUnit unit)
synchronized不具备尝试锁的能力
造成超时的可能性多:发生了死锁、线程陷入死循环、线程执行很慢
获取锁失败:打日志、发报警邮件、重启等
2.多使用并发类而不是自己设计锁
ConcurrentHashMap、ConcurrentLinkedQueue、AtomicBoolean等
实际应用中java.util.concurrent.atomic十分有用,简单方便且效率比使用Lock更高
多用并发集合少用同步集合,并发集合比同步集合的可扩展性更好
并发场景需要用到map,首先想到用ConcurrentHashMap
3.尽量降低锁的使用粒度:用不同的锁而不是一个锁

4.如果能使用同步代码块,就不使用同步方法:自己指定锁对象

5.给你的线程起个有意义的名字:debug和排查时事半功倍,框架和JDK都遵守这个最佳实践

6.避免锁的嵌套:MustDeadLock类

7.分配资源前先看能不能收回来:银行家算法

8.尽量不要几个功能用同一把锁:专锁专用

10、其他活性故障
死锁是最常见的活跃性问题,不过除了刚才的死锁之外,还有一些类似的问题,会导致程序无法顺利执行,统称为活跃性问题
活锁(LiveLock)
死锁:每个哲学家都拿着左手的餐叉,永远都在等右边的餐叉(或者相反)
活锁:在完全相同的时刻进入餐厅,并同时拿起左边的餐叉,那么这些哲学家就会等待五分钟,同时放下手中的餐叉,再等五分钟,又同时拿起这些餐叉
虽然线程并没有阻塞,也始终在运行(所以叫做“活”锁,线程是“活”的),但是程序却得不到进展,因为线程始终重复做同样的事
如果这里死锁,那么就是这里两个人都始终一动不动,直到对方先抬头,他们之间不再说话了,只是等待
如果发生活锁,那么这里的情况就是,双方都不停地对对方说“你先起来吧,你先起来吧”,双方都一直在说话,在运行
死锁和活锁的结果是一样的,就是谁都不能先抬头
饥饿
当线程需要某些资源(例如CPU),但是却始终得不到
线程的优先级设置得过于低,或者有某线程持有锁同时又无限循环从而不释放锁,或者某程序始终占用某文件的写锁
饥饿可能会导致响应性差:比如,我们的浏览器有一个线程负责处理前台响应(打开收藏夹等动作),另外的后台线程负责下载图片和文件、计算渲染等。在这种情况下,如果后台线程把CPU资源都占用了,那么前台线程将无法得到很好地执行,这会导致用户的体验很差

11、如何解决活锁问题
原因:重试机制不变,消息队列始终重试,吃饭始终谦
以太网的指数退避算法
加入随机因素
工程中的活锁实例:消息队列
策略:消息如果处理失败,就放在队列开头重试
由于依赖服务出了问题,处理该消息一直失败Message in
没阻塞,但程序无法继续
解决:放到队列尾部、重试限制IFO queue Message out

思考

1、Runnable 接口是个啥?

2、线程锁是什么状态下有的?

3、synchronized 什么方法去修饰

4、LinkedList 以及其 poll 方法

5、手写一个生产者消费者问题

6、用两个线程交替打印出 0~100 的奇偶数

7、为什么wait0需要在同步代码块内使用,而 sleep 不需要

8、为什么线程通信的方法wait0,notify)和notifyAll0被定义在Object类里?而sleep定义在Thread类里?

9、wait方法是属于Object对象的,那调用Thread.wait会怎么样?

建议看看源码

10、如何选择用notify还是nofityAll?

唤醒一个或者多个线程

11、notifyAll之后所有的线程都会再次抢夺锁,如果某线程抢夺失败怎么办?

进入等待状态

12、用suspend0和resume0来阻塞线程可以吗?为什么?
安全问题弃用

13、avaSE,JavaEE,JavaME是什么?
标准版
企业版
移动版

14、JRE和JDK和JVM是什么关系?

jre环境:跑的环境,包JVM还包含一些库
jdk:开发工具包,包含jre,还包含其他诊断工具等。
jvm: 虚拟机

15、独占锁?ReentrantLock();

16、wait/notify、sleep异同(方法属于哪个对象?线程状态怎么切换?)
相同
◆阻塞
◆响应中断
不同
◆同步方法中
◆释放锁指定时间◆所属类

17、Frames 与 Thread 面板

18、应用场景有哪些?

19、获取当前执行线程的引用:Thread.currentThread方法
start和run方法
stop,suspend,resume方法

20、什么时候我们需要设置守护线程?

21、我们应该如何应用线程优先级来帮助程序运行?有哪些禁忌?

22、不同的操作系统如何处理优先级问题?

23、线程的id为什么不会重复?线程的名字什么时候可以修改?

24、守护线程和普通线程的区别

25、我们是否需要给线程设置为守护线程?

容易造成资源不一致

26、如何全局处理异常?为什么要全局处理?不处理行不行?

27、run方法是否可以抛出异常?如果抛出异常,线程的状态会怎么样?

28、线程中如何处理某个未处理异常?

29、一共有哪几类线程安全问题?

30、哪些场景需要额外注意线程安全问题?

31、什么是多线程的上下文切换?

32、AtomicInteger 是个啥?

33、CyclicBarrier 怎么用?

34、CountDownLatch ?

35、插件过滤,idea那个神器?

36、volatile 什么作用?volatile和synchronized的关系?怎么用volatile修正重排序问题?

37、单例模式的作用,为什么需要单例?
节省内存和计算保证结果正确方便管理

38、单例模式适用场景
1.无状态的工具类:比如日志工具类,不管是在哪里使用,我们需要的只是它帮我们记录日志信息,除此之外,并不需要在它的实例对象上存储任何状态,这时候我们就只需要一个实例对象即可。
2.全局信息类:比如我们在一个类上记录网站的访问次数,我们不希望有的访问被记录在对象A上,有的却记录在对象B上,这时候我们就让这个类成为单例。

39、单例模式的8种写法:
1.饿汉式(静态常量式)
2.饿汉式(静态代码块)
3.懒汉式(线程不安全)
4.懒汉式(线程安全,同步方法)
5.懒汉式(线程不安全)
6.双重检查 推荐面试使用
mark
7.静态内部类的方式 推荐
8.枚举 最好
各种写法的适用场合?
最好的方法是利用枚举,因为还可以防止反序列化重新创建新的对象;
非线程同步的方法不能使用;
如果程序一开始要加载的资源太多,那么就应该使用懒加载;
饿汉式如果是对象的创建需要配置文件就不适用。
懒加载虽然好,但是静态内部类这种方式会引入编程复杂性

饿汉式的缺点?
懒汉式的缺点?
为什么要用double-check?不用就不安全吗?
为什么双重检查模式要用volatile?

40、用哪种单例的实现方案最好?

Joshua Bloch大神在《Effective Java》中明确表达过的观点:“使用枚举实现单例的方法虽然还没有广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。"
写法简单
线程安全有保障
避免反序列化破坏单例

41、讲一讲什么是Java内存模型

42、什么是原子操作?Java中有哪些原子操作?生成对象的过程是不是原子操作?
1.新建一个空的Person对象
2.把这个对象的地址指向p
3.执行Person的构造函数

43、什么是内存可见性?

44、64位的double和long写入的时候是原子的吗?

45、Thread.sleep(0) 到底有什么用?
答:这个东西要聊一聊linux与windows怎么分配线程的。一个是轮流,一个是届算法。
然后呢,饥饿算法可能会有问题,导致某一个线程一直占用着cpu。这时候调用 sleep(0) 的线程会告诉CPU,你从新算下任务调度。sleep 里面的参数代表着多少秒内该线程不需要执行。

46、什么是线程池,怎么创建线程销毁线程的?多余的空闲线程是怎么回收的呢?会不会有oom的情况出现?为什么?怎么解决?解决方式是个啥?

47、

喜欢这篇文章?打赏一下作者吧!

欢迎关注我的其它发布渠道