Java编程思想日志

Thinking In Java的作者是大牛!做事要站在巨人的肩膀上有助于提高效率和开阔眼界!建议学习java的小伙伴儿有时间可以抽空了解一下,以下内容为读书笔记,比较杂乱,仅供参考,推荐阅读原著:

(如果你能完全读懂以下内容,我是说理解我所说的不那么规整的言语,那么我更加建议你看一看原著,锦上添花!)

java编程思想:

0.
每一种语言的成功背后,都有一个支撑其发展的领域。针对不同的情况,各种编程语言在诸多特性中权衡。而这些特性往往无法兼得,比如:解决问题的复杂度的程度和高效率。
java在解决问题的复杂度上表现优异。他们关心的是,减少开发健壮代码所需的时间以及困难。
一切编程思想源于对生活的洞察。
面向对象程序设计的挑战之一就是在问题空间的元素和解空间的对象之间创建一对一的映射。
高内聚的组件或构件有利于程序设计。对象是一种高内聚的体现。
脆弱的地方需要隐藏,这是封装的一种思想。访问控制:public private protected 默认的 包的
对外的屏蔽体现在两方面:防止用户进入 防止内部改变影响外面
组合:创建成员对象。组合是动态发生的:聚合。
在非必要继承时,组合是一个不错的选择。
基类,类比一种核心的概念,在其基础上产生导出类:继承(添加新的)    重写(修改已有的)  
is a      is like a
oop默认程序执行采用动态绑定(后期绑定)。而一般的,如面向过程的语言默认采用静态绑定(前期绑定)。
例如:c++中若采用动态绑定,需要使用关键字virtual声明。
java中有一小段特定的代码,用于后期绑定:通过参数和返回值等计算所要绑定的对象。
基于向上转型,java的多态巧妙地实现了动态的绑定。例:几何图形dosomething()
与c++不同,java采用单根继承。虽然牺牲了巧妙地灵活性,但整体上,更利于在堆上创建对象和垃圾回收,参数传递也变得简单。
对于特定问题需要多少个对象及其生命周期的问题,只有在运行时才能知道。
面向对象的编程语言对于此类问题过于草率:创建另一种对象类型,其用于存储对其他对象类型的引用。
即:容器(集合)。在其他编程语言中有数组类型实现相应的功能。
单根继承的特性意味着所有类型都可以向上转型进而追溯到object。
向上转型是一种更泛化的过程,相对安全。
参数类型化:就是一个编译器可以自动定制作用于特定类型的类。
例如:参数化类型,编译器可以定制一个只接纳和去除Shape对象的容器。
java se5的重大变化之一就是增加了参数化类型,在java中它称为泛型。
例如:Array<Shape> shapes=new ArrayList<Shape>();
在C++中,认为效率控制是最重要的议题。在编写程序的过程中可以运用堆栈。即:程序员可以控制对象的存储空间和生命周期。
但这牺牲了灵活性,即:必须知道需要的对象个数和生命周期。
而java中,采用被称为内存池中动态地创建对象。关键字:new。
对于可以在堆栈上创建对象的语言,编译器可以确定对象存货的时间,并可以自动销毁它。
然而,如果在堆上创建对象,编译器就会对它的生命周期一无所知。这需要C++程序员知道何时创建和销毁对象,可能导致内存泄漏。
而java的垃圾回收器被设计用来处理内存泄漏问题,有效避免了这一点。
异常:是一种对象,它从出错地点被“抛出”,并被专门设计用来处理特定类型错误的相应的异常处理器“捕获”。
异常处理就像是和程序正常执行路径并行的、在错误发生时执行的另一条路径。
并发编程:同一时刻执行多个任务的思想。初期在机器底层实现中断。java采用多线程。用锁机制协调资源共享。
web:客户端服务器。
系统具有一个中央信息存储池,用来存储某种数据,它通常存储在数据库中,可以根据某些需要将它分发给某些人员或者机器集群。
中间件:介于操作系统和应用程序之间,负责通讯和整合逻辑,类比代理承办交互中的繁琐业务,简化工作调度。
客户端编程。CGI。插件。脚本语言。applet。Internet与Intranet。HTML。JSP。Servelet。
1.只有在你知道如何处理的情况下才捕获异常。避免吞食。
2.所有模型都是错误的,但有些是能用的。
3.从小程序来看,异常说明可以增加效率,但应用于大型项目,代码质量就微不足道,且效率低下。
4.Java的反射机制,java遵循一切都是对象的原则。类是对象的模板,其也可以是一个对象,即类对象。
类对象保存在.class文件,jvm使用类加载器这一子系统获取.class文件里的类的相关信息。当我们扩展一个系统,
又无法得到其内部的源码,就可以在外面单独写一个类,放到系统向外提供的加载器里,通过反射机制进行功能扩展。
5.string对象具有只读特性。重载的意义在于一个操作符在应用于特定的类时,被赋予了特殊的意义。用于string的+与=是java中仅有的操作符。
6.jdk 自带的javap可以反编译。
7.Java泛型的一个局限性:基本类型无法作为类型参数。但是java有自动打包和自动拆包的功能。
Java泛型使用擦除实现,即List<String>和List<Integer>在运行时是相同的类型,这两种形式都被擦除成它们的“原生”类型,即“List”。
而C++知道模板的类型。Java使用边界,即extends。
原因:Java语言泛型并非一开始就有,而是后续的一种折中,这种折中体现在擦除,这不是一种特性。
擦除减少了泛型的泛型化。如果Java像C++一样能够使类型参数保持为第一类实体,则能在类型参数上执行基于类型的语言操作和反射操作。
泛型类型只有在静态类型检查期间才能出现,在此之后,List<T> -> List.普通类型变量 -> object.
擦除的核心动机是:泛化的客户端可以用非泛化的类库来使用。“迁移兼容性”。
Java一开始无泛型,无泛化代码,相关类库。后来,引入泛型,有泛化的代码,相关类库。怎么办?“擦除”。
这是不是唯一且最佳的方案,需要时间来证明。
当前类继承某个泛型类时,在没有任何泛型参数时,编译器不会发出任何警告。只有在运行相关函数时,警告才会出现。
java中使用泛型绝非强制。
Java SE5之后@suppresswarnings("unchecked")
class A<>  <-> class A 字节码相同。
边界就是发生动作的地方。
通配符。
与数组不同,泛型没有内建的协变类型。逆变参数,协变参数。
List<? extends Fruit>具有任何从Fruit继承的类型的列表。实际上是特定的。即:某种引用没有指定的具体类型。
List<? extends Fruit> flist = new ArrayList<Apple>)();
compile Error:can't add any type of object.


超类型通配符<? super MyClass>或<? super T>
可以声明通配符是由某个特定类的任何基类来界定的。
协变:就是用一个窄类型替换宽类型。注意:泛型不支持。
逆变:则是用宽类型覆盖窄类型。
<? extends E>泛型参数支持E及E的子类。 “协变”
<? super E>          支持E及E的父类。 “逆变”
宽转换:低精度 -> 高精度
窄转换:高精度 -> 低精度
byte -> int宽
int -> char窄
byte -> char 宽窄
无界通配符 <?>
List 与 List<object>持有任何Object类型的原生List。
list<?>具有某种特定类型的非原生List。是同构集合特定的。
捕获转换:在方法内部,需要使用确切的类型。
8.泛型方法使得该方法能够独立于类而产生变化。泛型方法能办的事就不用泛型类。
9.static的泛型方法无法访问泛型类的类型参数,所以static方法需要使用泛型能力,就必须使其成为泛型方法。
10.泛型方法具有类型参数推断。只对赋值操作有用。
11.java对缺乏潜在类型机制的补偿:反射将所有类型检查都转移到了运行时,但在某些情况下这并不是我们希望的。
那么如何实现编译期检查和潜在类型机制呢?
使用反射和java可变参数。可变参数可以理解为参数数组。
12.元编程:生成程序的程序。
13.如何拥有两个不相干的接口呢?可以创建一个类,其实现一个接口,并继承另一个实现了另一个接口的类,这就是类适配器模式。
还可以创建一个类实现一个接口,然后将这个类的一个对象作为适配器类的成员变量,同时,这个适配器实现另一个接口。这种通过组合来实现适配器功能的方法称为对象适配器模式。
当一个接口有多个方法声明,而我们只需要其中的几个方法时,可以通过一个中间件:抽象类。这个抽象类实现该接口,相关方法都默认置空,我们称这个抽象类为适配器,我们可以通过继承这个抽象类进而实现需要的方法。
14.问题越早发现越好。数组在编译期间检查比ArrayList在运行期检查更加优雅。同时,ArrayList的灵活性也使得其效率与数组相比更低。数组可以持有基本类型。
15.数组与泛型不能很好的结合。你不能实例化具有参数化类型的数组。擦除会移除参数类型信息,而数组必须知道它们所持有的确切类型,以强制保证类型安全。但是可以参数化数组本身的类型。但是编译器允许你创建对这种数组的引用。
16.将会发生变化的代码封装在单独的子类中(策略对象),进而可以将策略对象传递给总是相同的代码。这些代码将会使用策略来完成其算法。通过这种方式,你能够用不同的对象来表示不同的比较方式,然后将它们传递给相同的排序代码。
这种思想是:将保持不变的事物与会发生改变的事物相分离。例如:不变的是通用的排序算法,变化的是各种对象的相互比较方式。你可以将策略对象传递给总是相同的代码,这些代码将使用不同的策略来完成其算法。
17.享元:为了节省内存,有效的节省空间,可以避免生成重复的对象。如果可以的话,可以共享对象。实现这一想法,同样也需要付出代价:我们需要知道当前对象是否已经存在,可以共享给其他使用者。实现这一机制的过程中,享元在大幅度降低内存中对象数量的同时,也使得系统更加复杂。
为了使对象可以共享,需要将一些状态外部化,进而导致逻辑复杂化。享元模式导致读取外部状态时间变长。JAVA中字符串使用享元模式。
享元工厂:负责生成,维护和管理享元对象,类比单利模式。
抽象享元:抽象逻辑接口。
具体享元类:具体逻辑实现。
客户端:通过工厂取得享元对象。
可以使用map将对象与状态关联起来。
单纯享元与复合享元的区别。
18.copyOnWriteArrayList是List的一个特殊实现,专门用于并发编程。
19.HashMap的默认负载是0.75,这是一个很好的平衡,在查找性能和空间利用率上。当负载超过0.75时,会进行再散列。事先计算空间资源可以避免再散列。
20.快速报错机制的工作原理。当程序的不同部分修改同一个容器时,可能导致容器状态不一致。比如:容器生成对应迭代器,再在容器中添加对象。
21.如果一个对象是可获得的,则在程序中该对象的引用是可以被找到的,那么垃圾回收器就不能对其进行回收。Reference对象提供了一种机制,即经过其包装的对象可以被垃圾回收器回收,但只有在内存满了这种万不得已的情况下才这么做。
这个java.lang.ref类库包含了一组类,使得垃圾回收更加灵活。当存在可能耗尽内存的大的对象时,其显得十分有用。持有引用,以Reference对象作为你和普通对象引用之间的媒介或代理。
22.由强到弱,SoftReference、WeakReference和PhantomReference对应不同级别的“可获取性”。
23.容器类中的WeakHashMap被用来保存WeakReference,使得规范映射更易于使用。在这种映射中,每个值只保存一个实例以节省内存空间。
24.将string类型对象对应到hashcode的过程中,hashcode值可能会重复,进而出现数据抖动现象,为了避免这一现象,我们可以迭代上述过程,使用多个hashcode算法生成多个hashcode。
在bitset的应用过程中,相应的可以在同一个BitSet进行多次着色,在判断存在性时,只有所有着色位为true时,才判定成功。


String url = "http://baidu.com/a";  
int hashcode1 = url.hashCode() & 0x7FFFFFFF;  
bitSet.set(hashcode1);  
  
int hashcode2 = (url + "-seed-").hashCode() & 0x7FFFFFFF;  
bitSet.set(hashcode2);  
System.out.println(bitSet.get(hashcode1) && bitSet.get(hashcode2));  
//也可以在两个不同的bitSet上进行2次“着色”,这样冲突性更小。但会消耗双倍的内存。
如果BitSet中存储了较多的数字,那么互相覆盖着色,最终数据冲突的可能性会逐渐增加。这里有一个权衡:hashcode算法个数*String字符串个数<Integer.MAX_VALUE*0.8
25.Vector是同步的,线程安全的,ArrayList是异步的。
26.  2^6=64  我们传进129作为参数的时候,我们会申请一个long[(129-1)>>6+1]也就是long[3]的数组。以6bits作为一个基本单元,long型数据,128/64+1=3


private static int wordIndex(int bitIndex) {
    return bitIndex >> ADDRESS_BITS_PER_WORD;
}
private final static int ADDRESS_BITS_PER_WORD = 6;


words[wordIndex] |= (1L << bitIndex); // Restores invariants
需要注意的是java中的移位操作会模除位数,也就是说,long类型的移位会模除64。例如对long类型的值左移65位,实际是左移了65%64=1位。
int transderBits = bitIndex % 64;
words[wordsIndex] |= (1L << transferBits);
如果想高效率的存储大量“开/关”信息,BitSet是很好的选择。其空间高效。时间效率比本地数组慢一些。当然,你也可以撰写自己的类。




25.java 回调策略:A类中实现了一个特定的接口interfaceA,然后A通过组合B类拥有其引用去调用B的方法b,在B类中可以通过interfaceA作为形参去接收A类子对象去调用interfaceA中实现的方法。
有同步回调和异步回调之分。
在程序设计中,回调函数是指通过函数参数传递到其他代码的某一块可执行代码的引用。
其目的是允许底层代码调用在高层定义的子程序。
再如:匿名内部类中的全部代码可以看成函数参数传递到其他代码的,某一块可执行代码的引用。
程序书面语言解释:A类中调用B类中的某个方法C,然后B类中再反过来调用A类中的方法D,其中,D就是回调方法。B类就是底层的代码,A类是高层的代码。
为了表示D方法的通用性,我们采用接口的形式让D方法称为一个接口方法,如果B类要调用A类中的方法a,那势必A类要实现这个接口,这样,根据实现的不同,就会有多态性,使方法具备灵活性。
A类要调用B类中的方法b,那么A类势必包含B的引用,要不然就无法调用,这一步称为注册回调接口。那么,如何实现B类中反过来调用a方法呢?
直接通过上面的方法b,由于B类中的方法b是接受一个接口类型的参数,那么只需要在C方法中,用这个接口类型的参数去调用a方法,就实现了B类中反过来调用A类中的方法a,这一步称为调用回调接口。
这里也就是说,a方法是在接口中声明的。
A类实现接口CallBack callback
A类中包含了一个B的引用
B中有一个参数为CallBack的方法f(CallBack callback)
在A类中调用B的方法f(CallBack callback)——注册回调接口
B就可以在f(CallBack callback)方法中调用A的方法——调用回调接口
回调就是把代码作为对象传进去,那么怎么把代码传进去呢,当然需要做成一个类,类中的某个方法就是这段代码。


什么时候使用回调函数?


假设有A、B两个类。


(1)A类有多种形态,要在B类中实现回调函数。如假设A类是网络请求开源类ASIHttpRequest,它可能请求成功,也可能请求失败。这个时候,B类就要针对以上两个情况,作不同的处理。


(2)A类的形态由B类决定时,要在B类中实现回调函数。如UITableView类就会提供很多回调函数(iOS专业术语称“委托”方法)


(3)A类需要向B类传递数据时,可以在B类中实现回调函数(A类一般是数据层比较耗时的操作类)。如举的那个发工资的例子。在实际编程中,这样的机制有个好处就是可以提升用户的操作体验。比如用户从X页面跳转到Y页面,需要向网络请求数据,而且比较耗时,那我们怎么办?有三种方案:第一种就是在X页面展示一个旋转指示器,当收到网络传回的数据时,在展现Y页面。第二种就是使用回调函数。用户从X页面直接跳转到Y页面,Y页面需要到数据让数据层去执行,当收到数据时,再在Y页面展现。第三种就是在Y页面中开启多线程。让一个子线程专门到后台去取数据。综合来说,第二种更加简介易懂,而且代码紧凑。


使用回调函数有什么好处?




(1)可以让实现方,根据回调方的多种形态进行不同的处理和操作。(ASIHttpRequest)


(2)可以让实现方,根据自己的需要定制回调方的不同形态。(UITableView)


(3)可以将耗时的操作隐藏在回调方,不影响实现方其它信息的展示。


(4)让代码的逻辑更加集中,更加易读。


什么是回调函数?——就是由声明函数的类来调用的函数叫做回调函数。普通函数可以让任何类调用。


“回调”的主语是谁?——声明“回调函数”的那个类。


Block、委托、通知、回调函数,它们虽然名字不一样,但是原理都一样,都是“回调机制”的思想的具体实现!


26.类中声明接口、接口中声明接口,由于接口不能实例化,所以默认都是静态的。伴随内部声明了接口的类,其实例化的过程中,需要定义实现对应的接口。这可以提供更多的上下文信息。
对于嵌套接口的情况,其好处是使得逻辑更加清晰、具有层次感。封装的思想,增强易读性与可维护性。
27.java输入与输出 我们很少使用单一的类来创建流对象,而是通过叠合多个对象来提供所期望的功能。这是装饰器设计模式。
实际上,Java中流类库让人迷惑的主要原因就在于:创建单一的流结果,却需要创建多个对象。
java的装饰器设计模式与适配器设计模式的相同点是:都拥有一个目标对象。不同点是:适配器模式需要实现另一个接口,而装饰器模式必须实现该对象的接口。
装饰器也可以扩展接口。
Java I/O类库需要多种不同功能的组合。这是装饰器存在的理由。但同时,提供灵活性也伴随着代码复杂性的增加。我们必须创建许多类——“核心”I/O类型加上所有的装饰器,才能得到我们所希望的单个I/O对象。


package pattern.decorator;    
    
public interface Sourcable {    
    public void operation();    
    
}
package pattern.decorator;    
    
public class Source implements Sourcable {    
    
    public void operation() {    
        System.out.println("原始类的方法");    
    }    
    
}
package pattern.decorator;    
    
public class Decorator1 implements Sourcable {    
    
    private Sourcable sourcable;    
    public Decorator1(Sourcable sourcable){    
        super();    
        this.sourcable=sourcable;    
    }    
        
    public void operation() {    
        System.out.println("第1个装饰器前");    
        sourcable.operation();    
        System.out.println("第1个装饰器后");    
    
    }    
    
}
package pattern.decorator;    
    
public class Decorator2 implements Sourcable {    
    
    private Sourcable sourcable;    
    public Decorator2(Sourcable sourcable){    
        super();    
        this.sourcable=sourcable;    
    }    
    public void operation() {    
        System.out.println("第2个装饰器前");    
        sourcable.operation();    
        System.out.println("第2个装饰器后");    
    
    }    
    
}
package pattern.decorator;    
    
public class Decorator3 implements Sourcable {    
    
    private Sourcable sourcable;    
    public Decorator3(Sourcable sourcable){    
        super();    
        this.sourcable=sourcable;    
    }    
    public void operation() {    
        System.out.println("第3个装饰器前");    
        sourcable.operation();    
        System.out.println("第3个装饰器后");    
    
    }    
    
}
package pattern.decorator;    
    
public class DecoratorTest {    
    
    /**  
     * @param args  
     */    
    public static void main(String[] args) {    
        Sourcable source = new Source();    
    
        // 装饰类对象     
        Sourcable obj = new Decorator1(new Decorator2(new Decorator3(source)));    
        obj.operation();    
    }    
    
}


运行该程序的输出如下:
第1 个装饰器装饰前
第2 个装饰器装饰前
第3 个装饰器装饰前
原始类的方法
第3 个装饰器装饰后
第2 个装饰器装饰后
第1 个装饰器装饰后
从输出的结果可以看出,原始类对象source 依次被 Decorator1 、 Decorator2 、 Decorator3 进行了装饰。
28.Java NIO -- 管道 (Pipe)
Java NIO 管道是2个线程之间的单向数据连接。
Pipe有一个source通道和一个sink通道。数据会被写到sink通道,从source通道读取。
管道设计模式:形象的对比拥有多个阀门的一个管道,流经管道的数据需要经过每一道阀门的处理,这里每一道阀门就是具有一段逻辑进行封装的对象。
细粒度下的模块化设计思想。
29.java 管道分为有名管道和无名管道。无名PIPE是一种非永久性的管道通信机制。有名管道FIFO是一种永久的管道通信机制。
管道通信方式的中间介质是文件,通常称为管道文件。无名管道一般用于父子进程间通信。而消息队列可用于你机器上的任何进程通信,只要进程有权操作消息队列。
如果大规模的复制数据,最快的方法莫过于共享内存。管道只是所有UNIX都支持,性能上不如共享内存。有些UNIX系统不支持消息队列。
消息队列与管道相比,其优点在于:提供格式字节流,减少开发工作量,更大的灵活性。且消息的类型可作为优先级使用。
30.BufferedInputStream使用它可以防止每次读取时都得进行实际写操作。代表使用缓冲区。
DataInputStream  dataInputStream = new DataInputStream(new FileInputStream(new File("text.txt")));“瓶中插花”,装饰器效果。
而BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(new File("text.txt"))));  
其中就可以把字节原数据当字符来读取了,因为InputStreamReader已经帮我们转换了。适配器模式。
31.适配器(adapter)类:InputStreamReader可以把InputStream转换为Reader,而OutputStreamWriter可以把OutputStream转换为Writer。
32.速度的提升在文件I/O和网络I/O中都有可能发生,我们这里只研究前者。速度的提升来自于所使用的结构更接近于操作系统执行I/O的方式:通道和缓冲器。通道可以想象成一个煤矿,包含煤炭(数据)的矿藏,缓冲器可以想象成派送矿藏的卡车。
我们获得煤炭是通过缓冲器,矿藏输出煤炭也是通过缓冲器。
唯一直接与通道交互的缓冲器是ByteBuffer,可以存储未加工字节的缓冲器,当我们查询JDK文档中的java.nio.ByteBuffer时,会发现它是一个相当基础的类,通过告知分配多少存储空间来创建一个ByteBuffer对象。用原始的字节形式或基本数据类型输出和读取数据。
但是没有输出和读取对象的能力,即便是字符串对象。
java 传统IO与NIO的区别:传统IO是阻塞的,而NIO是非阻塞的。这意味着一个单独的线程现在可以管理多个输入和输出通道(channel)。
NIO的一个目标就是快速的移动数据,因此,ByteBuffer的大小就变得尤为重要,1K可能比平时的小一些。
回顾java的发展史,早期的java,其jvm在解释字节码时速度慢,运行速率大大低于本地编译代码,因此忽略了IO的优化。
现在JVM运行时优化已前进了一大步,java应用程序更多的是受到IO的束缚,也就是将时间花在等待数据传输上。NIO减少了IO的等待时间,提升了IO的效率。
33.通道是一个相当基础的东西,可以向它传送用于读写的ByteBuffer,并且可以锁定文件的某些区域用于独占式访问。
通道的解释:
通道表示到能够执行 I/O 操作的实体(如文件和套接字)的连接。通道 表示到实体(如硬件设备、文件、网络套接字或者可以执行一个或多个诸如读取或写入之类的不同 I/O 操作的程序组件)的开放连接。正如在 Channel 接口中所指定的,通道可以处于打开或关闭状态,
并且它们既是可异步关闭的,又是可中断的。 通道并没有与之相关联的流,实际上它所拥有的read和write方法,都是通过调用buffer对象来实现的。
34.管道是Linux中很重要的一种通信方式,是把一个程序的输出直接连接到另一个程序的输入,常说的管道多是指无名管道,无名管道只能用于具有亲缘关系的进程之间,这是它与有名管道的最大区别。有名管道叫named pipe或者FIFO(先进先出),可以用函数mkfifo()创建。


Linux管道的实现机制
从本质上说,管道也是一种文件,但它又和一般的文件有所不同,管道可以克服使用文件进行通信的两个问题,具体表现为:·
1)限制管道的大小。实际上,管道是一个固定大小的缓冲区。在Linux中,该缓冲区的大小为1页,即4K字节,使得它的大小不象文件那样不加检验地增长。使用单个固定缓冲区也会带来问题,比如在写管道时可能变满,当这种情况发生时,随后对管道的write()调用将默认地被阻塞,等待某些数据被读取,以便腾出足够的空间供write()调用写。
2)读取进程也可能工作得比写进程快。当所有当前进程数据已被读取时,管道变空。当这种情况发生时,一个随后的read()调用将默认地被阻塞,等待某些数据被写入,这解决了read()调用返回文件结束的问题
注意:
从管道读数据是一次性操作,数据一旦被读,它就从管道中被抛 弃,释放空间以便写更多的数据。
管道的结构
在Linux 中,管道的实现并没有使用专门的数据结构,而是借助了文件系统的file结构和VFS的索引节点inode。
将两个file 结构指向同一个临时的VFS索引节点,而这个VFS引节点又指向一个物理页面而实现的。
35.字节存放次序:big endian 高位优先 将最重要的字节放在地址最低的存储器单元。little endian 低位优先 将最重要的字节放在地址最高的存储器单元。
36.bufferbyte缓冲区视图是bufferbyte到基本数据类型沟通的桥梁。
尽管通过对某个char数组调用wrap()方法来直接产生一个CharBuffer,但是在本例中取而代之的是分配一个底层的ByteBuffer,产生的CharBuffer只是ByteBuffer上的一个视图而已。
这里要强调的是:我们总是以操纵ByteBuffer为目标,因为它可以和通道进行交互沟通。
37.内存映射文件允许我们放入那些因为过大而无法放入内存的文件。通过内存映射文件就可以假定整个文件都放在内存中,而且可以完全把它当作非常大的数组来访问。
我们必须指定映射文件的初始位置和映射位置的长度,这意味着我们可以映射某个大文件的较小部分。
38.java的文件锁对其他的操作系统进程是可见的,因为java的文件加锁直接映射到了本地操作系统的加锁工具。
有参数的加锁是当前范围的锁定,当文件超出当前范围时,就无法控制超出的部分进行锁定。无参数的加锁是针对文件而言,即便文件扩大了,依旧锁定的是整个文件。
独占锁和共享锁的支持都必须由底层的操作系统提供。


39.Java 对象序列化将那些实现了Serializable接口的对象转换成一个字节序列,并能够在以后将这个序列完全恢复为原来的对象。序列化机制弥补了不同操作系统之间的差异。
轻量级持久性意味着对象的生命周期并不取决于程序是否在执行。轻量级体现在不能用某种persistent关键字来简单的定义一个对象。
对象序列化概念支持两个主要特性:一个是RMI,Remote Method Invocation远程方法调用,序列化辅助其远程传参。另一个是Java Beans,保存状态信息。
对象序列化是基于字节的。对象序列化特别聪明的地方是它不仅保存了全景图,而且能够追踪对象内包含的所有引用,进而形成一个对象网。
40.Java中常用的三个标记接口分别是:RandomAccess、Cloneable、Serializable,在查看JDK源码的时候,我们会经常发现这些接口的存在,它们不包含任何的方法,但是却广泛的存在,这种接口我们称之为标记接口(Mark Interface),
这些接口我们不用实现任何的方法,它们的作用就是当某个类实现这个接口的时候,我们就认为这个类拥有了接口标记的某种功能。
41.Externalizable提供显示序列化避免对象的敏感部分被序列化。如果我们正在操作的是一个Serializable对象,那么所有的序列化操作都会自动进行,为了能够给予控制,我们可以用transient关键字逐个关闭序列化。
只要将任何对象序列化到单一流中,就可以恢复出与我们写出时一样的对象网,并且没有任何意外重复复制出的对象。
为了更加安全的保存系统状态,我们引入“原子”操作进行序列化。将构成系统状态的所有对象都置入单一容器内,并在一个操作中将该容器直接写出。然后同样只需要一次方法调用,即可以将其恢复。
42.xml是序列化的一个很好的替代。
43.Preferences是一个键值集合,类似映射,Preferences API用于存储和读取用户的偏好以及程序配置项的设置。
44.enum 枚举类型的静态导入 能够将enum实例的标识带入当前的命名空间,所以无需再用enum类型来修饰enum实例。究竟是显示enum声明更好,还是静态导入更方便。具体情况具体分析。
45.编译器会自动把枚举用enum类来表示,无法被继承。
46.Class对象是一个特殊的对象,是用来创建其它对象的对象(这里的其他对象就是指:java类的实例)。其实Class对象就是java类编译后生成的.class文件,它包含了与类有关的信息。
每当第一次使用一个类时,JVM必须使用“类加载器”子系统加载该类对象的Class对象。一旦这个类的Class对象被载入内存,它就被用来创建这个类的所有对象。
当我们使用new关键字创建一个类的第一个对象的时候,JVM会帮助我们加载该类Class对象,但是当我们想自己加载这个类的Class对象怎么办呢?实际上有3种方法:
Class.forName("类名字符串")  (注意:类名字符串必须是全称,包名+类名)
类字面常量法:类名.class
实例对象.getClass()
47.对于enum而言,实现接口是使其子类化的唯一方法。
48.EnumSet 是一个专为枚举设计的集合类,EnumSet中的所有元素都必须是指定枚举类型的枚举值,该枚举类型在创建EnumSet时显式或隐式地指定。


EnumSet的集合元素也是有序的,EnumSet以枚举值在Enum类内的定义顺序来决定集合元素的顺序。
EnumSet在内部以位向量的形式存储,这种存储形式非常紧凑、高效,因此EnumSet对象占用内存很小,而且运行效率很好。尤其是进行批量操作(如调用containsAll()和retainAll()方法)时,如果其参数也是EnumSet集合,则该批量操作的执行速度也非常快。
EnumSet集合不允许加入null元素,如果试图插入null元素,EnumSet将抛出NullPointerException异常。
EnumSet类没有暴露任何构造器来创建该类的实例,程序应该通过它提供的类方法来创建EnumSet对象。
如果只是想判断EnumSet是否包含null元素或试图删除null元素都不会抛出异常,只是删除操作将返回false,因为没有任何null元素被删除。
49.位操作
再比如写入 1236 ,
字节位置: int nBytePos =1236/8 = 154;
位位置: int nBitPos = 1236 & 7 = 4


// / 把数组的 154 字节的 4 位置为 1
val = 1<<nBitPos; arrBit[nBytePos] = arrBit[nBytePos] |val;  
// 再写入 1236 得到arrBit[154]=0b00010100
函数实现:


#define SHIFT 5 
#define MAXLINE 32 
#define MASK 0x1F 
void setbit(int *bitmap, int i){     
    bitmap[i >> SHIFT] |= (1 << (i & MASK));
}
读指定位
bool getbit(int *bitmap1, int i){
    return bitmap1[i >> SHIFT] & (1 << (i & MASK));
}


50.责任链模式:将能够处理同一类请求的对象连成一条链,使这些对象都有机会处理请求,所提交的请求沿着链传递。从而避免请求的


发送者和接受者之间的耦合关系。链上的对象逐个判断是否有能力处理该请求,如果能则就处理,如果不能,则传给链上的下一个对象。


直到有一个对象处理它为止。
51.只要在一个形参的“类型”与“参数名”之间加上三个连续的“.”(即“...”,英文里的句中省略号),就可以让它和不确定个实参相匹配。而一个带有这样的形参的方法,就是一个实参个数可变的方法。
52.Java enum的功能很是强大。其中包括描述创建状态机。
53.多路分发:
java只支持单路分发。
如果想使用两路分发,就必须有两个方法调用:第一个方法调用决定第一个未知类型,第二个方法调用决定第二个未知类型。
要利用多路分发,程序员必须为每一个类型提供一个实际的方法调用,如果你要处理两个不同的类型体系,就需要为每个类型体系执行一个方法调用。一般而言,程序员需要有设定好的某种配置,以便一个方法调用能够引出更多的方法调用,从而能够在这个过程中处理多种类型。
54.石头剪刀布的算法示例,当两个Item项作为输入参数时,第一个进行动态绑定,之后在其内部进行另一个参数的具体判别,相当于另一个参数的判别封装到了具体的类中,逻辑实现更为优雅。其中,第二个参数可以通过switch来判断,由于enum限定了switch的选择范围,所以更安全。
55.有些时候程序可以变得更简短,但这种代码上的压缩并不等价于优化,对于某些大型系统而言,难以理解的代码将导致整个系统不够健壮。好的代码应该是易懂的,高效的和优雅的。
56.石头剪刀布的编码也可以通过EnumMap或者二维数组实现。其本质都是编织一个九宫格,将逻辑结果存储到里面,但不同的实现方法效果也不同,比如数组实现虽然代码简短,但也不安全,大型数组的尺寸也是很容易出错的。
这里“表驱动式编码”的概念具有非常强大的功能。
57.Unix设计原则时,其中有一条为“表示原则”:把知识叠入数据以求质朴而健壮。
数据驱动编程的核心出发点是相对于程序逻辑,人类更擅长处理数据。数据比程序逻辑更容易驾驭,所以我们应该尽可能的将设计的复杂度从程序代码转移至数据。
很多设计思路背后的原理都是相通的,隐含在数据驱动编程背后的实现思想包括:
控制复杂度:将程序逻辑转移到处理的数据中。
隔离变化:消息处理的逻辑不变,但消息是变化的,那就将两者分开。
机制和策略的分离:机制是消息的处理逻辑,策略就是不同的消息处理。
典型的用表驱动实现一个状态机。
总之,它不是一个全新的编程模型,而是一种设计思路,历史悠久,在Unix/linux应用很多。
不同于面向对象设计中的数据:“数据驱动编程中,数据不但表示了某个对象的状态,实际上还定义了程序的流程。OO看重的是封装,而数据驱动编程看重的是编写尽可能少的代码。”
值得思考的话:
数据压倒一切,如果选择了正确的数据结构并把一切组织的井井有条,正确的算法就不言自明。编程的核心是数据结构,而不是算法。——Rob Pike
程序员束手无策,,,只有跳脱代码,直起腰,仔细思考数据才是最好的行动。表达式编程的精髓。——Fred Brooks
数据比程序逻辑更易驾驭。尽可能把设计的复杂度从代码转移至数据是个好实践。——《unix编程艺术》作者
58.
01
interface Context {
02
    ByteBuffer buffer();
03
    State state();
04
    void state(State state);
05
}
06
interface State {
07
    /**
08
       * @return true to keep processing, false to read more data.
09
     */
10
    boolean process(Context context);
11
}
12
enum States implements State {
13
    XML {
14
        public boolean process(Context context) {
15
            if (context.buffer().remaining() < 16) return false;
16
            // read header
17
            if(headerComplete)
18
                context.state(States.ROOT);
19
            return true;
20
        }
21
    }, ROOT {
22
        public boolean process(Context context) {
23
            if (context.buffer().remaining() < 8) return false;
24
            // read root tag
25
            if(rootComplete)
26
                context.state(States.IN_ROOT);
27
            return true;
28
        }
29
    }
30
}
31
 
32
public void process(Context context) {
33
    socket.read(context.buffer());
34
    while(context.state().process(context));
35
}


-----------------------------------------------------
59.注解,也被称为元数据。元数据是用来描述数据的数据(Data that describes other data)。     元,即更为“抽象”的描述。数据在某种程度上是相对抽象的。
JavaSE5内置了三种,定义在java.lang中的注解:@Override @Deprecated @SuppressWarnings
60.元注解,专门用来形容其他注解的注解。Java 的四种元注解:@Target @Retention @documented @inherited
注解是真正的语言级的概念,一旦被构造出来,就享有编译期的类型检查保护。其是源代码级别保存所有的信息,而不是某种注释性的文字,这使得代码更为整洁,且易于维护。
与其他任何Java接口一样,注解也将会编译成class文件。使用中有默认值限制。自定义。
61.XML语言语法严谨,方便描述关系型数据。例如:Tomcat的配置文件、充当小型数据库。
62.使用apt生成注解处理器时,我们无法利用Java的反射机制,因为我们操作的是源代码,而不是编译后的类。
63.注解处理器在对单个注解处理分析时就有一定的工作量,当对多个注解处理分析时,为了避免工作量和复杂性的迅速攀升,mirror API提供了对访问者设计模式的支持。
64.Java 接口与抽象类的区别:
简单的说:接口是对行为的抽象,而类是对一种事物的抽象、对类抽象。所谓的面向对象语言,对象就是具体的事物。
先列举一下抽象类和接口的特性:
1)抽象类可以提供抽象方法的实现细节,即抽象方法可以具体化,一种极端的情况是具体定义某个抽象类的所有方法,但这也就失去了抽象类的本意。
  而接口中只能存在public abstract 方法。
2)抽象类的成员变量可以是具体类型的,而接口中的成员变量默认是且只能是public static final类型的,若private修饰则会编译报错,无法被继承。
接口的方法会被隐式地指定为public abstract方法,用其他关键字如private、protected、static、final等修饰都会编译报错。即同抽象类相比,接口更加抽象。
并且一般情况不在接口中定义常量。
3)接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法。
4)Java遵循单继承。一个类只能继承一个抽象类,而一个类却可以实现多个接口。接口间可以多重继承。
总的来说,继承是一个是不是的关系,而接口是一个有没有的关系。
如果从设计层面考虑,抽象类作为很多子类的父类,它是一种模板式设计。
而接口是一种行为规范,是一种辐射式设计。
具体来讲,模板式设计中,如果某个公共部分需求变了,只需要改动模板就可以了,无需变动具体的类。
也就是说对于抽象类,如果添加新的方法,可以直接在抽象方法中添加具体的实现,子类可以不进行变更。
因为抽象类中的具体方法是默认继承的,只是非抽象子类继承抽象方法时需要实现对应抽象方法。
而辐射式设计,比如电梯都安装了报警器,一旦更新报警器,那么所有电梯就必须全部更新。
接口就是这种情况,如果接口进行了变更,则所有实现这个接口的类都必须进行相应的改动。
因为某个类实现了某个具体的接口,由于接口中的方法都是public static修饰的,所以必须都实现、具体化描述方法的细节。当然,抽象类实现接口除外。
网上流传的最广泛的例子:门和报警器
门都有open()和close()两个动作,我们可以将其定义为抽象类。
但有些门有报警功能,怎么办?
如果将三个功能都放到抽象类中,则与某些门没有报警功能的情况不符。
如果将三个功能都放到接口中,则用到报警功能的类就要附加实现open和close方法。
这与实际情况不符,比如火灾报警器无需open和close。
所以,open和close 与 alarm相比,根本就不是同一个范畴里的行为。
这里,我们应该将open()和close()定义为抽象类,将alarm放到接口中。
65.访问者模式:
访问者模式(Visitor Pattern)是GoF提出的23种设计模式中的一种,属于行为模式。据《大话设计模式》中说算是最复杂也是最难以理解的一种模式了。
定义(源于GoF《Design Pattern》):表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素类的前提下定义作用于这些元素的新操作。
从定义可以看出结构对象是使用访问者模式必备条件,而且这个结构对象必须存在遍历自身各个对象的方法。这便类似于Java语言当中的collection概念了。
通用代码:


----------------------------------------------------
package Visitor;
abstract class West {//有几个east子类,这里就有几个goWest_n抽象方法。
    
    public abstract void goWest1(SubEast1 east);//对应east1 子类是基类,但基类不是子类。这里不能接收基类East对象。
    
    public abstract void goWest2(SubEast2 east);//对应east2  能够接收east静态类型,subEast2动态类型的引用对象动态绑定的方式传送到当前类中。
}
class SubWest1 extends West{
    
    @Override
    public void goWest1(SubEast1 east) {//east1 go west1 应该命名west1 for east1
        
        System.out.println("SubWest1 + " + east.myName1());
    }
    
    @Override
    public void goWest2(SubEast2 east) {//east2 go west1
        
        System.out.println("SubWest1 + " + east.myName2());
    }
}
class SubWest2 extends West{
    @Override
    public void goWest1(SubEast1 east) {//east1 go west2
        
        System.out.println("SubWest2 + " + east.myName1());
    }
    
    @Override
    public void goWest2(SubEast2 east) {//east2 go west2
        
        System.out.println("SubWest2 + " + east.myName2());
    }
}
class SubWest3 extends West{
    @Override
    public void goWest1(SubEast1 east) {//east1 go west2
        
        System.out.println("SubWest3 + " + east.myName1());
    }
    
    @Override
    public void goWest2(SubEast2 east) {//east2 go west2
        
        System.out.println("SubWest3 + " + east.myName2());
    }
}
abstract class East {


    public abstract void goEast(West west);
}
class SubEast1 extends East{//对应SubWest1
    @Override
    public void goEast(West west) {
        west.goWest1(this);
    }
    
    public String myName1(){
        return "SubEast1";
    }
}
class SubEast2 extends East{//对应SubWest2
    @Override
    public void goEast(West west) {
        west.goWest2(this);
    }
    
    public String myName2(){
        return "SubEast2";
    }
}
public class doubleDispatchByVisitorPattern {


    public static void main(String[] args) {
        //组合1
        East east = new SubEast1();
        West west = new SubWest1();
        //west.goWest1(east);East才可以。
        east.goEast(west);//这里就是一个通过visiterPattern实现的双重分配。
        //组合2
        east = new SubEast1();
        west = new SubWest2();
        east.goEast(west);
      //组合3
        east = new SubEast1();
        west = new SubWest3();
        east.goEast(west);
        
    }


}




----------------------------------------------------
package Visitor;


import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/*
 * 先声明一个抽象类,作为模板Element。
 * 它可以有两个方法,一个是接受访问者,参数类型是访问者的接口类型。
 * 另一个是具体的功能性行为。
 * */
abstract class Element
{
    public abstract void accept(IVisitor visitor);//1.这个专门为访问者提供的接口,究竟有什么用?让访问者访问呗!
    public abstract void doSomething();
}
class ConcreteElement1 extends Element{
    public void doSomething(){
        System.out.println("这是元素1");
    }
    public void accept(IVisitor visitor){
        visitor.visit(this);//访问当前元素。
    }
}
class ConcreteElement2 extends Element{
    public void doSomething(){
        System.out.println("这是元素2");
    }
    public void accept(IVisitor visitor){
        visitor.visit(this);//访问当前元素。
    }
}
interface IVisitor{//定义访问者接口。因为是行为的封装,所以定义为接口。
    public void visit(ConcreteElement1 el1);//某一个特定的、具体的元素。
    public void visit(ConcreteElement2 el2);//重载,针对其余特定元素。
}
class Visitor implements IVisitor{//访问者类的具体实现。
    public void visit(ConcreteElement1 el1){//与定义的接口是一一对应的。
        el1.doSomething();
    }
    public void visit(ConcreteElement2 el2){
        el2.doSomething();
    }
}
class ObjectStructure{//这个对象结构类是干啥的呢?是返回一个list容器啊!将每一个Element的实例放到list容器中。
    public static List<Element> getList(){//java泛型的应用。
        List<Element>list = new ArrayList<Element>();
        Random ran = new Random();//随机生成数。
        for(int i = 0 ; i < 10 ; i ++){
            int a=ran.nextInt(100);
            if(a>50){//分界条件。
                list.add (new ConcreteElement1());
            }else{
                list.add (new ConcreteElement2());
            }
        }
        return list;
    }
}
public class Client{//2.访问者模式的好处是啥呢?
    public static void main (String[]args){
        List<Element> list = ObjectStructure.getList();
        for(Element e:list){
            e.accept(new Visitor());
        }
    }
}
/*静下心了仔细研读,,
 * 首先,如果我们不用这个模式去设计当前的问题,如何实现相应的方法呢?添加访问者呗!
 * 那就没有访问者这个接口了。当然也没有里面包含的accept方法。
 * 那么Client中的e.accept(new Visitor)就要改写为e.doSomething()
 * 乍看起来好像也可以,而且代码量也减少了,如果我们修改Element抽象类中的数据成员,没问题。
 * 但我们修改Element抽象类中的行为方法时,比如添加visitor是可以得。直接添加。
 * 而添加Element元素,则需要修改每一个访问者,在访问者定义的接口中添加特定的针对该Element的接口。违背了开闭原则。
 */
/*科普:
 * 虚函数的存在是为了多态。
 * C++中普通成员函数加上virtual关键字就成为虚函数
 * Java中其实没有虚函数的概念,它的普通函数就相当于C++的虚函数,动态绑定是Java的默认行为。如果Java中不希望某个函数具有虚函数特性,可以加上final关键字变成非虚函数
 * PS: 其实C++和Java在虚函数的观点大同小异,异曲同工罢了。
 * 
 * 抽象函数或者说是纯虚函数的存在是为了定义接口。
 * C++中纯虚函数形式为:virtual void print() = 0;
 * Java中纯虚函数形式为:abstract void print();
 * PS: 在抽象函数方面C++和Java还是换汤不换药。
 * 
 * 抽象类的存在是因为父类中既包括子类共性函数的具体定义,也包括需要子类各自实现的函数接口。抽象类中可以有数据成员和非抽象方法。
 * C++中抽象类只需要包括纯虚函数,既是一个抽象类。如果仅仅包括虚函数,不能定义为抽象类,因为类中其实没有抽象的概念。
 * Java抽象类是用abstract修饰声明的类。
 * PS: 抽象类其实是一个半虚半实的东西,可以全部为虚,这时候变成接口。
 * 
 * 接口的存在是为了形成一种规约。接口中不能有普通成员变量,也不能具有非纯虚函数。
 * C++中接口其实就是全虚基类。
 * Java中接口是用interface修饰的类。
 * PS: 接口就是虚到极点的抽象类。
 * 
 * C++虚函数    ==  Java普通函数
 * C++纯虚函数  ==  Java抽象函数
 * C++抽象类    ==  Java抽象类
 * C++虚基类    ==  Java接口
 * 非虚方法是所有的类方法(也就是申明为static的方法) + 所有声明为final或private的实例方法.
 * Java虚函数是无法内联的。所谓内联就是:Java虚拟机运行的一种优化手段。
 * Java编译器会对编译的类进行类继承的分析,当确认某个类的具体方法只有一个版本,即其没有重写等,比如用final声明的,就会进行方法内联编译优化。
 * 非内联函数遵从函数调用机制,在汇编中用call来调用。内联函数则没有这些。
 * inline函數表示該函數是内聯的,它建議編譯器在調用該函數的地方直接將函數的代碼展開來 
 * 插入caller的代碼中.這個只是一種指示至於會不會被内聯編譯器還會根據被聲明為inline 
 * 的函數的内部結構如:是否包含循環,複雜的函數調用等等來選擇是否inline。 
1.虛函數肯定不會被内聯這一點毋庸置疑,因為虛函數只有到了Runtime才能被識別到底是哪一個被
    調用,而内聯是編譯期就會將代碼展開並安插這個明顯不是一囘事。 
2.inline有兩种表現方式一種就是以inline在實現文件中(.cpp)指出這被稱爲顯示内聯,另外一種 
    就如你所說類的聲明和定義放入同一個文件這稱爲隱式内聯,但是還是如前面所說inline只是一個 
    提示符至於會不會内聯還是由編譯器說了算。
 虚方法也可能是被静态分派的.特别注意,重载就是通过静态分派的。非虚方法是静态分配的。
    · invokespecial - super方法调用、private方法调用与构造器调用 
· invokevirtual - 用于调用一般实例方法(包括声明为final但不为private的实例方法) 
 invokespecial调用的目标必然是可以静态绑定的,因为它们都无法参与子类型多态;invokevirtual的则一般需要做运行时绑定
 所有invokespecial指令调用的方法都是非虚方法,而非虚方法也都是用invokespecial方法调用的.但是,后半句有两个例外,static修饰的方法与final修饰且非private的方法
理解:
Java 宗量 是指:调用方法的接收者或方法的参数,实际这些宗量是可以作为对目标方法进行选择的判别依据。
单分派是根据一个宗量对目标方法进行选择。
多分派是根据多于一个宗量对目标进行选择。
方法分派指的是虚拟机如何确定应该执行哪个方法!
Java是静态多分派的语言:在编译阶段,JVM会根据方法的接收者(静态方法中默认为this)和方法的参数一同判别。
重载方法的分派是根据静态类型进行的,这个分派过程在编译时期就完成了。
Java是动态单分派的语言:在运行阶段,JVM会只通过方法的接收者对目标函数进行判断。
Java语言中方法重载采用静态分派是JVM规范规定的还是语言级别的规定? 
Java语言对重载采用静态分派的原因在于Java是动态单分派的!编译阶段已经确定了静态的参数变量。
C++和Java均是单分派语言,多分派语言的例子包括CLOS和Cecil。
按照这样的区分,Java就是动态的单分派语言,因为这种语言的动态分派仅仅会考虑到方法的接收者的类型,同时又是静态的多分派语言,因为这种语言对重载方法的分派会考虑到方法的接收者的类型以及方法的所有参数的类型。
但是通过使用设计模式,也可以在Java语言里实现动态的双重分派。
@Override
    public void goEast(West west) {
        west.goWest1(this);
    }
    East对象调用goEast方法,进行一次动态单分配,West对象作为参数传入调用goWest_n方法,又进行一次动态单分配。
          注意,这里的goWest_n是针对不同east子类设定的接口。
          
访问者模式可以为为不同类型的元素提供多种访问操作方式,而且可以在不修改原有系统的情况下增加新的操作方式。
从增加新的元素的角度来看,访问者模式违背了“开闭原则”。
 */


------------------------------------------------------------------------------------------
科普一下:


所谓职责是指类变化的原因。如果一个类有多于一个的动机被改变,那么这个类就具有多于一个的职责。而单一职责原则就是指一个类或者模块应该有且只有一个改变的原因。
一个类,只有一个引起它变化的原因。应该只有一个职责。每一个职责都是变化的一个轴线,如果一个类有一个以上的职责,这些职责就耦合在了一起。这会导致脆弱的设计。
当一个职责发生变化时,可能会影响其它的职责。另外,多个职责耦合在一起,会影响复用性。例如:要实现逻辑和界面的分离。
没有任何的程序设计人员不清楚应该写出高内聚低耦合的程序,但是很多耦合常常发生在不经意之间,其原因就是:
职责扩散:因为某种原因,某一职责被分化为颗粒度更细的多个职责了。
单一职责原则并不是一个孤立的面向对象设计原则,它是面向对象设计五个基本原则(SOLID)之一。这些原则是:单一职责原则、开闭原则(类、函数和模块等应该是可以扩展的,但是不可修改。)接口隔离原则(多个专用的接口优于一个单一的通用接口。不要把所有的方法都添加到一个接口中。)、里氏替换原则(子类必须能够替换掉他们的父类。意思是,子类必须具有父类的所有特性。)和依赖倒置原则(抽象不应该依赖细节,细节应该依赖于抽象。)。
这些原则被一起应用时可以使一个软件系统更易被维护和扩展。这些原则被典型的应用在测试驱动开发上,并且是敏捷开发以及自适应软件开发等指导思想的重要组成部分。
迪米特法则又叫最少知道原则,一个对象应该对其他对象保持最少的了解。
迪米特法则还有一个更简单的定义:只与直接的朋友通信。
首先来解释一下什么是直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。
耦合的方式很多,依赖、关联、组合、聚合等。
其中,我们称出现成员变量、方法参数、方法返回值中的类为直接的朋友,而出现在局部变量中的类则不是直接的朋友。
也就是说,陌生的类最好不要作为局部变量的形式出现在类的内部。
组合/聚合复用原则:
就是说要尽量的使用合成和聚合,而不是继承关系达到复用的目的


该原则就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分:新的对象通过向这些对象的委派达到复用已有功能的目的。


其实这里最终要的地方就是区分“has-a”和“is-a”的区别。相对于合成和聚合,


继承的缺点在于:父类的方法全部暴露给子类。父类如果发生变化,子类也得发生变化。聚合的复用的时候就对另外的类依赖的比较的少。
66.
几种关系所表现的强弱程度依次为:


          组合>聚合>关联>依赖


  其中依赖(Dependency)的关系最弱,而关联(Association),聚合(Aggregation),组合(Composition)表示的关系依次增强。
换言之关联,聚合,组合都是依赖关系的一种,聚合是表明对象之间的整体与部分关系的关联,而组合是表明整体与部分之间有相同生命周期关系的聚合。
而关联与依赖的关系用一句话概括下来就是,依赖描述了对象之间的调用关系,而关联描述了对象之间的结构关系。


66.java的规范:一个java文件中有且只能有一个声明为public的class。


67.如果想使得操作与对象解耦,一个简单的想法是将针对不同的子对象的操作封装到一个类中,可以将其定义为一个接口,我们称之为访问者。
而被操作的对象可以定义为一个抽象类,不同的对象继承至抽象类,具体实现的个体我们称之为元素。
访问者接口中应该为每一个元素都提供一个对应的操作行为。每一个访问者个体实现该接口,都具有操作每一个元素的能力。
那么,在调用过程中,如何将不同的元素与其访问者中的对应操作关联到一起呢?
由于Java在动态绑定过程中,是根据单一宗量进行判别的,所以需要找到一种方式实现双向分派。访问者模式就是解决方案。
访问者模式的本质是实现双向分派以便于将操作和对象进行分离。添加访问者属于扩展性操作。
操作与对象的解耦,添加新的操作,无需向类的定义中添加方法。
那么有没有哪种模式是针对对象的呢?使得添加信息无需向类的定义中添加属性。这又有意义吗?
68.在处理注解时,一个Java类可以看作是一系列对象的集合,当配合访问者模式使用apt工具时,需要提供一个Visitor类,它具有一个能够处理你要访问的各种声明的方法。
然后,就可以为方法、类以及域上的注解实现相应的处理行为。


69.Java中,最著名的单元测试工具JUnit。
70.极限编程是一个轻量级的、灵巧的软件开发方法;同时它也是一个非常严谨和周密的方法。它的基础和价值观是交流、朴素、反馈和勇气;即,任何一个软件项目都可以从四个方面入手进行改善:加强交流;从简单做起;寻求反馈;勇于实事求是。、
XP是一种近螺旋式的开发方法,它将复杂的开发过程分解为一个个相对比较简单的小周期;通过积极的交流、反馈以及其它一系列的方法,开发人员和客户可以非常清楚开发进度、变化、待解决的问题和潜在的困难等,并根据实际情况及时地调整开发过程。
71.继承使我们失去了访问被测试类中的private方法的访问权。
72.在并发编程中,编写任何复杂程序之前,应该学习一下专门讨论这个主题的书籍。
73.吞吐量是指对网络、设备、端口、虚电路或其他设施,单位时间内成功地传送数据的数量(以比特、字节、分组等测量)。防火墙吞吐量是指在没有帧丢失的情况下,设备能够接收并转发的最大数据速率。
74.并发编程是针对多核处理器的。在单处理器进行并发编程的开销反而比整个程序的顺序执行要大,因为并发实现是有代价的,比如上下文切换。但是,如果单核处理器执行的程序中存在阻塞,则并发编程亦使得效率提高。
75.为需要处理的事件编写相应的事件处理程序。要理解事件驱动和程序,就需要与非事件驱动的程序进行比较。
实际上,现代的程序大多是事件驱动的,比如多线程的程序,肯定是事件驱动的。早期则存在许多非事件驱动的程序,这样的程序,在需要等待某个条件触发时,会不断地检查这个条件,直到条件满足,这是很浪费cpu时间的。
而事件驱动的程序,则有机会释放cpu从而进入睡眠态(注意是有机会,当然程序也可自行决定不释放cpu),当事件触发时被操作系统唤醒,这样就能更加有效地使用cpu.
76.包容是外部组件包含指向内部组件接口的指针。外部组件相对来说是内部组件的一个客户,它将使用内部组件的接口来实现它自己的接口。
聚合是包容的一个特例,当一个外部组件聚合了某个内部组件的一个接口时,它并没有像包容那样重新实现此接口并明确地将调用请求转发给内部组件。相反,外部组件将直接把内部件的接口指针返回给客户。
77.COM即组件对象模型,是Component Object Model取前三个字母的缩写,这三个字母在当今Windows世界中随处可见。随时涌现出来的大把大把的新技术都以COM为基础。各种文档中也充斥着诸如COM对象、接口、服务器之类的术语。
因此,对于一个程序员来说,不仅要掌握使用COM的方法,而且还要彻底熟悉COM的所有一切。
78.进程是指一种“自包容”的运行程序。
79.Erlang是一种通用的面向并发的编程语言,它由瑞典电信设备制造商爱立信所辖的CS-Lab开发,目的是创造一种可以应对大规模并发活动的编程语言和运行环境。
在编程范型上,Erlang属于多重范型编程语言,涵盖函数式、并发式及分布式。顺序执行的Erlang是一个及早求值, 单次赋值和动态类型的函数式编程语言。
80.函数型语言被设计为可以将并发任务彼此隔离,其中,每个函数调用都不会产生任何副作用,并因此可以当作独立的任务来驱动。
81.Java采取更加传统的方式,在顺序型语言的基础上提供对线程的支持。
82.抢占式线程 防止不公平,时间片分割,定期打断中断线程。
83.线程的调度有抢占式和非抢占式。抢占式防止单个线程的独占。
84.消息系统没有资源的共享,却有同步机制。
85.一个线程就是一个进程中的一个单一的顺序控制流。
86.例程的作用类似于函数,但含义更为丰富一些。
例程是某个系统对外提供的功能接口或服务的集合。比如操作系统的API、服务等就是例程;Delphi或C++Builder提供的标准函数和库函数等也是例程。
我们编写一个DLL的时候,里面的输出函数就是这个DLL的例程。
87.协程(coroutine)技术是一种程序控制机制,早在上世纪60年代就已提出,用它可以很方便地实现协作式多任务。
协程是一种程序组件,是由子例程的概念泛化而来的,子例程只有一个入口点且只返回一次,而协程允许多个入口点,可以在指定位置挂起和恢复执行。
88.当java调用yield时,只是在建议具有相同优先级的其他线程可以运行。
89.后台线程setDaemon(true),daemon.start().只要有任何非后台线程运行,程序就不会终止。
90.后台线程创建的任何线程将被自动设置成后台线程。
91.线程组无意义。继续错误的代价由别人来承担,而承认错误的代价由自己承担。
92.工厂模式主要是为创建对象提供过渡接口,以便将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。 
工厂模式可以分为三类: 


1)简单工厂模式(Simple Factory) 
2)工厂方法模式(Factory Method) 
3)抽象工厂模式(Abstract Factory) 


 这三种模式从上到下逐步抽象,并且更具一般性。 
        GOF在《设计模式》一书中将工厂模式分为两类:工厂方法模式(Factory Method)与抽象工厂模式(Abstract Factory)。


        将简单工厂模式(Simple Factory)看为工厂方法模式的一种特例,两者归为一类。 
简单工厂模式又称静态工厂方法模式。重命名上就可以看出这个模式一定很简单。它存在的目的很简单:定义一个用于创建对象的接口。 


93.每个访问临界共享资源的方法都必须被同步,否则它们就不会正常的工作。
94.原子性操作:防止多cpu同时获得临界资源的锁权限。
基石是CPU对总线加锁,加锁的方式是:拉低电位。
CAS:compare and swap 比较和替换是设计并发算法时用到的一种技术。是一种系统原语。
95.如果使用volatile关键字,就会获得原子性。原子操作可由线程机制来保证其不可中断,专家级的程序员可以利用这一点来编写无锁的代码,这些代码不需要被同步。
原子操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程).
96.主存储器即是内存。辅助存储器即是硬盘或者U盘。
内存是计算机中重要的部件之一,它是与CPU进行沟通的桥梁。
计算机中所有程序的运行都是在内存中进行的,因此内存的性能对计算机的影响非常大。
内存(Memory)也被称为内存储器,其作用是用于暂时存放CPU中的运算数据,以及与硬盘等外部存储器交换的数据。
只要计算机在运行中,CPU就会把需要运算的数据调到内存中进行运算,当运算完成后CPU再将结果传送出来,内存的运行也决定了计算机的稳定运行。 
内存是由内存芯片、电路板、金手指等部分组成的。
97.volatile(易变的)保证了应用中的可视性。所谓的可视性是指一个域被声明为volatile的,那么只要对这个域产生了写操作,那么所有的读操作就可以看到这个修改。
即便是使用了本地缓存,情况也确实如此,volatile域就会立即被写入主存中,而读取操作就发生在主存中。
对于volatile修饰的变量,jvm虚拟机只是保证从主内存加载到线程工作内存的值是最新的。
例如假如线程1,线程2 在进行read,load 操作中,发现主内存中count的值都是5,那么都会加载这个最新的值
在线程1堆count进行修改之后,会write到主内存中,主内存中的count变量就会变为6
线程2由于已经进行read,load操作,在进行运算之后,也会更新主内存count的变量值为6
导致两个线程及时用volatile关键字修改之后,还是会存在并发的情况。
98.happen-before
对volatile字段的写操作happen-before后续的对同一个字段的读操作。
同步也会导致向主存中刷新。
这都是为了保证多线程下的操作逻辑是正确的。
99.一个任务(对应线程)所作的任何写入操作对这个任务来说都是可视的,因此如果它只需要在这个任务内部可视,那么你就不需要将其设置为volatile的。
当一个域的值依赖于它之前的值时,例如递增一个计数器,volatile就无法工作了。如果某个域的值受到其他域的值的限制,那么volatile也无法工作,例如Range类的lower和upper边界就必须遵循lower<=upper的限制。
使用volatile而不是synchronized的唯一安全的情况是类中只有一个可变的域。再次提醒,你的第一个选择应该是使用synchronized关键字,这是最安全的方式,而尝试其他任何方式都是有风险的。


100.你永远都不知道一个线程何时在运行。单人浴室洗澡类比线程访问临界区资源。
101.所有对象都自动含有单一的锁。当在对象上调用任意synchronized方法的时候,此对象都被加锁,这时候该对象上的其他synchronized方法只有等到前一个方法调用完毕并释放了锁之后才能被调用。
102.为了避免其它任务直接访问域,使用并发时,将域设置为private是非常重要的。
103.一个任务可以多次获得对象的锁,具体个数由JVM负责跟踪。
104.每个访问临界区共享资源的方法都必须被同步,否则它们就不会正确的工作。
105.显示的Lock对象,增加了灵活性,可以在finally子句unlock,将系统维护在正确的状态。
106.如果多个任务在同时访问某个域,那么这个域就应该是volatile的:确保本条指令不会因编译器的优化而省略(即直接内存操作,而非缓存),且要求每次直接读值。即自动刷新到主存。
否则,这个域就应该只能经由同步来访问。同步也会导致向主存中刷新,因此如果一个域完全由synchronized方法或语句块来防护,那就不必将其设置为是volatile的。
一个任务所作的任何写入操作都是对其自身可视的,而volatile是让其对外部也可视。
107.同步控制块。synchronized修饰的代码块。代码段被称为临界区。
108.方法签名 = 方法名 + 参数列表;
方法名:定义的方法的名称;
参数列表:参数的类型,个数,顺序;
方法签名不同,则方法不同。
方法重载是让类以统一的方式处理不同类型数据的一种手段。多个同名函数同时存在,具有不同的参数个数/类型。重载是一个类中多态性的一种表现。
Java的方法重载,就是在类中可以创建多个方法,它们具有相同的名字,但具有不同的参数和不同的定义。调用方法时通过传递给它们的不同参数个数和参数类型给它们的不同参数个数和参数类型给它们的不同参数个数和参数类型来决定具体使用哪个方法, 这就是多态性。
重载的时候,方法名要一样,但是参数类型和个数不一样,返回值类型可以相同也可以不相同。无法以返回型别作为重载函数的区分标准。
父类与子类之间的多态性,对父类的函数进行重新定义。如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)。在Java中,子类可继承父类中的方法,而不需要重新编写相同的方法。
但有时子类并不想原封不动地继承父类的方法,而是想作一定的修改,这就需要采用方法的重写。方法重写又称方法覆盖。
若子类中的方法与父类中的某一方法具有相同的方法名、返回类型和参数表,则新方法将覆盖原有的方法。如需父类中原有的方法,可使用super关键字,该关键字引用了当前类的父类。
子类函数的访问修饰权限不能少于父类的;
109.模板方法:行为有相同,也有不同。相同的在抽象方法中具体化,大家公用。不同的抽象方法抽象化,子类具体实现,各走各的路。
110.Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。


yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。


结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。
也就是说,刚刚的那个线程还是有可能会被再次执行到的,并不是说一定会执行其他线程了。
该方法与sleep()类似,只是不能由用户指定暂停多长时间,并且yield()方法只能让同优先级的线程有执行的机会。
111.并发编程中,某些错误是低概率事件,测试过程中都很难被发现。所以要格外小心。
112.无论任何时刻,只要任务以不可中断的方式被阻塞,那么都有潜在的会锁住程序的可能。
113.中断是指计算机运行过程中,出现某些意外情况需主机干预时,机器能自动停止正在运行的程序并转入处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行。
114.当任务协作时,关键问题是这些任务之间的握手。为了实现握手,我们使用了相同的基础特性:互斥。互斥能够确保只有一个任务可以响应某个信号,这样就可以根除任何可能的竞争条件。
在互斥之上,可以添加一种途径,即自身挂起,直到某些外部条件发生变化。
115.挂起(等待,阻塞)进程在操作系统中可以定义为暂时被淘汰出内存的进程,机器的资源是有限的,在资源不足的情况下,操作系统对在内存中的程序进行合理的安排,其中有的进程被暂时调离出内存,
当条件允许的时候,会被操作系统再次调回内存,重新进入等待被执行的状态即就绪态,系统在超过一定的时间没有任何动作。
116.
线程的挂起:被动的,被挂起,被操作系统挂起。
睡眠:主动的,停一会儿。
阻塞:客观因素,不得不停。
117.
如果线程在等待锁,对线程对象调用interrupt()只是会设置线程的中断标志位,线程依然会处于BLOCKED状态。
interrupt方法只会设置线程的中断标志,而并不会使它从锁等待队列中出来。
在使用synchronized关键字获取锁的过程中不响应中断请求,这是synchronized的局限性。如果这对程序是一个问题,应该使用显式锁,java中的Lock接口,它支持以响应中断的方式获取锁。
对于Lock.lock(),可以改用Lock.lockInterruptibly(),可被中断的加锁操作,它可以抛出中断异常。等同于等待时间无限长的Lock.tryLock(long time, TimeUnit unit)。
线程对中断的反应
RUNNABLE:线程在运行或具备运行条件只是在等待操作系统调度
WAITING/TIMED_WAITING:线程在等待某个条件或超时
BLOCKED:线程在等待锁,试图进入同步块
NEW/TERMINATED:线程还未启动或已结束
由于使用stop()方法停止线程非常的暴力,人家线程运行的好好的,突然就把人家杀死了,线程占用的锁被强制释放,极易导致数据的不一致性。
因此,提出了一种温和的方式:请求另外一个线程不要再执行了,这就是中断方式。
118.wait、notify、notifyAll的作用是:(中断与唤醒)
wait() 与 notify/notifyAll 方法必须在同步代码块中使用。
由于 wait() 与  notify/notifyAll() 是放在同步代码块中的,因此线程在执行它们时,肯定是进入了临界区中的,即该线程肯定是获得了锁的。


当线程执行wait()时,会把当前的锁释放,然后让出CPU,进入等待状态。
当执行notify/notifyAll方法时,会唤醒一个处于等待该 对象锁 的线程,然后继续往下执行,直到执行完退出对象锁锁住的区域(synchronized修饰的代码块)后再释放锁。
从这里可以看出,notify/notifyAll()执行后,并不立即释放锁,而是要等到执行完临界区中代码后,再释放。
故,在实际编程中,我们应该尽量在线程调用notify/notifyAll()后,立即退出临界区。即不要在notify/notifyAll()后面再写一些耗时的代码。
如果用 if 测试List中的数据的个数,则会出现IndexOutofBoundException。
但是如果用while 测试List中数据的个数,则不会出现越界异常!
因此,需要用while循环来判断条件的变化,而不是用if。
当notifyAll()因某个特定的锁而被调用时,只有等待这个锁的任务才会被唤醒。


119.wait()和 notifyAll()每次交互都握手,比较低级的方式解决了任务的互操作问题。
120.产生死锁的4个必要条件:
1:互斥条件。任务使用的资源中至少有一个是不能共享的。
2:至少有一个任务手握某个资源并等待另一个被其他任务占有的资源。
3:每个任务不能强行获取资源。资源不能被任务抢占,任务必须把资源释放当作普通的事情。
4:必须有循环等待。
避免死锁的方法是打破其中的某一个条件即可:
1:将独占资源改为虚拟资源。
2:打破不可抢占条件:当前任务在获得某个独占性资源的情况下,无法获得另一个独占性资源,则释放当前占有的独占性资源。
3:打破占有且申请条件:采用资源预先分配策略,当所有条件满足再运行,否则则等待。避免占着茅坑不拉屎的情况发生。
4:打破循环等待条件:实现资源有序分配策略。例如:哲学家的吃饭问题中,最后一个哲学家与其他哲学家不同,其先拿左边的筷子,再拿右边的筷子。


口诀:资源有限,不能硬抢,争相使用,互不退让。


121.Exchanger是在两个任务之间交换的栅栏。当这些任务进入时,它们各自拥有一个对象,当它们离开时,它们都拥有之前由对方持有的对象。
简单来说:线程资源互换。交换过程是同步的。
122.
wait()方法,notify()方法/notifyall()方法 都是锁级别的操作,锁级别的操作是object对象中封装的,线程锁定是针对对象进行,而非针对Thread类进行,所以没有将锁级别操作封装到线程中。
且这一对方法必须放到synchronized方法或者块中,只有在synchronized方法或者块(类似操作系统原语的功能,其执行不会受到多线程机制的干扰)中,当前线程才能占有锁,才有锁可以释放。
而相比之下,sleep()来自Thread类,和wait()来自Object类不同,调用sleep()方法过程中,线程不会释放对象锁。而调用wait()方法,对象会调用对象锁。
sleep()睡眠后不出让系统资源,wait让其它线程可以调用CPU。sleep(milliseconds)需要指定一个睡眠时间,时间一到会自动唤醒。而wait()需要配合notify()或者notifyall()使用。
123.
其实这是一个如何停止线程的问题,要真正理解interrupt()方法,要先了解stop()方法。
在以前通过thread.stop()可以停止一个线程,注意stop()方法是可以由一个线程去停止另外一个线程,这种方法太过暴力而且是不安全的,怎么说呢,线程A调用线程B的stop方法去停止线程B,调用这个方法的时候线程A其实并不知道线程B执行的具体情况,这种突然间地停止会导致线程B的一些清理工作无法完成,还有一个情况是执行stop方法后线程B会马上释放锁,这有可能会引发数据不同步问题。基于以上这些问题,stop()方法被抛弃了。
在这样的情况下,interrupt()方法出现了,它与stop不同,它不会真正停止一个线程,它仅仅是给这个线程发了一个信号告诉它它应该结束了(设置一个停止标志)。
真正符合安全的做法,就是让线程自己去结束自己,而不是让一个线程去结束另外一个线程。
通过interrupt()和.interrupted()方法两者的配合可以实现正常去停止一个线程,线程A通过调用线程B的interrupt方法通知线程B让它结束线程,在线程B的run方法内部,通过循环检查.interrupted()方法是否为真来接收线程A的信号,如果为真就可以抛出一个异常,在catch中完成一些清理工作,然后结束线程。Thread.interrupted()会清除标志位,并不是代表线程又恢复了,可以理解为仅仅是代表它已经响应完了这个中断信号然后又重新置为可以再次接收信号的状态。
从始至终,理解一个关键点,interrupt()方法仅仅是改变一个标志位的值而已,和线程的状态并没有必然的联系。理解了这点,其他的问题就很清晰了。
124.
Thread.suspend 天生容易引起死锁。如果目标线程挂起时在保护系统关键资源的监视器上持有锁,那么其他线程在目标线程恢复之前都无法访问这个资源。如果要恢复目标线程的线程在调用 resume 之前试图锁定这个监视器,死锁就发生了。这种死锁一般自身表现为“冻结( frozen )”进程。
我应该用什么来取代 Thread.suspend 和 Thread.resume ?
与Thread.stop ,类似,谨慎的方式,是让“目标线程”轮询一个指示线程期望状态(活动或挂起)的变量。当期望状态是挂起时,线程用 Object.wait 来等待;当恢复时,用 Object.notify 来通知目标线程。
总之,java 由interrupt代替stop,由wait()和notify()/notifyall() 代替 suspend()和resume().
125.SynchronousQueue 没有缓冲的队列。进队的前提是立刻出队,出队的同时是立刻进队。瞬时同步的思想。
126.SynchronousQueue是一种没有内部容量的阻塞的队列。
127.volatile与static的区别:
volatile声明当前字段是易变的(可能被多个线程使用),Java内存模型负责各个线程的工作区与主存区的该字段的值保持同步,即一致性。
static声明当前字段是静态的(可能被多个实例共享),在主存区上该类的所有实例的该字段为同一个变量,即唯一性。
volatile声明变量值的一致性,static声明变量的唯一性。
此外,volatile同步机制不同于synchronized,前者是内存同步,后者是内存同步(一致性),且保证线程互斥(互斥性)。
static只是声明变量在主存上的唯一性,不能保证工作区与主存区变量值的一致性。除非变量是不可变的,即加上final的修饰符,否则static声明的变量,不是线程安全的。
128.Callable与Runnable的区别:都是接口,用来编写多线程,且Thread.start()启动线程。但是实现Callable的任务线程能够返回执行结果,而实现Runnable接口的任务线程不能返回结果。
Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛;
Callable接口支持返回执行结果,此时需要调用FutureTask.get()方法实现,此方法会阻塞主线程直到获取‘将来’结果;当不调用此方法时,主线程不会阻塞!
注意ExecutorService 在Callable中使用的是submit(), 在Runnable中使用的是 execute()
129.
只互斥那些你绝对必须互斥的部分。测试程序对synchronized关键字和Lock及Atomic进行实验,看起来synchronized关键字占优,突然越过门槛值以后,
synchronized关键字似乎变得非常低效。注意:Atomic对象只有在非常简单的情况下才能有用。
130.
同步就是烧开水,需要自己去轮询(每隔一段时间去看看水开了没),异步就是水开了,然后水壶会通知你水已经开了,你可以回来处理这些开水了。
阻塞就是说在煮水的过程中,你不可以去干其他的事情,非阻塞就是在同样的情况下,可以同时去干其他的事情。阻塞和非阻塞是相对于线程是否被阻塞。
131.
volatile关键字,Java规范的特性是:happens-before
保证多线程等读到的内存数据是一致的,把改写的数据在读之前写回到主存。保证了共享变量的修改对所有线程是可见的。
在Java官方的教程讲“原子性操作”时,提到平常写代码遇到的最简单的原子操作:
对引用变量(不是引用的对象)和大多数基本类型变量(除了long和double)的读写操作都是原子性的。
long和double除外的原因很简单,8字节长的它们,如果运行在32位的机器上,JVM需要执行更多的操作来实现long和double的运算。所以JVM不能保证long和double类型读写操作的原子性。
但volatile的++和- -操作并不是原子性的。读写操作仅仅是指“取值”和“赋值”操作。
对于所有volatile修饰的变量,它们的取值和赋值操作是原子性的。
132.compare and swap
简单来说,CAS 的含义是“我认为原有的值应该是什么,如果是,则将原有的值更新为新值,否则不做修改,并告诉我原来的值是多少”(这段描述引自《Java Concurrency in Practice》)
133.
乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。而乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本( Version )记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
悲观锁(Pessimistic Lock),正如其名,具有强烈的独占和排他特性。它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。
悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。[1]
乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。[1] 乐观锁不能解决脏读的问题。


Java中的乐观锁和悲观锁:我们都知道,cpu是时分复用的,也就是把cpu的时间片,分配给不同的thread/process轮流执行,时间片与时间片之间,需要进行cpu切换,也就是会发生进程的切换。切换涉及到清空寄存器,缓存数据。然后重新加载新的thread所需数据。当一个线程被挂起时,加入到阻塞队列,在一定的时间或条件下,在通过notify(),notifyAll()唤醒回来。在某个资源不可用的时候,就将cpu让出,把当前等待线程切换为阻塞状态。等到资源(比如一个共享数据)可用了,那么就将线程唤醒,让他进入runnable状态等待cpu调度。这就是典型的悲观锁的实现。独占锁是一种悲观锁,synchronized就是一种独占锁,它假设最坏的情况,并且只有在确保其它线程不会造成干扰的情况下执行,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。
但是,由于在进程挂起和恢复执行过程中存在着很大的开销。当一个线程正在等待锁时,它不能做任何事,所以悲观锁有很大的缺点。举个例子,如果一个线程需要某个资源,但是这个资源的占用时间很短,当线程第一次抢占这个资源时,可能这个资源被占用,如果此时挂起这个线程,可能立刻就发现资源可用,然后又需要花费很长的时间重新抢占锁,时间代价就会非常的高。
所以就有了乐观锁的概念,他的核心思路就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。在上面的例子中,某个线程可以不让出cpu,而是一直while循环,如果失败就重试,直到成功为止。所以,当数据争用不严重时,乐观锁效果更好。比如CAS就是一种乐观锁思想的应用。
134.
在CAS操作中,会出现ABA问题。就是如果V的值先由A变成B,再由B变成A,那么仍然认为是发生了变化,并需要重新执行算法中的步骤。有简单的解决方案:不是更新某个引用的值,而是更新两个值,包括一个引用和一个版本号,即使这个值由A变为B,然后为变为A,版本号也是不同的。AtomicStampedReference和AtomicMarkableReference支持在两个变量上执行原子的条件更新。AtomicStampedReference更新一个“对象-引用”二元组,通过在引用上加上“版本号”,从而避免ABA问题,AtomicMarkableReference将更新一个“对象引用-布尔值”的二元组。
135.
享元设计模式
erlang语言
《Java Concurrency in Practice》
《Concurrent Programming in Java》
《The Java Language Specification》
136.
设计中要遵循的一条基本原则是:让简单的事情变得容易,让困难的事情变得可行。
137.
合理的假定Swing(就是一组易于使用的,易于理解的JavaBeans,能通过拖放操作,也可以手工编写,来合理的创建GUI程序)就是SUN的最终的Java GUI库。
138.
最小吃惊原则:别让用户感到惊讶。
139.
orthogonality of use
正交使用
140.
swt:standard widget toolkit
www.eclipse.org/downloads
141.

protected 是针对继承的。

142.

一个Java Swing小程序,可以参考练手,用到了windowBuilding Editor,拖拽预览,界面设计更加方便!

https://download.csdn.net/download/gzharryanonymous/10496481

未完待续,学无止境!


猜你喜欢

转载自blog.csdn.net/GZHarryAnonymous/article/details/80792810