今天公司的一位技术专家在我们这里聊天,聊完之后我突然对Java并发又多了些许理解。
先以Java虚拟机的内存结构开始,在《Java虚拟机规范》中,Java虚拟机被定义为程序计数器、Java栈、本地方法栈、Java堆和方法区五大块。
程序计数器为线程私有的,并发处理在处理器层面实际上依旧是串行的,处理轮流在各个线程轮流执行其指令,因速度非常快,而达到了宏观并行微观串行的效果。处理器每条要执行的指令会由程序计数器给出其地址。也就是说,程序计数器用来给出处理器下一条指令的地址。
Java栈与Java堆是Java程序员最经常接触的两块内存区域。它描述了Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧,栈帧用来保存局部变量表、操作数栈、动态链接、方法出口等信息,每个方法从调用直至执行完毕,就对应着一个栈帧在虚拟机栈中进栈和出栈的过程。从这个过程来看,如果虚拟机中同时执行了多个线程,每个线程都会在运行着某个方法,每个方法都对应着一个栈帧,但是这些不同的栈帧不可能在同一个栈中,否则它们就无法同时执行,因此可以得出结论,Java虚拟机中的Java栈的个数与线程的个数是相同的,Java栈的生命周期与线程是一致的。
本地方法栈与虚拟机栈所发挥的作用是相似的,它们之间的区别不过是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为虚拟机使用的Native方法服务。因此hotspot虚拟机直接将此块实现与Java栈合二为一。
Java堆这块内存区域存在的唯一目的就是存放对象实例。这一点在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。
方法区用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。因此也有叫非堆,将方法区也划进Java堆中,来方便进行垃圾回收。
以上,我们可以简化的将Java虚拟机的内存看成是五个这样的结构:
首先,要声明的是,只有分配在Java堆(包括方法区)中的变量才会有线程问题,分配在Java栈中的变量是方法内部的变量 ,最重要的是,在方法中,每个线程对应一个Java栈,只会访问本栈中的变量值,因此方法内部的局部变量不会出现线程问题。而Java栈中会存储对Java对象的引用,也就意味着,可能多个线程都存在着对Java堆中同一对象的引用 ,也就有可能会出现多个线程同时修改对象变量的情形。
还有一点,一个类的所有对象,实际上不一样的只有它们的成员变量值可能会有差异,它们的方法的代码是不会有差异的,因此虚拟机在Java堆上为对象分配内存时,其实相当于以数组的形式为这个对象分配内存,数组中依次存储着该类的各个成员变量副本。而类变量(静态变量)全部存储在方法区(永久代)中,并且只有一份。因此可以说,Java堆存储所有对象,而方法区中存储类对象(即String.getClass()的返回结果)。
如果一个类的某些非static方法被synchronized关键字修饰,那么就类似于synchronized(this)的效果,线程执行到该方法时,会首先获取一个锁,这个锁将会锁定该对象。因此,假如一个类的两个非static方法都用synchronized修饰,那么若多个线程执行到这些方法,只能以串行的方式一个一个执行,即使他们要调用的方法不一样,也会等待,因为他们要获得的锁是同一个锁,这个锁锁的是同一个对象。切记,Java中的锁永远只锁对象,不会锁方法。
而如果该类的多个static方法也被synchronized修饰,那么线程执行到这些static方法时,也会申请获得一个锁,而这个锁则会锁定类对象,而与对象无关。如果多个线程执行这些static方法时,即使方法不同,也会需要等待,因为这些锁锁的是同一个类对象,因此它们是同一个锁,因此,这些线程依旧需要以串行的方式来执行。
以上。可总结,Java中的锁只会锁对象,或锁普通对象,或锁类对象。如果多个锁锁的是同一对象,那么这些锁都是一个锁。而Java的锁之间是互斥的,如果线程获得锁时发现锁已经被别的线程获得,那么该线程只能等待,直至上一个线程释放了所持有的锁,这个线程才有机会获得锁。
其实今天大拿还分享了些数据库中的并发,就下次另开一篇文章来写吧。