锁机制

进程和线程的区别

  1. 进程和线程的由来
    进程为了解决cpu浪费而出现,独占内存空间,一个进程代表一段程序,各个进程互不干扰,可以相互转换,记录跳转位置以知道切换回来应在哪里开始继续开始。
    线程是将进程更加细化,粒度更小,在同一个进程中包含多个线程,相互切换,即进程的子任务,可以解决进程中,无关子任务之间的并发操作。
    总结:工程学就是在不断面临挑战的过程中,不断的推出新概念和技术,将任务不断细化,一个迭代的过程。
    在这里插入图片描述
  2. 区别
    在这里插入图片描述
    在这里插入图片描述
    总结:
    在这里插入图片描述
    一个线程挂掉相当于一个进程挂掉了.
    在宏观上看像多个任务执行是因为,cpu给子任务分配的时间很短,切换的频率高导致的。
    在这里插入图片描述
  3. start和run方法的区别:正在底层实现来阐述
    1)首先卡一个start的源码:
    在这里插入图片描述
    在start方法中有个start0是主要方法,查看start0的源码
    在这里插入图片描述
    start0源码是个native方法,即调用非java的方法

在这里插入图片描述
,那么在http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/在查看本地方法

在这里插入图片描述
在途中的路径中找到Thread.c的路径,查找start0,即如图,调用了JVM_StartThread
在这里插入图片描述
那么就到相应的的文件JVM.h中去查找JVM_StartThread
在这里插入图片描述
其中比较重要的是如下图那一句,是创建一个新的线程
在这里插入图片描述
看一下thread_entry方法,创建一个新的线程,即call虚拟机,调用新建子线程的run方法
在这里插入图片描述
在这里插入图片描述

Thread和Runnable的区别

  1. Thread是个类,Runnable是个接口
    在这里插入图片描述
    在这里插入图片描述
  2. 采用Thread实现多线程
    建立一个类MyThread
    在这里插入图片描述
    主类是ThreadDemo
    在这里插入图片描述
  3. 采用Runnable实现多线程
    由于Thread的源码中存在一个构造函数,可以传入一个Runnable类型的参数。如下图所示:

在这里插入图片描述
步骤如下:建立一个类MyRunnable如下:
在这里插入图片描述
实现Runnable接口,重写run方法
用RunnableDemo来测试:
由于Runnable中无start方法,导致MyRunnable无start方法,所以要创建Thread对象,在Thread的构造方法将Runnable的对象实例传入。如下图所示:

在这里插入图片描述
4. 总结:
Thread是继承Runnable接口的二类,使run方法支持多线程
因类的单一继承原则,推荐多使用Runnable接口。保证系统的可扩展性,将业务封装在run的方法中,使业务可以实现多线程的效果。

如何给run()方法传参(再思考,不是重点)

在这里插入图片描述

如何实现处理线程的返回值

1.主线程等待法
缺点:必须自己实现等待循环的逻辑,当成员变量比较多时,代码显得臃肿。需要循环多久是无法控制的,(即如果在等待的100ms内子线程反悔了结果,可是主线程还在等待,所以控制的粒度大小无法恰到好处)
创建一个类CycleWait
在这里插入图片描述
在此类建立主函数:
在这里插入图片描述

  1. ??使用Thread类的join()阻塞当前线程以等待子线程处理完毕。
    优点:是比主线程等待法会更精准的控制,但是还是粒度不够细。
    会阻塞调用join方法的线程,直到join所在线程完成为止。
    针对上一个改变是用t.join()去替代循环等待代码块,如下:

在这里插入图片描述
3. ??通过Callable接口实现:通过FutureTask或线程池获取
1)通过FutureTask获取返回值
建立一个集成Callable接口的类MyCallable
重写call方法
如下:
在这里插入图片描述
Callable接口如下:
在这里插入图片描述
创建 一个类FutureTaskDemo
在这里插入图片描述

FutureTask的源码如下
在这里插入图片描述
主要的方法有FutureTask的构造方法,接受Callable的实例
在这里插入图片描述
还有个重要方法isDone方法如下:判断线程是否完成
在这里插入图片描述
不带参数的get方法
在这里插入图片描述
带参数的get方法:
其中有一个超时机制,超时就抛出异常,即一定时间内还没有返回Callable中call方法的返回值,就会抛出异常。
在这里插入图片描述
2)通过线程池返回返回值
优点:可以提交多个实现Callable方法call的类,让线程池并发的处理结果。
常见一个ThreadPoolDemo类
在这里插入图片描述

线程状态

可以通过Thread的源码看State源码:
在这里插入图片描述
六个状态:
在这里插入图片描述
在这里插入图片描述以下方法实现限期等待
在这里插入图片描述
在这里插入图片描述
当线程的run方法结束代表线程状态为结束,也许线程仍是活的,但是已经不是单独执行的进程。
在一个结束状态的线程调用start方法会抛出异常
在这里插入图片描述

sleep和wait区别

基本区别:
在这里插入图片描述
注意:正因为wait是要释放锁,所以一定是在sychronized方法或代码块,即只有获得了锁才能释放锁。
最主要的本质区别:
在这里插入图片描述

  1. sleep方法
    在这里插入图片描述
  2. wait方法
    在这里插入图片描述
    在这里插入图片描述
    验证过程 :
    建立线程A和B,一个是用wait的超时等待方法,另一个用sleep睡眠方法
    lock设为对象锁,线程1和2都要锁住这个lock对象才能完成任务,线程A在wait方法后如果释放了对lock的锁,线程B就会进行,否则会堵塞。事实上是wait方法会让线程1释放锁。
    1)线程A用wait方法,线程B用sleep方法—证明wait释放锁。
    在这里插入图片描述
    在这里插入图片描述
    2)线程A用sleep方法,线程B用wait方法----证明sleep不释放锁。

Synchronized

实现方法

分类:对象锁和类锁
实现方式有同步代码块和同步方法
对象锁----1)同步代码快,锁住对象2)同步方法:锁住非静态方法
类锁-----1)同步代码块,锁住类.class,即class对象2)同步方法:锁住静态方法
在这里插入图片描述

notify和notifyall的区别

wait通过这两者来叫醒(在不使用超时机制时)
对于java虚拟机运行程序,每个对象都会涉及锁池和等待池两个概念。
针对对象而言
在这里插入图片描述
在这里插入图片描述
总结:
在这里插入图片描述
证明:

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

yeild

在这里插入图片描述
验证:
如果线程A先执行,会不会在i=5时让出cpu给线程B,但是结果是不一定的,因为虽然调用yeild方法的线程有让出cpu的意愿,但是最终的决定权在进程调度器那。
在这里插入图片描述

如何中断线程在这里插入图片描述

会造成线程数据不一致,即线程A不知道线程B调用stop方法图软停止,最终造成数据不一样的后果。
在这里插入图片描述
第一条线程处于被阻塞状态即之前调用了wait或sleep或join方法。
在这里插入图片描述

验证:
在这里插入图片描述
在这里插入图片描述

各个线程状态的转换流程

在这里插入图片描述

Synchronized底层实现原理

底层代码

  1. 依靠对象头和monitor实现。
  2. 对象在内存中的布局:
    对象头(着重说这个)
    实例数据
    对齐填充
  3. 对象头的结构
    在这里插入图片描述
    Mark Word非固定的存储数据,以便存储更多的数据
    在这里插入图片描述
  4. monitor锁
    每个java对象都隐含一个默认锁,即monitor(同步工具)
    monitor存在每个java对象的对象头中
    重量级锁:指针指向monitor的起始位置。
    _waitSet:等待池
    _entryList:锁池
    在这里插入图片描述
    让我们先看一下_owner,它指向持有ObjectMonitor对象的线程。当多个线程同时访问一段同步代码时,会先存放到 _EntryList 集合中,接下来当线程获取到对象的monitor时,就会把_owner变量设置为当前线程。同时count变量+1。如果线程调用wait() 方法,就会释放当前持有的monitor,那么_owner变量就会被置为null,同时_count减1,并且该线程进入 WaitSet集合中,等待下一次被唤醒。
    若当前线程顺利执行完方法,也将释放monitor,重走一遍刚才的内容,也就是_owner变量就会被置为null,同时_count减1,并且该线程进入 WaitSet集合中,等待下一次被唤醒。
    因为这个锁对象存放在对象本身,也就是为什么Java中任意对象可以作为锁的原因。
    在这里插入图片描述
    在这里插入图片描述
    synchronized对于再次请求自己持有对象锁的临界资源时,是可重入的。
    在命令行输入以下命令行,查看字节码
    在这里插入图片描述
    synchronized代码块的底层实现
    在这里插入图片描述
    编译器需要确保方法中调用过的每条monitorenter指令都要执行对应的monitorexit 指令。为了保证在方法异常时,monitorenter和monitorexit指令也能正常配对执行,编译器会自动产生一个异常处理器,它的目的就是用来执行 异常的monitorexit指令。而字节码中多出的monitorexit指令,就是异常结束时,被执行用来释放monitor的。

synchronized方法的底层实现
在这里插入图片描述
字节码中并没有monitorenter指令和monitorexit指令,取得代之的是ACC_SYNCHRONIZED标识,JVM通过ACC_SYNCHRONIZED标识,就可以知道这是一个需要同步的方法,进而执行上述同步的过程,也就是_count加1,这些过程。

  1. synchornized优化过程
    早期:
    在这里插入图片描述
    java6,有了很大的优化
    在这里插入图片描述

1)自旋锁和自适应自旋锁

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

锁优化

锁消除:虚拟机的另外一个锁优化
在这里插入图片描述
锁粗化:
在这里插入图片描述

synchronized锁的四种状态

锁的升级顺序
无锁–.>偏向锁—>轻量级锁—>重量级锁
偏向锁
在这里插入图片描述
轻量级锁:
在这里插入图片描述
在这里插入图片描述

synchronized和ReentrantLock的区别

  1. ReentrantLock:https://www.cnblogs.com/takumicx/p/9338983.html
    在这里插入图片描述
    ReentrantLock源码如下:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述
ReentranLock适合编写应用于下列场景
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
notEmpty用在take方法中,count==0表队列为空,notEmpty.await()等待线程加入队列之后返回
在这里插入图片描述
not
在这里插入图片描述

总结:
在这里插入图片描述

AQS

源码的几个核心方法和变量
在这里插入图片描述
在这里插入图片描述
先入先出的线程队列
在这里插入图片描述
CAS操作方法
在这里插入图片描述
AQS实现同步结构至少要实现两个基本方法即acquire()和release()方法
acquire():获取资源的独占权
在这里插入图片描述
release():释放对某个资源的独占
在这里插入图片描述

JMM

在这里插入图片描述
JMM的工作内存是在每个线程中的

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值