-
内存基本框架
1.方法区为存放类的描述信息
2.栈中存放一些局部变量表、帧栈、操作数栈等
3.堆上存放着new出来的具体对象(数据组织)
4.PC寄存器存放着执行的指令内存地址
5.执行引擎联接寄存器、堆、栈等,进行代码、数据的运算判断
-
java栈
栈是线程私有的,存储对方法的描述,一般栈中运行的数据,在方法执行完成后,会进行GC
栈中存放:局部变量表、操作数栈(c=a+b这样的代码逻辑)
局部变量表(方法中的参数、static、返回值等信息的描述)
方法调用形成帧栈
-
栈、堆、方法区之间的联系
1.方法区存放类的描述
2.堆放置new出的实例
3.栈放置局部变量表等
-
基础词汇
- 程序:指令、数据及其组织形式的描述。(指令:加减的命令。数据:理解为java对象所拥有的属性,比如年龄、姓名等。组织形式:即加减操作代码和对象所携带的数据在JVM中如何运行的描述。)
- 进程:理解为一个程序(微信的代码及其背后的各种数据)的真正的运行实例。(JVM是铁轨的话,程序即为圣诞老人-代码和礼物-数据,进程就是和谐驯鹿号,程序根据礼物派发地,进行if、for等操作跳转。)进程是线程的容器。
- 线程:是操作系统能够进行运算调度的最小单位,一条线程指的是进程中一个单一顺序的控制流(if、for代码逻辑)。【和谐号中有好多圣诞老人的克隆人,即许多一样的代码分布到不同的车厢(一个车厢就是一个线程的调用栈)】
- 调度:将cpu计算资源分配给不同的线程。
- 并行 : 多台计算机或者多个CPU实例同时处理同一段逻辑,比如10台计算机同时计算1到100的和,不涉及资源共享(春运10个窗口开始同时买火车票,假设火车票无限量且无座次限制)
- 并发:CPU通过调度算法处理同一段逻辑的时候,涉及共享资源。(两个人同时买火车票最后一张,此时就要商量下谁更紧急,此时车票数量有限制)
-
正传
有了以上的前传,应该会了解些时代背景了,溜哒同学表示以上一定会有错误,请各位大虾批评指正。
多线程问题,即多个进程跑同一段逻辑的时候,因涉及共享资源,不同优先级的线程按照不同执行顺序执行会出现不一样的结果。我们要处理的就是控制多线程控制流的执行,保证线程按照一定顺序执行。
CPU1命令dog1从a点前进100米到b点,CPU2命令dog2从b点倒退100米到a点,代码书写的是,先前进100米到b点,再后退100米到a点。但是两条线程执行调度并不是一定按照我们代码书写的希望所做,很可能前进了50米,然后后退了20米,再前进..后退..
问题出现在,两个人通过同一个遥控器在遥控同一个dog,第一个可能按了前进,紧接着第二个人按了后退。
那么如何才能保证先前进100米,再后退100米呢?这就需要第一个人拿到遥控器先操作完100米,然后把遥控器交给第二个人,第二个人来操作后退的100米。那么问题来了,两人争夺遥控器,其实是争夺遥控器的使用权,代码中如何才能判断谁有使用权呢?这时可以想一下通用的一种分布式锁的实现——在redis中设置一个标记,setIfabsent()可以判断如果redis中没有这个key,就set进一个值,如果有就set不进去,能set进去就能锁住这段代码!(集群中的定时任务,10台机器都想跑同一个定时任务,那么哪台机器在redis中设定了该值即获取了该锁,那么就执行任务,其他的9台直接过掉)。
回到遥控器的使用权这里,道理一样,哪个人(线程)获得执行权(锁)就能遥控dog,那么这个锁放到哪里呢?java提供了自己的锁机制。
-
锁
1.synchronized
使用synchronized
public void drink(){
synchronized(this){
// 对象锁,调用该方法的对象使用的锁
}
}
public synchronized void eat(){
// 对象锁,调用该方法的对象使用的锁
}
public void paly(){
Object obj = new Object();
synchronized(obj){
// 对象锁,使用object对象锁
}
}
public void laugh(){
synchronized(Object.class){
// 类锁
}
}
当多个线程调用某个被synchronized修饰的代码部分的时候就会进行获取锁,执行,放开锁的过程。溜哒同学觉得用天朝高速收费站来看锁机制对理解这玩意儿还是有点帮助的。
我们先说this锁即对象锁,就像八达岭高速收费站那里,好多收费站,好多车来过路,每辆车都是一个对象,而每个收费站都是被synchronized修饰的代码,若想通过执行代码,就需要获取锁,即那计费卡或者ETC上登记。每个线程拥有自己的调用栈,栈中包含着对象(堆中才是,栈中撑死算引用),帧栈(函数调用)等。每个线程用自己的对象锁来执行帧栈中的代码,其实线程间是不存在调用交叉的。当然这时候,我们只是利用多线程,更多的利用CPU资源来完成任务,这里并不涉及共享资源,也就没有多线程问题。下图里,每个线程在自己的栈中跑的很欢实,到了收费站,拿卡,登记等。自己做自己的事情就好。这比一条告诉公路,一堆汽车来过路,处理的快很多。
上图中其实没有必要加锁,但是如果到了春运大流量的时候,过路卡稀缺的时候就需要加锁了
上图中,每辆汽车都想先过去,好早点回家过年,但是就3张了,谁先拿到谁先过路,谁就先执行代码。比如类锁,JVM全局中某个Class对象只有一个,哪条线程拿到,哪条先执行。其实这只是锁的一个简单理解。用锁,我们重点关注的是处理共享资源问题。
我们把这张图调整下,这样理解,理解为六个人,每个人拥有一个数字,需要将这个数字放到前方一个又大又破的计算器中,这个计算器只能做从小到大的加和动作,而且每次只能计算两个数。比如:2+3 = 5, 计算完了5之后,才能计算5+6=11的操作(强行扯上关系)
可以看到第一步的时候需要1号线程先过去,把他的数字1放到计算器中,他在synchronized代码之前已经获取锁(图中的红色圈),且全局只有这一个锁(比如类锁),虽然其他线程跑的比1号线程快,但是到了synchronized锁住的代码的时候,因为没有通行证,就只能跟那儿等着(阻塞),然后1号线程完成把数放到计算器中,最后它还要把锁交给2号线程,因为我们有一个只能依次向上加和的限制,所以要notify2号线程(线程间通信),然后依次累加计算。这样计算器正常运行,结果如我们所愿。
-
小结
多线程目的即高效的使用CPU资源,去处理没有共享资源的部分,加快任务执行速度,但是如果到了共享资源的时候,需要看是不是有数据的前后依赖关系,如果有依赖关系,我们需要让线程间通信,哪个线程该先执行,哪个该后执行,最终保证计算正确。
-
后传
synchronized 其实不只是一个锁的概念,还有一个内存可见性需要我们了解。线程第二季的时候,我们会由synchronized 的内存可见性引出内存可见性和volatile关键字,另外也会提到java5之后的lock锁,可以提前了解下hashtable和ConcurrentHashMap相关的知识。线程通信也是很6的部分,不过也是很难操作的呢!!请各路英雄轻拍,指点。希望慢慢的进步,将来不奢望成为大牛,成个小牛就行,但是不能是肥牛不是。