java学习笔记 Java多线程JVMthreadJSP
程序员文章站
2024-02-21 19:44:46
...
Java学习笔记(适合面试前复习)
关键字: collection, i/o, jvm, jdk, thread
Java基础
基本类型不是new出来的则是放在栈里面,对象的引用也是放在栈里面的,只要是用new()来新建对象的,都会在堆中创建
String类被设计成为不可改变(immutable)的类。如果你要改变其值,可以,但JVM在运行时根据新值悄悄创建了一个新对象,然后将这个对象的地址返回给原来类的引用。这个创建过程虽说是完全自动进行的,但它毕竟占用了更多的时间。在对时间要求比较敏感的环境中,会带有一定的不良影响。
关于String str = "abc"的内部工作。Java内部将此语句转化为以下几个步骤:
(1)先定义一个名为str的对String类的对象引用变量:String str;
(2)在栈中查找有没有存放值为"abc"的地址,如果没有,则开辟一个存放字面值为"abc"的地址,接着创建一个新的String类的对象o,并将o的字符串值指向这个地址,而且在栈中这个地址旁边记下这个引用的对象o。如果已经有了值为"abc"的地址,则查找对象o,并返回o的地址。
(3)将str指向对象o的地址。
值得注意的是,一般String类中字符串值都是直接存值的。但像String str = "abc";这种场合下,其字符串值却是保存了一个指向存在栈中数据的引用!
java内存泄露的两个条件:无用,无法回收。
BigInteger能表示任意精度的整数, BigDecimal能表示任意精度的浮点数, 但都是用精度换速度的做法
容器集合
List的子类里面LinkedList和ArrayList是非同步的,而Vector和Stack是同步的, LinkedList底层采用的是双向链表(前指针+数据+后指针), 因此查询效率低, 增删效率高; ArrayList底层采用的是Array,因此查询效率高, 增删效率低; Vecotr是重量级的List, 一般在并发保证线程安全时采用
如果想跟踪添加给HashSet的元素的顺序,LinkedHashSet实现会有帮助。LinkedHashSet的迭代器按照元素的插入顺序来访问各个元素。
List中的元素有顺序, 可重复, Set中的元素无序, 不可重复(SortedSet是有序的)
Iterator的next方法将跳到下一个元素, 并返回刚刚跳过的元素, 因此使用remove方法的时候,必须先调用next方法
在Map 中插入、删除和定位元素,HashMap 是最好的选择。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。
LinkedHashMap扩展HashMap,以插入顺序将关键字/值对添加进链接哈希映像中。
Hashtable 类似于 HashMap,但是不允许 null 键和 null 值。它也比 HashMap 慢,因为它是同步的。
效率
在方法内部频繁存取变量的时候, 使用局部变量比使用实例变量和静态变量要有更高的效率
异常对性能不利。抛出异常首先要创建一个新的对象。Throwable接口的构造函数调用名为fillInStackTrace()的本地(Native)方法,fillInStackTrace()方法检查堆栈,收集调用跟踪信息。只要有异常被抛出,VM就必须调整调用堆栈,因为在处理过程中创建了一个新的对象。
I/O
任何有能力产生数据流(源)的java io对象就可以看作是一个InputStream对象, 何有能力接收数据源(流)的java io对象我们就可以看作是一个OutputStream对象
io数据流有两种类别,一种就是InputStream/OutputStream,一种是Reader/Writer, 分别用来处理字节流和字符流, 字符也是一种字节, 为什么还要用Reader/Writer呢, 这是因为在处理字符的时候可能涉及到转码,转码主要是Reader与InputStream,以及Writer与OutputStream之间, 转码的过程都是通过InputStreamReader和OutputStreamWriter来做的, 有时候我们可能没有显式的调用这两个类, 但是相关的类在内部是做了这种转换的, 比如FileReader其实就是通过InputStreamReader将数据从FileInputStream取过来的, 比如将操作系统的文字编码转化为Unicode(因为在Java内部都是采用Unicode来保存数据的), 记住这里有一个前提,那就是操作系统当前的编码格式和文档的编码格式是一直的,如果不一致就必须指定当前的InputStream的编码格式.如果不知道字符所采用何种编码方式, 那么就不要使用Reader和Writer而应该改用InputStream和OutStream
对输入/输出流进行缓冲可以提高代码执行效率, 也就是使用BufferedXxxx和BufferedXxxx对原有的流进行封装, 缓冲是一个非常重要而基本的加速I/O访问的技术
final关键字修饰的变量,表明该变量是不变的, 但是这里的不变只是所引用本身不变, 至于引用的对象是否改变则没有约束, 比如final StringBuffer a=new StringBuffer("immutable");a.append(" broken!"); //编译通过, 这里我们可以看出, final只对引用的"值"(也就是它所指向的那个对象的内存地址)有效, 它迫使引用只能指向初始指向的那个对象, 改变它的指向会导致编译器错误, 至于它所指向的对象的变化, final是不负责的.
当你要clone的类里面含有可修改的引用字段的时候,那么你一定要把整个类的蓝图进行复制,如果对你clone得到的对象进行修改的时候还会影响到原来的实例,那么这是不可取的。所以应该这样clone()
JVM
一般来说,我们使用虚拟机的类装载时需要继承抽象类java.lang.ClassLoader,其中必须实现的方法是loadClass(),对于这个方法需要实现如下操作:(1) 确认类的名称;(2) 检查请求要装载的类是否已经被装载;(3) 检查请求加载的类是否是系统类;(4) 尝试从类装载器的存储区获取所请求的类;(5) 在虚拟机中定义所请求的类;(6) 解析所请求的类;(7) 返回所请求的类。
Java中的类的装载过程也就是代理装载的过程。比如:Web浏览器中的JVM需要装载一个小应用程序TestApplet。JVM调用小应用程序装载器ACL(Applet ClassLoader)来完成装载。ACL首先请求它的父装载器, 即系统装载器装载TestApplet是否装载了这个类, 由于TestApplet不在系统装载器的装载路径中, 所以系统装载器没有找到这个类, 也就没有装载成功。接着ACL自己装载TestApplet。ACL通过网络成功地找到了TestApplet.class 文件并将它导入到了JVM中。在装载过程中, JVM发现TestAppet是从超类java.applet.Applet继承的。所以JVM再次调用ACL来装载java.applet.Applet类。ACL又再次按上面的顺序装载Applet类, 结果ACL发现他的父装载器已经装载了这个类, 所以ACL就直接将这个已经装载的类返回给了JVM , 完成了Applet类的装载。接下来,Applet类的超类也一样处理。最后, TestApplet及所有有关的类都装载到了JVM中。
当 JVM 需要使用类时,它根据名称向 ClassLoader 请求这个类,然后 ClassLoader 试图返回一个表示这个类的 Class 对象。通过覆盖对应于这个过程不同阶段的方法,可以创建定制的 ClassLoader。其中有个loadClass(String name, boolean resolve)方法,该方法为ClassLoader的入口点,在jdk1.2以后,loadClass方法将缺省调用findClass方法,详细内容可以参考API文档,我们编写的ClassLoader主要就是为了覆盖以上两个方法。
Java EE
Tomcat Server处理一个http请求的过程
假设来自客户的请求为:
http://localhost:8080/wsota/wsota_index.jsp
1) 请求被发送到本机端口8080,被在那里侦听的Coyote HTTP/1.1 Connector获得
2) Connector把该请求交给它所在的Service的Engine来处理,并等待来自Engine的回应
3) Engine获得请求localhost/wsota/wsota_index.jsp,匹配它所拥有的所有虚拟主机Host
4) Engine匹配到名为localhost的Host(即使匹配不到也把请求交给该Host处理,因为该Host被定义为该Engine的默认主机)
5) localhost Host获得请求/wsota/wsota_index.jsp,匹配它所拥有的所有Context
6) Host匹配到路径为/wsota的Context(如果匹配不到就把该请求交给路径名为""的Context去处理)
7) path="/wsota"的Context获得请求/wsota_index.jsp,在它的mapping table中寻找对应的servlet
8) Context匹配到URL PATTERN为*.jsp的servlet,对应于JspServlet类
9) 构造HttpServletRequest对象和HttpServletResponse对象,作为参数调用JspServlet的doGet或doPost方法
10)Context把执行完了之后的HttpServletResponse对象返回给Host
11)Host把HttpServletResponse对象返回给Engine
12)Engine把HttpServletResponse对象返回给Connector
13)Connector把HttpServletResponse对象返回给客户browser
JDK新特性
注释(Annotation),得先提一提什么是元数据(metadata)。所谓元数据就是数据的数据。也就是说,元数据是描述数据的。就象数据表中的字段一样,每个字段描述了这个字段下的数据的含义。而J2SE5.0中提供的注释就是java源代码的元数据,也就是说注释是描述java源代码的。
多线程 线程
实现线程,有两种方法,一种是继承Thread类,一种是实现Runnable接口。优先采用实现Runnable接口的方法。首先,把需要共享的数据放在一个实现Runnable接口的类里面,然后,把这个类的实例传给多个Thread的构造方法。这样,新创建的多个Thread,都共同拥有一个Runnable实例,共享同一份数据。如果采用继承Thread类的方法,就只好使用static静态成员了。如果共享的数据比较多,就需要大量的static静态成员,令程序数据结构混乱,难以扩展。这种情况应该尽量避免。使用Runnable还有一个好处就是多线程应用对象可以继承别的对象而不是必须继承Thread类, 从而提高多线程对象的灵活性.
多线程的执行过程是当一个程序(一个线程)执行的过程中, 通过调用Thread.start(), 让当前的程序(线程)继续执行的同时, 那个start的Thread会同时执行位于run()方法中的代码, 多段可执行代码交替执行的过程.
由于多个线程拥有度大力的执行对战和程序执行上下文, 因此定义在线程方法中的局部变量不会出现资源共享的问题, 资源共享同步主要发生在成员变量上.
按照线程体在计算机系统内存中的状态不同,可以将线程分为创建、就绪、运行、睡眠、挂起和死亡等类型。这些线程状态类型下线程的特征为:
创建状态:当利用new关键字创建线程对象实例后,它仅仅作为一个对象实例存在,JVM没有为其分配CPU时间片等线程运行资源;
就绪状态:在处于创建状态的线程中调用start方法将线程的状态转换为就绪状态。这时,线程已经得到除CPU时间之外的其它系统资源,只等JVM的线程调度器按照线程的优先级对该线程进行调度,从而使该线程拥有能够获得CPU时间片的机会。
睡眠状态:在线程运行过程中可以调用sleep方法并在方法参数中指定线程的睡眠时间将线程状态转换为睡眠状态。这时,该线程在不释放占用资源的情况下停止运行指定的睡眠时间。时间到达后,线程重新由JVM线程调度器进行调度和管理。
挂起状态:可以通过调用suspend方法将线程的状态转换为挂起状态。这时,线程将释放占用的所有资源,由JVM调度转入临时存储空间,直至应用程序调用resume方法恢复线程运行。
死亡状态:当线程体运行结束或者调用线程对象的stop方法后线程将终止运行,由JVM收回线程占用的资源。
在Java中比较特殊的线程是被称为守护(Daemon)线程的低级别线程。这个线程具有最低的优先级,用于为系统中的其它对象和线程提供服务。将一个用户线程设置为守护线程的方式是在线程对象创建之前调用线程对象的setDaemon方法。典型的守护线程例子是JVM中的系统资源自动回收线程,它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。
jvm固定了线程和内存之间的关系, jvm的内存又分为主内存和工作内存, 线程要操作的数据放在工作内存中, 工作内存的变脸是主内存的拷贝, 线程执行完后工作内存中的变量还需要更新主内存中对应的变量.对于使用了synchornized保护的代码对主内存变量将被锁定从而保证主内存和工作内存之间的变量报纸一致性.但是使用synchonrized的代码在性能上会带来不小的损失
ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是thread local variable(线程局部变量)。也许把它命名为ThreadLocalVar更加合适。线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。它的内部其实可以理解为仅有一个Entry的Map, key是当前的thread, 而value则是要保存的变量.因此它只有两个方法set()将变量跟thread关联, get()方法从map中取出变量, 还有一个就是initValue方法, 是用来生成自己的ThreadLocal的时候用的, 用来给出默认值, 在get和set方法中调用
基本类型不是new出来的则是放在栈里面,对象的引用也是放在栈里面的,只要是用new()来新建对象的,都会在堆中创建
String类被设计成为不可改变(immutable)的类。如果你要改变其值,可以,但JVM在运行时根据新值悄悄创建了一个新对象,然后将这个对象的地址返回给原来类的引用。这个创建过程虽说是完全自动进行的,但它毕竟占用了更多的时间。在对时间要求比较敏感的环境中,会带有一定的不良影响。
关于String str = "abc"的内部工作。Java内部将此语句转化为以下几个步骤:
(1)先定义一个名为str的对String类的对象引用变量:String str;
(2)在栈中查找有没有存放值为"abc"的地址,如果没有,则开辟一个存放字面值为"abc"的地址,接着创建一个新的String类的对象o,并将o的字符串值指向这个地址,而且在栈中这个地址旁边记下这个引用的对象o。如果已经有了值为"abc"的地址,则查找对象o,并返回o的地址。
(3)将str指向对象o的地址。
值得注意的是,一般String类中字符串值都是直接存值的。但像String str = "abc";这种场合下,其字符串值却是保存了一个指向存在栈中数据的引用!
java内存泄露的两个条件:无用,无法回收。
BigInteger能表示任意精度的整数, BigDecimal能表示任意精度的浮点数, 但都是用精度换速度的做法
容器集合
List的子类里面LinkedList和ArrayList是非同步的,而Vector和Stack是同步的, LinkedList底层采用的是双向链表(前指针+数据+后指针), 因此查询效率低, 增删效率高; ArrayList底层采用的是Array,因此查询效率高, 增删效率低; Vecotr是重量级的List, 一般在并发保证线程安全时采用
如果想跟踪添加给HashSet的元素的顺序,LinkedHashSet实现会有帮助。LinkedHashSet的迭代器按照元素的插入顺序来访问各个元素。
List中的元素有顺序, 可重复, Set中的元素无序, 不可重复(SortedSet是有序的)
Iterator的next方法将跳到下一个元素, 并返回刚刚跳过的元素, 因此使用remove方法的时候,必须先调用next方法
在Map 中插入、删除和定位元素,HashMap 是最好的选择。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。
LinkedHashMap扩展HashMap,以插入顺序将关键字/值对添加进链接哈希映像中。
Hashtable 类似于 HashMap,但是不允许 null 键和 null 值。它也比 HashMap 慢,因为它是同步的。
效率
在方法内部频繁存取变量的时候, 使用局部变量比使用实例变量和静态变量要有更高的效率
异常对性能不利。抛出异常首先要创建一个新的对象。Throwable接口的构造函数调用名为fillInStackTrace()的本地(Native)方法,fillInStackTrace()方法检查堆栈,收集调用跟踪信息。只要有异常被抛出,VM就必须调整调用堆栈,因为在处理过程中创建了一个新的对象。
I/O
任何有能力产生数据流(源)的java io对象就可以看作是一个InputStream对象, 何有能力接收数据源(流)的java io对象我们就可以看作是一个OutputStream对象
io数据流有两种类别,一种就是InputStream/OutputStream,一种是Reader/Writer, 分别用来处理字节流和字符流, 字符也是一种字节, 为什么还要用Reader/Writer呢, 这是因为在处理字符的时候可能涉及到转码,转码主要是Reader与InputStream,以及Writer与OutputStream之间, 转码的过程都是通过InputStreamReader和OutputStreamWriter来做的, 有时候我们可能没有显式的调用这两个类, 但是相关的类在内部是做了这种转换的, 比如FileReader其实就是通过InputStreamReader将数据从FileInputStream取过来的, 比如将操作系统的文字编码转化为Unicode(因为在Java内部都是采用Unicode来保存数据的), 记住这里有一个前提,那就是操作系统当前的编码格式和文档的编码格式是一直的,如果不一致就必须指定当前的InputStream的编码格式.如果不知道字符所采用何种编码方式, 那么就不要使用Reader和Writer而应该改用InputStream和OutStream
对输入/输出流进行缓冲可以提高代码执行效率, 也就是使用BufferedXxxx和BufferedXxxx对原有的流进行封装, 缓冲是一个非常重要而基本的加速I/O访问的技术
final关键字修饰的变量,表明该变量是不变的, 但是这里的不变只是所引用本身不变, 至于引用的对象是否改变则没有约束, 比如final StringBuffer a=new StringBuffer("immutable");a.append(" broken!"); //编译通过, 这里我们可以看出, final只对引用的"值"(也就是它所指向的那个对象的内存地址)有效, 它迫使引用只能指向初始指向的那个对象, 改变它的指向会导致编译器错误, 至于它所指向的对象的变化, final是不负责的.
当你要clone的类里面含有可修改的引用字段的时候,那么你一定要把整个类的蓝图进行复制,如果对你clone得到的对象进行修改的时候还会影响到原来的实例,那么这是不可取的。所以应该这样clone()
JVM
一般来说,我们使用虚拟机的类装载时需要继承抽象类java.lang.ClassLoader,其中必须实现的方法是loadClass(),对于这个方法需要实现如下操作:(1) 确认类的名称;(2) 检查请求要装载的类是否已经被装载;(3) 检查请求加载的类是否是系统类;(4) 尝试从类装载器的存储区获取所请求的类;(5) 在虚拟机中定义所请求的类;(6) 解析所请求的类;(7) 返回所请求的类。
Java中的类的装载过程也就是代理装载的过程。比如:Web浏览器中的JVM需要装载一个小应用程序TestApplet。JVM调用小应用程序装载器ACL(Applet ClassLoader)来完成装载。ACL首先请求它的父装载器, 即系统装载器装载TestApplet是否装载了这个类, 由于TestApplet不在系统装载器的装载路径中, 所以系统装载器没有找到这个类, 也就没有装载成功。接着ACL自己装载TestApplet。ACL通过网络成功地找到了TestApplet.class 文件并将它导入到了JVM中。在装载过程中, JVM发现TestAppet是从超类java.applet.Applet继承的。所以JVM再次调用ACL来装载java.applet.Applet类。ACL又再次按上面的顺序装载Applet类, 结果ACL发现他的父装载器已经装载了这个类, 所以ACL就直接将这个已经装载的类返回给了JVM , 完成了Applet类的装载。接下来,Applet类的超类也一样处理。最后, TestApplet及所有有关的类都装载到了JVM中。
当 JVM 需要使用类时,它根据名称向 ClassLoader 请求这个类,然后 ClassLoader 试图返回一个表示这个类的 Class 对象。通过覆盖对应于这个过程不同阶段的方法,可以创建定制的 ClassLoader。其中有个loadClass(String name, boolean resolve)方法,该方法为ClassLoader的入口点,在jdk1.2以后,loadClass方法将缺省调用findClass方法,详细内容可以参考API文档,我们编写的ClassLoader主要就是为了覆盖以上两个方法。
Java EE
Tomcat Server处理一个http请求的过程
假设来自客户的请求为:
http://localhost:8080/wsota/wsota_index.jsp
1) 请求被发送到本机端口8080,被在那里侦听的Coyote HTTP/1.1 Connector获得
2) Connector把该请求交给它所在的Service的Engine来处理,并等待来自Engine的回应
3) Engine获得请求localhost/wsota/wsota_index.jsp,匹配它所拥有的所有虚拟主机Host
4) Engine匹配到名为localhost的Host(即使匹配不到也把请求交给该Host处理,因为该Host被定义为该Engine的默认主机)
5) localhost Host获得请求/wsota/wsota_index.jsp,匹配它所拥有的所有Context
6) Host匹配到路径为/wsota的Context(如果匹配不到就把该请求交给路径名为""的Context去处理)
7) path="/wsota"的Context获得请求/wsota_index.jsp,在它的mapping table中寻找对应的servlet
8) Context匹配到URL PATTERN为*.jsp的servlet,对应于JspServlet类
9) 构造HttpServletRequest对象和HttpServletResponse对象,作为参数调用JspServlet的doGet或doPost方法
10)Context把执行完了之后的HttpServletResponse对象返回给Host
11)Host把HttpServletResponse对象返回给Engine
12)Engine把HttpServletResponse对象返回给Connector
13)Connector把HttpServletResponse对象返回给客户browser
JDK新特性
注释(Annotation),得先提一提什么是元数据(metadata)。所谓元数据就是数据的数据。也就是说,元数据是描述数据的。就象数据表中的字段一样,每个字段描述了这个字段下的数据的含义。而J2SE5.0中提供的注释就是java源代码的元数据,也就是说注释是描述java源代码的。
多线程 线程
实现线程,有两种方法,一种是继承Thread类,一种是实现Runnable接口。优先采用实现Runnable接口的方法。首先,把需要共享的数据放在一个实现Runnable接口的类里面,然后,把这个类的实例传给多个Thread的构造方法。这样,新创建的多个Thread,都共同拥有一个Runnable实例,共享同一份数据。如果采用继承Thread类的方法,就只好使用static静态成员了。如果共享的数据比较多,就需要大量的static静态成员,令程序数据结构混乱,难以扩展。这种情况应该尽量避免。使用Runnable还有一个好处就是多线程应用对象可以继承别的对象而不是必须继承Thread类, 从而提高多线程对象的灵活性.
多线程的执行过程是当一个程序(一个线程)执行的过程中, 通过调用Thread.start(), 让当前的程序(线程)继续执行的同时, 那个start的Thread会同时执行位于run()方法中的代码, 多段可执行代码交替执行的过程.
由于多个线程拥有度大力的执行对战和程序执行上下文, 因此定义在线程方法中的局部变量不会出现资源共享的问题, 资源共享同步主要发生在成员变量上.
按照线程体在计算机系统内存中的状态不同,可以将线程分为创建、就绪、运行、睡眠、挂起和死亡等类型。这些线程状态类型下线程的特征为:
创建状态:当利用new关键字创建线程对象实例后,它仅仅作为一个对象实例存在,JVM没有为其分配CPU时间片等线程运行资源;
就绪状态:在处于创建状态的线程中调用start方法将线程的状态转换为就绪状态。这时,线程已经得到除CPU时间之外的其它系统资源,只等JVM的线程调度器按照线程的优先级对该线程进行调度,从而使该线程拥有能够获得CPU时间片的机会。
睡眠状态:在线程运行过程中可以调用sleep方法并在方法参数中指定线程的睡眠时间将线程状态转换为睡眠状态。这时,该线程在不释放占用资源的情况下停止运行指定的睡眠时间。时间到达后,线程重新由JVM线程调度器进行调度和管理。
挂起状态:可以通过调用suspend方法将线程的状态转换为挂起状态。这时,线程将释放占用的所有资源,由JVM调度转入临时存储空间,直至应用程序调用resume方法恢复线程运行。
死亡状态:当线程体运行结束或者调用线程对象的stop方法后线程将终止运行,由JVM收回线程占用的资源。
在Java中比较特殊的线程是被称为守护(Daemon)线程的低级别线程。这个线程具有最低的优先级,用于为系统中的其它对象和线程提供服务。将一个用户线程设置为守护线程的方式是在线程对象创建之前调用线程对象的setDaemon方法。典型的守护线程例子是JVM中的系统资源自动回收线程,它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。
jvm固定了线程和内存之间的关系, jvm的内存又分为主内存和工作内存, 线程要操作的数据放在工作内存中, 工作内存的变脸是主内存的拷贝, 线程执行完后工作内存中的变量还需要更新主内存中对应的变量.对于使用了synchornized保护的代码对主内存变量将被锁定从而保证主内存和工作内存之间的变量报纸一致性.但是使用synchonrized的代码在性能上会带来不小的损失
ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是thread local variable(线程局部变量)。也许把它命名为ThreadLocalVar更加合适。线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。它的内部其实可以理解为仅有一个Entry的Map, key是当前的thread, 而value则是要保存的变量.因此它只有两个方法set()将变量跟thread关联, get()方法从map中取出变量, 还有一个就是initValue方法, 是用来生成自己的ThreadLocal的时候用的, 用来给出默认值, 在get和set方法中调用