J2EE框架有一个很好的优点,就是已经实现了面对客户端的多线程机制,你开发J2EE项目基本上不用怎么管。但是你如果不知道的话,如何能编出好的Servlet代码呢?说到这里,又多说一句,如果你没有任何的编程基础,开始就只学java的话,虽然也会学的很好,但是你会失去代码的基本内涵。是,java是抛弃了app层面的指针地址操作,安全了,简单了,但是作为一个app的开发人员不知道内存机制,那怎么可能写的出好的代码呢?所以学c++吧,学了c++,java不用学,自然上手。在这里力挺王亚少老师程序开发环境那门课的前四关于c++的节课,一定要好好的上好那4次课啊,绝对的深层次启蒙。学语言不是从语法开始的,而是从字的构造开始学的,还记得小学一年级学的比较多的是什么么?不是较你认识很多的字,而是主要较你学上下结构,左右结构,和声形字,象形字等等。
接着说,每个客户端请求,都会自动产生一个新的java线程来处理这个请求。那么一个java线程又是什么概念呢?我也说不来,但是可以从内存管理来理解这个概念。java的内存管理。如果你看了前面关于java的介绍,应该知道JVM的内存管理是什么个玩意了,就是JVM的管理机制加上物理内存的结合。这样,自然有一个问题,相比操作系统本身的内存管理而言,JVM的内存管理肯定是不成熟的。
我们还是来看看JVM的内存管理吧。jvm把它的内存管理下的内存称为运行时数据区,包括方法区,堆,Java栈,PC寄存器,本地方法栈。这个结构的划分基本上可以把java对内存的需求划分清楚了。方法区主要放类的类型数据,就是加载java类的时候放的,关于类型数据的构成,在这里就不深入了,它和class文件是对应的,内容也大部分相同。堆是放对象的,就是实例化一个类了。那么到底是什么呢?就是为类的类变量分配内存。因为某个对象是类的对象,所以这对象这边要保存着它对应的类的类型数据的引用。也就是一个指针或说地址。
上面两个内存块是所有线程都共享的。对于方法区的线程共享很好理解,就是某个线程需要用到某个类,那它总得对类的类型数据可见。堆也还好理解,因为对象本身就是可以让各个线程共享的。这里面有关于互斥等处理,这里就不讲。这一块也称为主内存。
下面就是java栈和PC寄存器和本地方法栈了。每一个线程都有自己的Java栈和PC寄存器。本地方法栈就要看线程需不需要了,需要就有。这里面的需要看线程中是否直接调用了本地方法而言的,在前面说过,java是通过javaAPI来和本地方法交互的,但是java也可以直接和本地方法交互,如果java代码中直接调用了本地的某个链接库,就是直接调用了本地方法,这个要另外处理的,因为本地方法的地址已经不归JVM管了,它已经管不到了。
那么,看看Java栈,Java栈就是存放了该线程用到的局部变量,因为是局部变量,所以用栈,用完就弹掉。所以这个栈一般又要分为变量栈和操作栈,具体的就不讲了。每个栈又分很多的栈帧,每个方法调用都分配一个栈帧。这一块也称为工作内存。
那么工作内存要和主内存交互啊,也就是你或许要在某个方法里面设置类变量的值。这里会为工作内存分配一个主内存的缓存,就是那些类变量先缓存在工作内存中,改变后就会写回主内存。
上面的这些,既说明了线程之间是可以共享某个对象的,同时也暗示了问题,就是互斥问题了,每个线程的工作内存都保留着主内存中类变量的缓存,大家都可以改,然后都会写会主内存中,而当线程再去调用主内存里面的类变量的值的时候,那如何保证是自己的缓存写回去的值呢?很有可能是其他线程写回去的值啊。这个问题就是Servlet初期的时候一个蛮大的问题,所以才会出来一个Servlet的singlethread模式,就是服务器端的Servlet每次只处理一个请求,处理完了再处理下一个。问题是解决了,但是却带了更大的问题,就是那么的客户端请求,难道都等着?完全的忽视了java的多线程机制的优势。于是这个模式马上就被抛弃了,后来又提出可以用javaAPI提供的互斥对象来操作,就是synchronized这个词,如果学过操作系统高级教程,就应该对互斥问题非常了解的。就是这个词可以让操作同步,把设置类变量的值和引用类变量的值放到一起执行,其他用到这个对象的线程必须等着。这种方法呢?同样让性能大大的降低了,和前面那个相比较,就是一个是全面的等,一个是局部的等。到后来,人们开始从Servlet本身入手,解决问题不入逃避问题,所以就建议大家尽量妥善处理Servlet中的类变量的问题了,不是说不可以用,而是你用,但是尽量避免发生上面的问题。当然,实在是会遇到这个问题,就可以使用synchronized这个词了。