JVM原理(java虚拟机)
1.jvm内存
在Java中,“线程”指两件不同的事情:
1、java.lang.Thread类的一个实例;
2、线程的执行。
使用java.lang.Thread类或者java.lang.Runnable接口编写代码来定义、实例化和启动新线程。
一个Thread类实例只是一个对象,像Java中的任何其他对象一样,具有变量和方法,生死于堆上。
Java中,每个线程都有一个调用栈,即使不在程序中创建任何新的线程,线程也在后台运行着。
一个Java应用总是从main()方法开始运行,mian()方法运行在一个线程内,它被称为主线程。
一旦创建一个新的线程,就产生一个新的调用栈。
线程总体分两类:用户线程和守候线程。
当所有用户线程执行完毕的时候,JVM自动关闭。但是守候线程却不独立于JVM,守候线程一般是由操作系统或者用户自己创建的
1.类变量(类里面static修饰的变量) 保存在“方法区” 线程共享数据区
2.实例变量(类里面的普通变量)保存在“堆” 线程共享数据区
3.局部变量(方法里声明的变量)保存在 “虚拟机栈” 线程私有数据区
因此,局部变量是不能多个线程共享的,而类变量和实例变量是可以多个线程共享的。
事实上,在java中,多线程间进行通信的唯一途径就是通过类变量和实例变量。
一段多线程程序中如果没有类变量和实例变量,就是没有线程共享数据区,那么这段多线程程序就一定是线程安全的。
其实多线程根本的问题只有一个:线程间变量的共享,
这里的变量,指的就是类变量和实例变量,后续的一切,都是为了解决类变量和实例变量共享的安全问题。
1.2安全的共享变量
有一个类变量a=0,现在启动5个线程,每个线程执行a++;
1.ThreadLocal的方式,其实这种方式并不是真正的共享,而是为每个线程分配一个自己的值,结果就是5个线程都拥有一份自己的a值,最终结果都是1。
2.直接声明一个类变量a=0(类里面static修饰的变量) 保存在“方法区” 线程共享数据区,然后让5个线程分别去执行a++;这样结果依旧不对,而且结果是不确定的,可能是1,2,3,4,5中的任一个。这种情况叫做竞态条件(Race Condition)。
解决方法:
每个java线程都有一份自己的工作内存,线程访问变量的时候,不能直接访问主内存中的变量,而是先把主内存的变量复制到自己的工作内存,然后操作自己工作内存里的变量,最后再同步给主内存。
为什么5个线程执行a++最后结果不一定是5了,因为a++可以分解为3步操作:
1.把主内存里的a复制到线程的工作内存
2.线程对工作内存里的a执行a=a+1
3.把线程工作内存里的a同步回主内存
5个线程并发执行的时候完全有可能5个线程都先执行了第一步,这样5个线程的工作内存里a的初始值都是0,
然后执行a=a+1后在工作内存里的运算结果都是1,最后同步回主内存的值肯定也是1。
避免这种情况的方法就是:在多个线程并发访问a的时候,保证a在同一个时刻只被一个线程使用。
同步(synchronized)就是:在多个线程并发访问共享数据的时候,保证共享数据在同一个时刻只被一个线程使用。
为了保证共享数据在同一时刻只被一个线程使用,我们有一种很简单的实现思想,就是在共享数据里保存一个锁,
当没有线程访问时,锁是空的,当有第一个线程访问时,就在锁里保存这个线程的标识并允许这个线程访问共享数据。
在当前线程释放共享数据之前,如果再有其他线程想要访问共享数据,就要等待锁释放。
1.3jvm的同步实现
jvm中的三种锁都是以上述思想为基础的,只是实现的“重量级”不同,jvm中有以下三种锁(由上到下越来越“重量级”):
1.偏向锁
2.轻量级锁
3.重量级锁