`
lifeforce
  • 浏览: 9516 次
  • 性别: Icon_minigender_1
  • 来自: 广州
最近访客 更多访客>>
社区版块
存档分类
最新评论

[转]Java 学习杂谈(四)

阅读更多
1.关于序列化和反序列化
应该大家都大概知道Java中序列化和反序列化的意思,序列化就是把一个Java对象转换成二进制进行磁盘上传输或者网络流的传输,反序列化的意思就是把这个接受到的二进制流重新组装成原来的对象逆过程。它们在Java中分别是通过ObjectInputStream和 ObjectInputStream这两个类来实现的(以下分别用ois和oos来简称)。

oos的writeObject()方法用来执行序列化的过程,ois的readObject()用来执行反序列化的过程,在传输二进制流之前,需要讲这两个高层流对象连接到同一个Channel上,这个Channel可以是磁盘文件,也可以是socket底层流。所以无论用哪种方式,底层流对象都是以构造函数参数的形式传递进oos和ois这两个高层流,连接完毕了才可以进行二进制数据传输的。例子:
可以是文件流通道
file = new File(“C:/data.dat”);
oos = new ObjectOutputStream(new FileOutputStream(file));
ois = new ObjectInputStream(new FileInputStream(file));

或者网络流通道
oos = new ObjectOutputStream(socket.getOutputStream());
ois = new ObjectInputStream(socket.getInputStream());

不知道大家是否注意到oos总是在ois之前定义,这里不希望大家误解这个顺序是固定的么?回答是否定的,那么有顺序要求么?回答是肯定的。原则是什么呢?
原则是互相对接的输入/输出流之间必须是output流先初始化然后再input流初始化,否则就会抛异常。大家肯定会问为什么?只要稍微看一看这两个类的源代码文件就大概知道了,output流的任务很简单,只要把对象转换成二进制往通道中写就可以了,但input流需要做很多准备工作来接受并最终重组这个Object,所以ObjectInputStream的构造函数中就需要用到output初始化发送过来的header信息,这个方法叫做 readStreamHeader(),它将会去读两个Short值用于决定用多大的缓存来存放通道发送过来的二进制流,这个缓存的size因jre的版本不同是不一样的。所以output如果不先初始化,input的构造函数首先就无法正确运行。

对于上面两个例子,第一个顺序是严格的,第二个因为oos和ois连接的已经不是对方了,而是socket另外一端的流,需要严格按照另外一方对接的output流先于对接的input流打开才能顺利运行。

这个writeObject和readObject本身就是线程安全的,传输过程中是不允许被并发访问的。所以对象能一个一个接连不断的传过来,有很多人在运行的时候会碰到EOFException, 然后百思不得其解,去各种论坛问解决方案。其实笔者这里想说,这个异常不是必须声明的,也就是说它虽然是异常,但其实是正常运行结束的标志。EOF表示读到了文件尾,发送结束自然连接也就断开了。如果这影响到了你程序的正确性的话,请各位静下心来看看自己程序的业务逻辑,而不要把注意力狭隘的聚集在发送和接受的方法上。因为笔者也被这样的bug困扰了1整天,被很多论坛的帖子误解了很多次最后得出的教训。如果在while循环中去readObject,本质上是没有问题的,有对象数据来就会读,没有就自动阻塞。那么抛出EOFException一定是因为连接断了还在继续read,什么原因导致连接断了呢?一定是业务逻辑哪里存在错误,比如NullPoint、 ClassCaseException、ArrayOutofBound,即使程序较大也没关系,最多只要单步调适一次就能很快发现bug并且解决它。

难怪一位程序大师说过:解决问题90%靠经验,5%靠技术,剩下5%靠运气!真是金玉良言,笔者大概查阅过不下30篇讨论在while循环中使用 readObject抛出EOFExceptionde 的帖子,大家都盲目的去关注解释这个名词、反序列化的行为或反对这样写而没有一个人认为EOF是正确的行为,它其实很老实的在做它的事情。为什么大家都忽略了真正出错误的地方呢?两个字,经验!

2.关于Java的多线程编程
关于Java的线程,初学或者接触不深的大概也能知道一些基本概念,同时又会很迷惑线程到底是怎么回事?如果有人认为自己已经懂了不妨来回答下面的问题:
a. A对象实现Runnable接口,A.start()运行后所谓的线程对象是谁?是A么?
b. 线程的wait()、notify()方法到底是做什么时候用的,什么时候用?
c. 为什么线程的suspend方法会被标注过时,不推荐再使用,线程还能挂起么?
d. 为了同步我们会对线程方法声明Synchronized来加锁在对象上,那么如果父类的f()方法加了Synchronized,子类重写f()方法必须也加Synchronized么?如果子类的f()方法重写时声明Synchronized并调用super.f(),那么子类对象上到底有几把锁呢?会因为竞争产生死锁么?

呵呵,各位能回答上来几道呢?如果这些都能答上来,说明对线程的概念还是满清晰的,虽说还远远不能算精通。笔者这里一一做回答,碍于篇幅的原因,笔者尽量说得简介一点,如果大家有疑惑的欢迎一起讨论。

首先第一点,线程跟对象完全是两回事,虽然我们也常说线程对象。但当你用run()和start()来启动一个线程之后,线程其实跟这个继承了 Thread或实现了Runnable的对象已经没有关系了,对象只能算内存中可用资源而对象的方法只能算内存正文区可以执行的代码段而已。既然是资源和代码段,另外一个线程当然也可以去访问,main函数执行就至少会启动两个线程,一个我们称之为主线程,还一个是垃圾收集器的线程,主线程结束就意味着程序结束,可垃圾收集器线程很可能正在工作。

第二点,wait()和sleep()类似,都是让线程处于阻塞状态暂停一段时间,不同之处在于wait会释放当前线程占有的所有的锁,而 sleep不会。我们知道获得锁的唯一方法是进入了Synchronized保护代码段,所以大家会发现只有Synchronized方法中才会出现 wait,直接写会给警告没有获得当前对象的锁。所以notify跟wait配合使用,notify会重新把锁还给阻塞的线程重而使其继续执行,当有多个对象wait了,notify不能确定唤醒哪一个,必经锁只有一把,所以一般用notifyAll()来让它们自己根据优先级等竞争那唯一的一把锁,竞争到的线程执行,其他线程只要继续wait。

从前Java允许在一个线程之外把线程挂起,即调用suspend方法,这样的操作是极不安全的。根据面向对象的思想每个对象必须对自己的行为负责,而对自己的权力进行封装。如果任何外步对象都能使线程被挂起而阻塞的话,程序往往会出现混乱导致崩溃,所以这样的方法自然是被毙掉了啦。

最后一个问题比较有意思,首先回答的是子类重写f()方法可以加Synchronized也可以不加,如果加了而且还内部调用了super.f ()的话理论上是应该对同一对象加两把锁的,因为每次调用Synchronized方法都要加一把,调用子类的f首先就加了一把,进入方法内部调用父类的 f又要加一把,加两把不是互斥的么?那么调父类f加锁不就必须永远等待已经加的锁释放而造成死锁么?实际上是不会的,这个机制叫重进入,当父类的f方法试图在本对象上再加一把锁的时候,因为当前线程拥有这个对象的锁,也可以理解为开启它的钥匙,所以同一个线程在同一对象上还没释放之前加第二次锁是不会出问题的,这个锁其实根本就没有加,它有了钥匙,不管加几把还是可以进入锁保护的代码段,畅通无阻,所以叫重进入,我们可以简单认为第二把锁没有加上去。

总而言之,Synchronized的本质是不让其他线程在同一对象上再加一把锁。
分享到:
评论

相关推荐

    Java学习杂谈1-12

    Java学习杂谈1-12 网上搜到的,整理集合了一下

    杂谈_软件江湖_Java学习之路

    杂谈_软件江湖_Java学习之路, 找到正确的学习之路!!!!!!

    java杂谈

    Java杂谈 牛人学习java总结 学习经验感受

    java学习杂谈

    值得java初学者和java爱好者仔细研读和珍藏的著作!

    2009年Java认证考试重点指导

    [学习资料] 09年Java认证考试:我的Java学习道路 [学习资料] 09年Java认证考试:学好java开发的关键七步 [学习资料] 09年Java认证考试:JAVA求素数算法实现 [学习资料] 09年Java认证考试:java类的构造方法 [学习...

    主流编程语言的选择和学习杂谈

    主要介绍主流编程语言及其特点、发展趋势,给新手提供学习参考

    JAVA数据结构和算法

    深入浅出 例子实用 代码丰富 是java程序人员学习和深入数据结构和算法的实用读物 由于资源过大,分两部分上传 本资源为第二部分

    积分管理系统java源码-mic-demo:《Java猫说》公众号系列课程:微服务入门视频讲解Demo

    现架构设计(码农)兼创业技术顾问,不羁平庸,热爱开源,杂谈程序人生与不定期干货。 视频教程地址 第一章 从传统单体架构走向微服务 Hello,大家好,我是猫叔MySelf,本课程将带领大家入门微服务。 各位年轻又帅气...

    kuangyu0801.github.io

    欢迎来到GitHub Pages ,,,, ,,,, Mac键符号 命令键⌘ Shift键⇧ 选项(或Alt)键⌥ ... Java学习 Java IO看黑马 Java并发看黑马 Java Casting...。有关重载和覆盖的更多详细信息 [Java

    asp.net知识库

    体验.net2.0的优雅(四):Provider、策略、控制反转和依赖注入 泛型最佳实践 asp.net 2.0下嵌套masterpage页的可视化编辑 C# 2.0与泛型 动态调用对象的属性和方法——性能和灵活性兼备的方法 泛型技巧系列:用泛型...

    localhost-8080.github.io:基于 VuePress 和 GitHub Pages 的技术博客

    localhost-8080 本地煮鸡:8080技术博客架构Vuepress + GitHub Pages / Netlify内容Java 程序设计Kotlin 程序设计C 程序设计Web 技术Spring 技术大数据技术前端技术深度学习软件工程杂谈持续施工中在线展示页面 或,

    Netty-4.1.97.Final源码

    Netty-4.1.97.Final源码 适用于想深入学习Netty源码的java程序员 可用于对Netty源码进行阅读以及Netty本地化修改的场景

Global site tag (gtag.js) - Google Analytics