jvm-多线程
多线程的目的
为什么要使用多线程?可以简单的分两个方面来说:
- 在多个cpu核心下,多线程的好处是显而易见的,不然多个cpu核心只跑一个线程其他的核心就都浪费了;
- 即便不考虑多核心,在单核下,多线程也是有意义的,因为在一些操作,比如IO操作阻塞的时候,是不需要cpu参与的,这时候cpu就可以另开一个线程去做别的事情,等待IO操作完成再回到之前的线程继续执行即可。
多线程带来的问题其实多线程根本的问题只有一个:线程间变量的共享
java里的变量可以分3类:
1类变量(类里面static修饰的变量)
2实例变量(类里面的普通变量)
3局部变量(方法里声明的变量)
根据各个区域的定义,我们可以知道:
- 类变量 保存在“方法区”
- 实例变量 保存在“堆”
- 局部变量 保存在 “虚拟机栈”
“方法区”和“堆”都属于线程共享数据区,“虚拟机栈”属于线程私有数据区。
因此,局部变量是不能多个线程共享的,而类变量和实例变量是可以多个线程共享的。事实上,在java中,多线程间进行通信的唯一途径就是通过类变量和实例变量。
也就是说,如果一段多线程程序中如果没有类变量和实例变量,那么这段多线程程序就一定是线程安全的。
以Web开发的Servlet为例,一般我们开发的时候,自己的类继承HttpServlet之后,重写doPost()、doGet()处理请求,不管我们在这两个方法里写什么代码,只要没有操作类变量或实例变量,最后写出来的代码就是线程安全的。如果在Servlet类里面加了实例变量,就很可能出现线程安全性问题,解决方法就是把实例变量改为ThreadLocal变量,而ThreadLocal实现的含义就是让实例变量变成了“线程私有”的,即给每一个线程分配一个自己的值。
现在我们知道: 其实多线程根本的问题只有一个:线程间变量的共享, 这里的变量,指的就是类变量和实例变量,后续的一切,都是为了解决类变量和实例变量共享的安全问题。
如何安全的共享变量
现在唯一的问题就是要让多个线程安全的共享变量(下文中的变量一般特指类变量和实例变量),上文提到了一种ThreadLocal的方式,其实这种方式并不是真正的共享,而是为每个线程分配一个自己的值。
比如现在有一个特别简单的需求,有一个类变量a=0,现在启动5个线程,每个线程执行a++;如果用ThreadLocal的方式,最后的结果就是5个线程都拥有一份自己的a值,最终结果都是1,这显然不符合我们的预期。
那么如果不使用ThreadLocal呢?直接声明一个类变量a=0,然后让5个线程分别去执行a++;这样结果依旧不对,而且结果是不确定的,可能是1,2,3,4,5中的任一个。这种情况叫做竞态条件(Race Condition),要理解竞态条件先要理解Java内存模型:
要理解java的内存模型,可以类比计算机硬件访问内存的模型。由于计算机的cpu运算速度和内存io速度有几个数量级的差距,因此现代计算机都不得不加入一层尽可能接近处理器运算速度的高速缓存来做缓冲:将内存中运算需要使用的数据先复制到缓存中,当运算结束后再同步回内存。如下图:

因为jvm要实现跨硬件平台,因此jvm定义了自己的内存模型,但是因为jvm的内存模型最终还是要映射到硬件上,因此jvm内存模型几乎与硬件的模型一样:

每个java线程都有一份自己的工作内存,线程访问变量的时候,不能直接访问主内存中的变量,而是先把主内存的变量复制到自己的工作内存,然后操作自己工作内存里的变量,最后再同步给主内存。
现在就可以解释为什么5个线程执行a++最后结果不一定是5了,因为a++可以分解为3步操作:
- 把主内存里的a复制到线程的工作内存
- 线程对工作内存里的a执行a=a+1
- 把线程工作内存里的a同步回主内存
而5个线程并发执行的时候完全有可能5个线程都先执行了第一步,这样5个线程的工作内存里a的初始值都是0,然后执行a=a+1后在工作内存里的运算结果都是1,最后同步回主内存的值肯定也是1。
而避免这种情况的方法就是:在多个线程并发访问a的时候,保证a在同一个时刻只被一个线程使用。
同步(synchronized)就是:在多个线程并发访问共享数据的时候,保证共享数据在同一个时刻只被一个线程使用。
同步基本思想
为了保证共享数据在同一时刻只被一个线程使用,我们有一种很简单的实现思想,就是 在共享数据里保存一个锁 ,当没有线程访问时,锁是空的,当有第一个线程访问时,就 在锁里保存这个线程的标识 并允许这个线程访问共享数据。在当前线程释放共享数据之前,如果再有其他线程想要访问共享数据,就要 等待锁释放 。
我们把这种思想的三个关键点抽出来:
- 在共享数据里保存一个锁
- 在锁里保存这个线程的标识
- 其他线程访问已加锁共享数据要等待锁释放
jvm-多线程的更多相关文章
- 2019百度阿里Java面试题(基础+框架+数据库+分布式+JVM+多线程)
前言 很多朋友对面试不够了解,不知道如何准备,对面试环节的设置以及目的不够了解,因此成功率不高.通常情况下校招生面试的成功率低于1%,而社招的面试成功率也低于5%,所以对于候选人一定要知道设立面试的初 ...
- 70道阿里百度高频Java面试题(框架+JVM+多线程+算法+数据库)
基础与框架 1.String类能被继承吗,为什么 2.String,Stringbuffer,StringBuilder的区别? 3.ArrayList和LinkedList有什么区别 4.类的实例化 ...
- 多线程概述(好处和弊端)(jvm多线程解析、主线程运行示例)
1 package multithread; 2 3 /* 4 * 进程:正在进行中的程序(直译). 5 * 6 * 线程:就是进程中一个负责程序执行的控制单元(执行路径). 7 * 一个进程中可以多 ...
- Java JVM多线程
- Java基础之多线程
1.进程和线程: 进程:正在进行的程序.每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元. 线程:进程内部的一条执行路径或者一个控制单元. 两者的区别: 一个进程至少有一个线程 ...
- JVM内存简单理解
1.首先简单说一下CPU与内存之间的关系 CPU运转速度快,磁盘的读写速度远远不及CPU运转速度,所以设计了内存来缓冲CPU等待磁盘读写:随着CPU的发展,内存读写也远远跟不上CPU的读写速度,CPU ...
- Java基础:Java虚拟机(JVM)
当我们第一次学习Java时这些原理上的东西就会被提到,但是很少有真正去学习.今天开始从头过一遍Java,打算从JVM开始. 1. JVM是什么 2. JRE和JDK 3. JVM结构 3.1. 程序计 ...
- JVM内存结构/JVM运行时数据区,以及堆内存的划分
1.程序计数器: 程序计数器是线程私有的内存,JVM多线程是通过线程轮流切换并分配处理器执行时间的方式实现的,当线程切换后需要恢复到正确的执 行位置(处理器)时,就是通过程序计数器来实现的.此内存区域 ...
- 软帝学院:java多线程知识点分享
1.进程和线程: 进程:正在进行的程序.每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元. 线程:进程内部的一条执行路径或者一个控制单元. 两者的区别: 一个进程至少有一个线程 ...
- 二、运行时JVM结构组成及作用
二.运行时JVM结构组成及作用 程序计数器 是否共享:否,线程私有,每个线程有1个独立的程序计数器! 所处位置:线程私有的内部区域 生命周期:与线程绑定 主要作用: 当前线程执行字节码的行号指示器! ...
随机推荐
- Java——异常的分类
2.异常的分类 异常主要分为:错误.一般性异常(受控异常).运行期异常(非受控异常) 错误:Java虚拟机无法解决的严重问题.如:JVM系统内部错误.资源耗尽等严重情况.一般不编写针对性的代码进行处理 ...
- NOIP2019模拟2019.9.20】膜拜大会(外向树容斥,分类讨论)
传送门. 题解: 我果然是不擅长分类讨论,心态被搞崩了. 注意到\(m<=n-2\),意味着除了1以外的位置不可能被加到a[1]两遍. 先考虑个大概: 考虑若存在\(x,x-1,-,2\)(有序 ...
- TrMemo控件
unit TrMemo; {$R-} interface uses Windows, Messages, Controls, StdCtrls, Classes; const TMWM__Specia ...
- error LNK2001: 无法解析的外部符号 __imp__RegEnumKeyExA@32
错误: error LNK2001: 无法解析的外部符号 __imp__OpenProcessToken@12 error LNK2001: 无法解析的外部符号 __imp__LookupPrivil ...
- makefile.new(7117) : error U1087: cannot have : and :: dependents for same target
makefile.new(7117) : fatal error U1087: cannot have : and :: dependents for same target(2012-05-21 2 ...
- MProtect使用小计【三】 – 权限管理
说明 本篇简单的说一下怎么样使用的VMProtect的权限管理功能,使我们的程序拥有注册码的功能.只用的注册版的程序才能执行指定的函数. 同样这个功能VMProtect也有例子位置在:安装目录\VMP ...
- tomcat 相关异常
端口没有被占用却提示Several ports (8005, 8080, 8009) required by Tomcat??? 解决办法:1.运行cmd进入命令行界面:2.运行命令: netsh w ...
- [资料]C#操作SQL Server数据库
1.概述 ado.net提供了丰富的数据库操作,这些操作可以分为三个步骤: 第一,使用SqlConnection对象连接数据库: 第二,建立SqlCommand对象,负责SQL语句的执行和存储过程的调 ...
- 剑指offer第二版面试题10:斐波那契数列(JAVA版)
题目:写一个函数,输入n,求斐波那契数列的第n项.斐波那契数列的定义如下: 1.效率很低效的解法,挑剔的面试官不会喜欢 使用递归实现: public class Fibonacci { public ...
- 『Golang』—— 标准库之 time
... package main import ( "fmt" "time" ) func main() { time.AfterFunc(time.Milli ...