《Java编程思想》第18章——Java I/O系统
- File类既能代表一个特定文件的名称,又能代表一个目录下的一组文件的名称。如果它指的是一个文件集,我们就可以对此集合调用list()方法,这个方法会返回一个字符数组。
- 通过继承,任何自InputStream或Reader派生而来的类都含有名为read()的基本方法,用于读取单个字节或者字节数组。同样,任何字OuputStream或Writer派生而来的类都含有名为write()的基本方法,用于写单个字节或自己数组。但是我们不会用到这些方法,它们之所以存在是因为别的类可以使用它们,以便提供更有用的接口。因此,我们很少使用单一的类来创建流对象,而是通过叠合多个对象来提供所期望的功能。
- 实际上,Java中“流”类库让人迷惑的主要原因就在于:创建单一的结果流,却需要创建多个对象。
- System.setIn(InputStream)可以对标准输入重定向,System.setOut(PrintStream),System.setErr(PrintStream)是对标准输出和错误I/O流进行重定向。
- 要序列化一个对象,首先要创建某些OutputStream对象,然后将其封装在一个ObjectOutputStream对象内。这时,只需要调用writeObject()即可将对象序列化,并将其发送给OutputStream(对象序列化是基于字节的,因此要使用InputStream和OutputStream继承层次结构)。要反向进行该过程(即将一个序列还原为一个对象),需要将一个InputStream封装在ObjectInputStream内,然后调用readObject()。我们最后获得的是一个引用,它指向一个向上转型的Object,所以必须向下转型才能直接设置它们。
《Java编程思想》第19章——枚举类型
- 如果你打算定义自己的方法,那么必须在enum实例序列的最后添加一个分号。同时,Java要求你必须先定义enum实例。如果在定义enum实例之前定义了任何方法或属性,那么在编译时就会得到错误信息。
- 虽然一般情况下我们必须使用enum类型来修饰一个enum实例,但是在case语句中却不必如此。
《Java编程思想》第20章——注解
- @Override,表示当前的方法定义将覆盖超类中的方法。如果你不小心拼写错误,或者方法签名对不上被覆盖的方法,编译器就会发出错误提示。
- @Deprecated,如果程序员使用了注解为它的元素,那么编译器会发出告警信息。
- @SuppressWarnings,关闭不当的编译信息警告信息。在Java SE5之前的版本中,也可以使用该注解,不过会被忽略不起作用。
《Java编程思想》第21章——并发
- 事实上,从性能的角度看,如果没有任务会阻塞,那么在单处理器机器上使用并发就没有任何意义。
- 像Java所使用的这种并发系统会共享诸如内存和I/O这样的资源,因此编写多线程程序最基本的困难在于协调不同线程驱动的任务之间对这些资源的使用,以使得这些资源不会同时被多个任务访问。
- Java的线程机制是抢占式的,这表示调度机制会周期性地中断线程,将上下文切换到另一个线程,从而为每个线程都提供时间片,使得每个线程都会分配到数量合理的时间片去驱动他的任务。
- 一个线程其实就是在进程中的一个单元的顺序控制流。
- 要定义一个任务,只需实现Runnable接口并编写run()方法,使得该任务可以执行你的命令。
- 任务的run()方法通常总会有某种形式的循环,使得任务一直运行下去直到不再需要,所以要设定跳出循环的条件。
- Thread构造器只需要一个Runnable对象。调用Thread对象的start()方法为该线程执行必需的初始化操作,然后调用Runnable的run()方法,以便在这个新线程中启动该任务。
- 如果在你的机器上有多个处理器,线程调度器将会在这个处理器之间默默地分发线程。同时,线程调度机制是非确定的。
- Executor允许你管理异步任务的运行,再无须显示地管理线程的生命周期。Executor在Java SE5/6中是启动任务的优选方法。
- Runnable()是执行工作的独立任务,但是它不返回任何值。如果你希望任务在完成时能够返回一个值,那么可以实现Callable接口而不是Runnable接口。
- 因为异常不能跨线程传播回main(),所以你必须在本地处理所有在任务内部产生的异常。
- 调度器将倾向于让优先权最高的线程先执行。然而,这并不是意味着优先权较低的线程将得不到执行(也就是说,优先权不会导致死锁)。优先级较低的线程仅仅是执行的频率较低。
- 你可以在一个任务的内部,通过调用Thread.currentThread()来获得对驱动该任务的Thread对象的引用。通过Thread.currentThread().setPriority()来修改当前线程的优先级。
- 注意,优先级是在run()的开头部分设定的,在构造器中设置它们不会有任何好处,因为Executor在此刻还没有开始执行任务。
- 所谓后台(daemon)线程,是指在程序运行的时候在后台提供一种通用服务的线程,并且这种线程并不属于程序中不可或缺的部分。
- 必须在线程启动(调用start())之前调用setDaemon()方法,才能把它设置为后台线程。
- 如果是一个后台线程,那么它创建的任何线程将被自动设置成后台线程。
- 线程组持有一个线程的集合。但是,最好把线程组看成一次不成功的尝试,你只要忽略它就好了。
- Java以提供关键字synchronized的形式,为防止资源冲突提供了内置支持。当任务要执行synchronized关键字保护的代码片段时,它将检查锁是否可用,然后获取锁,执行代码,释放锁。
- 所有对象都自动含有单一的锁(也称为监视器)。当在对象上调用其任意synchronized方法的时候,此对象都被加锁,这时该对象上的其他synchronized方法只有等到前一个方法调用完毕并释放了锁之后才能被调用。
- 注意,在使用并发时,将域设置为private是非常重要的,否则,synchronized关键字就不能防止其他任务直接访问域,这样就会产生冲突。
- 每个访问临界共享资源的方法都必须被同步,否则它们就不会正确的工作。
- 大体上,当你使用synchronized关键字时,需要写的代码量更少,并且用户错误出现的可能性也会降低,因此通常只有在解决特殊问题时,才使用显示的Lock对象。
- 理解原子性和易变性是不同的概念这一点很重要。在非volatile域上的原子操作不必刷新到主内存中去,因此其他读取该域的任务也不必看到这个新值。如果多个任务在同事访问某个域,那么这个域就应该是volatile的,否则,这个域就应该只能经由同步来访问。同步也会导致向主内存中刷新,因此如果一个域完全由synchronized方法或语句块来访问,那就不必将其设置为是volatile的。
- 使用volatile而不是synchronized的唯一安全的情况是类中只有一个可变的域。再次提醒,你的第一个选择应该是使用synchronized关键字,这是最安全的方式,而尝试其他任何方式都是有风险的。
- 有时,你只是希望防止多个线程同时访问方法内部的部分代码,而不是防止访问整个方法。通过这种方式分离出来的代码段被称为临界区,它也使用synchronized关键字建立,这里,synchronized被用来指定某个对象,此对象的锁被用来对花括号内的代码进行同步控制。这也称为同步控制块。
- synchronized块必须给定一个在其上进行同步的对象,并且最合理的方式是,使用其方法正在被调用的当前对象:synchronized(this)。
- 当你调用wait()方法时,就是在声明:“我已经刚刚做完能做的所有事情,因此我要在这里等待,但是我希望其他的synchronized操作在条件适合的情况下能够执行”。
- 实际上,只能在同步控制方法或同步控制块里调用wait()、notify()和notifyAll()。
- 事实上,当notifyAll()因某个特定锁而被调用时,只有等待这个锁的任务才会被唤醒。
- LinkedBlockingQueue和ArrayBlockingQueue可用于同步。
- Java对死锁并没有提供语言层面上的支持;能否通过仔细地设计程序来避免死锁,这取决于你自己。
- ConcurrentHashMap和ConcurrentLinkedQueue允许并发的读取和写入。
- 从JDK1.5开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发容器,它们是CopyOnWriteArrayList和CopyOnWriteArraySet。CopyOnWrite容器非常有用,可以在非常多的并发场景中使用到。CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。(来源——网络博客)