本文是清华大学许斌老师的公开课:Java语言程序设计进阶 的课堂笔记,快速复习一下(我不是专门搞java的话),时间有限,因此大量直接截图。许斌老师声明:没有配套讲义,建议参考书籍:周志明《深入理解java虚拟机》。(JUC) java.utile.concurrency 部分参考源码和技术博客。
第一章 线程(上)
1.0 导学
1.1 线程的基本概念
1.2 通过Thread类创建线程
1.3 线程的休眠
注:线程休眠的原因就是让其他线程有执行的机会
1.4 Thread类详解
注:线程启动(即调用start方法)并不意味着线程马上运行,线程是否运行取决于线程调度器。
1.5 通过Runnable接口创建线程
注:Runnable接口中我们所实现的run方法就是我们这个线程想要执行的代码
1.6 线程内部的数据共享
同样一个线程类,它可以实例化出很多线程。同样一个线程,它们是可以共享它们的代码和数据,那也就是说当我们实现了Runnable接口的这个类,它所实例出来的对象的话,它去构造出的线程,它们之间是可以共享它们的代码和它们之间的一些数据的。
小结
第二章 线程(中)
2.0 导学
2.1 线程同步的思路
注:那原因就是在于说这两个线程的话,它们是同一优先级,只不过是说这个producer先这个排在前面,所以的话从调度上,往往会调度它这个producer先执行,那它一执行呢就把这个票都生产完了,然后再等待着卖票的程序把它去卖掉,这是一种有意思的这个现象
2.2 线程同步的实现方式—Synchronization
注:把这两行代码变成一个(原子)操作,就是在执行过程中不可能被打散执行
注:用synchronized后面大括号括起来其实是代码,实际上它把它变成一个原子操作,也就是说当我拿到这个对象t的锁的时候,我这里面的这些代码是肯定都会被执行的,不会说我执行某一句以后就被这个打断,然后那个插入别的线程去执行去访问这个对象t,所以这个是synchronized它的很重要的作用。
就像刚才我们那个例子:我们在这个售票线程里面,每售出来票的时候,它就会休眠一毫秒,但休眠一毫秒的时候,它不会释放出它所占有的这个ticket对象的锁的,它一直会持有,所以这是一个独特的一个地方。
2.3 线程的等待与唤醒
注:那现在wait notify notifyAll方法这三个方法都属于object这个类的方法,也就意味着我们java当中所有的类它都有这个三个方法
注:修改之后,相当于票箱大小为1,Tickets.size = 1。Tickets.put() 方法中的 notify() 与 Tickets.sell() 方法中的wait()一一对应,Tickets.put() 方法中的 wait() 与 Tickets.sell() 方法中的notify()一一对应。
2.4 后台进程
2.5 线程的生命周期与死锁
注:线程进入就绪状态就是runnable state,即可运行状态,但是并未开始运行,所以不是运行状态(running state),是否运行取决于线程调度器是否调度它。(Runnable state isn’t running state)。
2.6 线程的调度
注:可以通过这个使用这个yield的方法来去稍微改变一下它的这个执行过程,yield方法主要作用是把自己当前运行的线程暂停下来,把线程让给同优先级的线程执行,当如果这时候不存在同优先级的线程,那还是继续执行当前运行的线程。
注:有交错执行的这个过程,重要的原因:线程调用sleep方法,sleep方法是说我自己进入休眠,线程调度器有可能调度低优先级的这个线程,也就是说对高优先级的这个线程,如果要让出自己的执行权限的话,就要调用sleep方法,然后给其它低优先级线程机会。如果高优先级的线程,仅仅只是调用了yield方法,它并不能给我们低优先级线程以执行的机会,它只给了它同优先级的线程以执行的这个机会。
小结
第三章 线程(下)线程安全与锁优化
3.0 导学
注:它是想描述线程的安全,而最重要的是描述你的程序,甚至你是某个类它的线程安全的特性。
3.1 线程安全与线程兼容与对立
向大家展示一下,一些java API中类,它在碰到这个线程操作的时候,有可能产生线程出错的这个情况。
运行过程当中它不经常出错,但是偶尔也会出错,出现了数组下标越界的错误。最重要的原因:刚才有两个线程Thread remove,Thread print这两个线程都在同时访问一个数据:vector,其中一个线程的操作:删除我们相量中的元素,另外一个线程的操作读取我们相量中的元素。大家看到这其实这两个操作:是有点互逆的 互斥的,那在这个读写向量的过程当中就可能产生错误,那从我们发现了这个运行的结果当中也发现了这一点。
3.2 线程的安全实现-互斥同步
3.3 线程的安全实现-非阻塞同步
那其实大家也可以这么理解,也就是说我们对于这种线程安全,就是对我们这个访问对象的线程安全的这种控制不是放到我们当前count这个类,它的increment方法来去实现的而是已经放到底下的叫 Atomiclnteger 这个类来实现了,所以你就可以直接去调用它的这个方法来去实现加1的这个功能,那整体上我们这个新的类,class Counter就是这个类通过用 Atomiclnteger 来改进这类,它也是线程安全的,整体上也是线程安全的,只不过说当你写这个类的时候,你不需要考虑自己去加上synchronized这样的同步互斥的这种实现方式,而是通过直接使用了Atomiclnteger这样一个本身就是线程安全的这个类,就能够保证你的整个这个代码达到线程安全的目的。
3.4 线程的安全实现-无同步方案
注:Threadlocal是我们java当中的一个类,它是存在于java.lang这个默认这个包当中。
这个 SequenceNumber 的实例,通过用 ThreadLocal 的这个方式也能够保证这三个线程来访问同一数据的时候,没有产生错误。这也是为什么说,可以通过这个 ThreadLocal 就来去达到这个同步,就是说安全的这个目的。也不一定非得加个synchronize,因为如果一旦加了synchronize的话,性能可能会受到影响,如果能通过类似 ThreadLocal 这样这种线程的本地存储的方式来达到我们这个对于数据访问安全的控制化,那就能提高这个程序代码的性能。
3.5 锁优化
操作系统的堆栈与数据结构中堆栈概念参考:
- 什么是堆?什么是栈?他们之间有什么区别和联系? - 知乎
https://www.zhihu.com/question/19729973 - https://jingyan.baidu.com/article/6c67b1d6a09f9a2786bb1e4a.html
注:由于细锁太多,然后不断切换线程的开销反而降低了性能。
小结
- 线程安全指的是我们的访问对象无论被多少个线程进行访问都能够保证我们这对象访问的正确性,那与之相关的这个概念是线程兼容。
- 线程兼容是指我们的对象本身不是线程安全的,但是通过我们外部的同步控制能够达到线程安全的目的.
- 线程对立指的是我们的访问对象,它本身不是线程安全,那我们外部即使加上了同步的控制也不能保证这个对象的这个正确性。
- 我们还学习实现线程安全的几种方式
- 首先是互斥同步
- 其次是非阻塞同步
- 无同步方案
- 最后我们还学习了锁优化,那锁优化的目标就是在我们不得不给我们的代码加锁的情况下如何去提高锁的效率,进而达到提升整个代码的效率的目标。
第四章 网络编程(上)
4.0 导学
4.1 URL对象
注:保留端口号是计算机系统进行网络交互需要的端口号,自己编写程序不要去占用这些保留端口号,具体保留端口号对应网络服务google一下。
4.2 URLConnection对象
4.3 Get请求与Post请求
4.4 Socket通信原理
4.5 Socket通信实现
accept这个方法属于ServerSocket方法,这种方法我们称之它为阻塞方法,就是说它是在那里运行一直等着有客户端来给它发送Socket连接请求,如果没有客户端给我们的服务端发送这个Socket连接请求accept就一直在那里循环执行一直不返回,一直等到有客户端的Socket发连接请求过来。那我们这服务端的 ServerSocket 这个对象的话它就会accept方法就会返回一个值,返回的是一个Socket对象,而这个Socket对象就是和我们客户端的Socket对象进行对应的。
注:那需要提醒大家注意是我们这个程序非常简单,简单到什么程度呢?就是说聊天的时候,都是你说一句 我说一句如果一个想连续说两句话的话可能现有这个机制还处理不过来,必须是一人一句,当然我们同学可以把这个程序再进一步改进使它更加的丰富。
小结
第五章 网络编程(下)
5.0 导学
5.1 Socket 多客户端通信实现
注:先运行server线程,再运行client。
5.2 数据报通信
one-liners.txt这个是构造了一个文件输入流,因为我们做了一个非常简单的模拟,也就是说把一些股票信息就写到这个文件里面了,写到这个文件里面了以后就是每次有客户端发过来请求,说咨询一下股票的价格的时候,我们就从这个文件里读出某一股票的价格在返还给我们的客户端。
那刚才这个程序当中客户端服务端各一个程序,客户端和服务端之间的通讯是通过数据报这个Socket来进行通讯的然后整个过程就非常类似于,我们人类进行平信这个通讯方式,也就是说客户端通过构造一个DatagramPacket这个对象向它写一封信,然后通过DatagramSocket的send方法把它发出去了,服务端收到了这封来信以后,通过这个来信知道了客户端的地址和端口号,然后服务端它自己也写一封信,说白了写信就是构造一个DatagramPacket对象,写好了以后,通过DatagramSocket的这个对象的send方法,把这个信再发出去又发还给客户端,所以这个数据报包总结起来就非常类似于我们人类写平信的这个过程。
5.3 使用数据报进行广播通信
5.4 网络聊天程序
那整个这个布局其实大家采用一个BorderLayout就可以达到你的目标,那就是在BorderLayout的center中间那区域先放一个滚动面板,然后接着再放一个TextArea,然后在它的南部区域,我们先放一个Panel,紧接着再放一个TextView文本输入区域,然后接着再放一个Button而且TextView和Button的话都是按照FlowLayout这种放置规则。
大家需要这个写的事件响应是什么呢?其实首先最重要的是说我们能够接收这个在文本区域,就是最下面这个文本区域这个输入的文本,我们可以给TextView这个组件来注册一个监听器,当我们这个一回车就在TextView里面一输入字符一回车的时候,它产生的是一个ActionEvent,所以我们可以给TextView注册一个EventListener。那TextView旁边的话,是一个按钮发送,其实发送的话它所对应的这个事件处理也是ActionEvent,所以在这个例子当中我们只需要写一个事件处理类然后都分别这个授权来去处理TextView和我们按钮的这么这个事件处理就可以完成获得我们这个文本的这么一个过程以及把它发送的一个过程。那在这里面怎么去获得内容呢?TextView里面有个一个getText的方法,那我们只要在我们的ActionPerform方法里面去通过TextView的getText来获得它的内容然后来决定一个是往外发送同时把它显示到当前我们的这个界面上面。
小结
第六章 java虚拟机
6.0 导学
6.1 Java虚拟机概念
6.2 Java虚拟机内存划分
本地方法(native method)它不一定是拿Java语言来编写的方法,Java虚拟机是会运行在不同的操作系统和硬件上面,那在本身Java虚拟机的内部实现的时候,也会有一部分代码是运行的是本地代码非Java这个代码,包括你们自己写程序的时候,也可以比如用C或C++,写一段程序,最后把它嵌入到Java代码当中这也是可以的。这个本地方法栈主要是用来执行本地方法,它同样有可能会抛出异常,那所谓的抛出异常的种类也和虚拟机栈一样,而我们的虚拟机栈它的对应的功能主要是用来执行我们的Java方法。
注:官网G1垃圾回收器介绍Getting Started with the G1 Garbage Collector
6.3 Java虚拟机类加载机制
比较旧的一些这个编程语言的经验,当我们编译完了以后可能还要做链接然后再执行,Java它有一个这个大的特点就是在程序运行过程当中来进行这个类的加载和连接,这样的话就保证了,它这个一个程序运行的这个流畅和灵活性。
6.4 判断对象是否存活算法及对象引用
通过sf.get方法可以获取到这个对象,当然当这个对象被标志为需要回收的对象时,它就会返回的是空,所以说软引用主要用户来实现类似缓存的功能,在内存足够的情况下,我们可以直接通过软引用来取值而不需要从繁忙的真实来源去查询数据提升速度,那当内存不足的时候就会自动删除这部分的缓存数据,然后从真正的数据来源当中去查询这些数据。
6.5 分代垃圾回收
注
- 将引用对象设置为空,这种方式来去释放内存的话,应该没什么大问题,但如果我们用system gt方法去释放内存的话会大大的影响我们的系统性能。
- 不可达含义:不用了,没有引用链指向。看英文:unavailable 就明白了,没有引用指向它们而且毕竟不属于空闲区,当然就不可使用。
6.6 典型的垃圾收集算法
6.7典型的垃圾收集器
注:jvm垃圾回收详解参考官网G1垃圾回收器介绍Getting Started with the G1 Garbage Collector
今天介绍这几种垃圾收集器一般说来它会影响到程序执行性能,尤其是当想编一些对效率要求非常高的Java程序的时候,比如说服务器端的Java程序的时候,有时候你会比较顾及Java虚拟机的垃圾回收效率是不是足够那个帮助你的程序的运行,那据我所知国内外都有一些大公司在他们的服务器端性质当中重新改写了一些关于Java虚拟机的里面的垃圾回收的机制。
就举一个例子来说:双十一淘宝它的这个系统肯定就会承受着极大的这个用户购买商品的点击的压力,淘宝实际上它的很多后台系统拿是拿Java写的,所以为了提高这个Java在服务器端的这种工作效率,那我听说他们也是对于一些垃圾回收的这些机制进行了改进。在一些性能要求特别高的情况下的话,可能我们在服务器端会对这些Java虚拟机以及相应它的一些局部做一些改进。
第七章 深入集合collection
7.0 导学
7.1 集合框架与ArrayList
7.2 LinkedList
7.3 HashMap与HashTable
可以看到这个HashMap底层数组的长度它总是2的N次方,这就是为了保证数组的使用率最高,尽可能的减少这个碰撞现象的产生,当HashMap中的元素越来越多的时候,这个哈希冲撞的冲突的可能性也就越来越高,因为数组的长度是固定的。那为了提高这查询的效率就要对这个HashMap的数组进行扩容容量变为原来的两倍,这时候,原数组当中的数据必须重新计算它在新数组当中的位置,并且放进去,那这个过程呢就非常耗时。当HashMap中的元素个数超过数组大小(取个名字叫lot fat),就会进行数组的扩容,这个(lot fat)的默认值为0.75。那这个是一个非常消耗性能的操作所以如果我们已经预知这个HashMap当中元素的个数,那么就能够有效的提高HashMap的性能,所以这也是一个HashMap它的一个独特的地方。
注:关于hashtable与hashmap 建议参考源码解析 Java7/8 中的 HashMap 和 ConcurrentHashMap 全解析
7.4 TreeMap与LinkedHashMap
所以说它和之前的那个HashMap的区别就是:HashMap里面的数据结构第一级结构是一个数组,第二级结构是一个单向链表,而我们的LinkedHashMap第二级结构是一个双向链表,所以在往里添加的时候就可以根据你要添加元素的位置来决定是从正向的去检索往里添加,还是反向从队尾开始去检索进行添加。
7.5 HashSet
小结
常用的一些集合类
首先介绍了List Map Set 这几个接口,那在这几个接口之下又有好多的类是实现了这些接口,比如说我们的HashSet、ArrayList linkedList、HashMap、TreeMap等等这些。
集合类的内部的实现过程
为什么要介绍这些呢?是因为要告诉大家说,它的每一个数据结构这些类它到底是怎么实现的,你明白了它的实现原理了以后,你就知道说它的效率和性能到底是怎样的,为什么高为什么低,哪些类到底和线程安全有没有关系,有没有已经实现的线程安全特性,那没有实现的话,你就得自己通过加 synchronize 办法来去实现,所以对于通过了解我们集合类的内部,你就可以很好的去运用这些集合类
各个集合类的适用范围
由于这些集合类它本身所具有的特点并不一样,所以当我们在编程序过程中,考虑选择哪个类作为我们的数据结构的时候,你就能够很好的去选择和决定。
第八章 反射与代理机制
8.0 导学
8.1 Java反射机制
8.2 Java静态代理
8.3 Java动态代理
这个例子实际上是告诉大家说你可以给一个真实的对象,你给它生成一个动态代理,那生成这个动态代理的话。既然是个代理,你可以在这个真实对象的方法执行之前先做一些预处理,执行之后你还可以做一些后处理,所以你就可以增加一些你想干的这个事情,而通过这个动态代理的话,它的好处就是能够让你更加方便的去实现这些代理的过程。
8.4 Java 反射扩展-jvm加载类原理
JAVA虚拟机中类加载的原理,什么叫类加载,想想当编辑完JAVA的源程序以后,一编译会得到是一大堆点class文件,那点class文件就是这些类文件,而这些类文件平常是存在硬盘上,也就存在电脑的文件系统当中那当这些类文件需要执行的时候就需要JAVA虚拟机把它从硬盘上给挪到我们内存当中,那整个挪到JAVA虚拟机内存当中过程实际上就是一个类加载的过程。
- 第二步要做连接,连接里面也会包括第一部分是验证、注意验证主要验证说,你装载进来的这个点class文件它是不是符合我们JAVA虚拟机对于自解码文件的一个规范,做格式的这种校验,甚至是不是有恶意是不是有危害,这些都是我们验证的过程,那第二个小步骤是准备要把我们这些类它的一些相应的静态的成员做一下内存的分配,那第三小步骤的话是要解析,解析是什么就是把我们很多的这些符号性的引用把它转化成一种直接的引用。
- 第三个步骤是做类的初始化,比如说我们将类的静态变量给它做赋于正确的初始值,注意这个初始值是指的是程序员在给它定义的这个初始值而不是说默认初始值,默认初始值这个确定是在第二个步骤连接步骤里边,这个准备小步骤里边已经实现了。
小结
我们今天主要讲了三个方面的内容
JAVA的反射机制
JAVA反射机制为程序员提供了一种直接去获取类以及对象它的方法以及它的成员变量的一种方式,通过反射机制可以去通过一个字符串的名字去创建一个类的对象,并且很灵活的去调动它的所有的方法。
JAVA的代理机制
介绍了静态代理和动态代理,之所以有JAVA代理机制是因为说有些情况下并不想或者不能够去直接访问目标对象而需要中间有一个中介的渠道,那这中介渠道就可以帮助很好的去控制和访问目标对象。并且在访问目标的前和后都可以增加一些预处理或者后处理。介绍了静态代理的方式和动态代理的方式,动态代理方式会给大家很大的一个方便和灵活性
类的加载机制
把我们所有编译好的点Class文件把它加载到我们的JAVA虚拟机当中来进行运行,那这个类加载过程当中,它实际上对于JAVA是一个动态的过程而且是一个可以从多个源头进行加载的这个过程,理解的加载类的加载过程,对于大家今后编写更加高效有效的JAVA程序会带来很大的帮助。
参考:
- 周志明《深入理解java虚拟机》
- Java7/8 中的 HashMap 和 ConcurrentHashMap 全解析
- Java Concurrency in Practice