Java编程思想
2019-10-08 11:28:25 0 举报
AI智能生成
《Java编程思想》思维导图
作者其他创作
大纲/内容
内部类
为什么需要内部类?
每个内部类都来继承自一个接口的实现
使得多重继承变得完整
闭包与回调
模板方法模式
泛型
Tuple(元组),仅一次方法调用可返回多个对象
Sentinel(末端哨兵)
泛型方法
枚举
Java SE5
职责链模式
状态机
多路分发
调用时的优雅语法,避免了在一个方法中判定多个对象的类型的丑陋代码
难以理解的代码会导致整个系统的不健壮
EnumMap
注解
Java SE5
并发
基本概念
并发通常是提高运行在单处理器上的程序的性能
某些编程语言(erlang)被设计为可以将并发任务彼此隔离,这些语言通常被称为函数型语言
协作多线程
每个任务都会自动地放弃控制
上下文切换的开销通常比抢占式系统低廉
对可以同时执行的线程数量在理论上没有任何限制
Java的线程机制是抢占式的,调度机制会周期性的中断线程,将上下文切换到另一个线程
基本的线程机制
静态方法Thread.yield()是对线程调度器的一种建议,它在声明:
后台线程(daemon),当所有非后台线程结束时,程序终止,同时会杀死所有后台线程。
反过来说,只要有任何非后台线程还在运行,程序就不会终止。
反过来说,只要有任何非后台线程还在运行,程序就不会终止。
一个线程可以在其他线程之上调用join()方法,其效果是等待一段时间直到第二个线程结束才继续执行,即t.isAlive()返回假。
Java SE5的java.util.concurrent类库包含诸如CycleBarrier这样的工具,它们可能比最初的线程类库中的join()更加合适。
Thread.setUncaughtExceptionHandler(),线程异常处理程序
共享资源竞争
基本上所有的并发模式在解决线程冲突问题的时候,都是采用序列化访问共享资源的方案
因为锁语句产生了一种互斥的效果,这种机制常常称为互斥量(mutex)
所有对象都自动含有单一的锁,也称为监视器
对于某个对象来说,其所有synchronized方法共享同一个锁
一个任务可以多次获得对象的锁,JVM负责咔嚓对象被加锁的次数
针对每个类,也有一个锁(作为类的Class对象的一部分),所以synchronized static方法可以在类的范围内防止对static数据的并发访问
如果你正在写一个变量,它可能接下来将被另一个线程读取,或者正在读取一个上一次已经被 另一个线程写过的变量,
那么你必须使用同步,并且,读写线程都必须用相同的监视器锁同步
那么你必须使用同步,并且,读写线程都必须用相同的监视器锁同步
Java SE5的java.util.concurrent类库还包含有定义在java.util.concurrent.locks中的显式的互斥机制,Lock对象必须被显式的创建、锁定和释放
显示的Lock对象在加锁和释放锁方面,相对于内建的synchronized锁来说,还赋予了你更细粒度的控制力,这对于实现专有数据结构是很有用的。
例如用于遍历链表节点的节点传递加锁机制(锁耦合),这种遍历代码必须在释放当前节点的锁之前捕获下一个节点的锁。
例如用于遍历链表节点的节点传递加锁机制(锁耦合),这种遍历代码必须在释放当前节点的锁之前捕获下一个节点的锁。
volatile
如果多个任务在同时访问某个域,那么这个域就应该是volatile的,否则这个域就应该只能经由同步来访问
如果一个域完全由synchronized方法或语句块来防护,那么就不必将其设置为volatile
使用volatile而不是synchronized的唯一安全的情况是类中只有一个可变的域,你的第一选择应该是使用synchronized关键字,这是最安全的方式,尝试其他任何方式都是有风险的
如果一个域可能会被多个任务同时访问,或者这些任务中至少一个是写入任务,那么你就应该将这个域设置为volatile的
如果你将一个域定义为volatile,那么它会告诉编译器不要执行任何移除读取和写入操作的优化,这些操作的目的是用线程中的局部变量维护对这个域的精确同步。
读取和写入都是针对内存的,而没有被缓存
volatile并不能对递增而不是原子性操作这一事实产生影响
基本类型的自增自减不是原子性操作,因为在jvm内存模型中,分为get,put两步操作
原子类
Java SE5引入
AtomicInteger, AtomicLong等,被 调整为可以使用在某些现代处理器上的可获得的,并且是机器级别的原子性
临界区(critical section)
希望防止多个线程同时访问内部的部分代码而不是防止访问整个方法
对象锁
同步控制块
线程本地存储
为使用相同变量的每个不同线程创建不同的存储
终结任务
线程的状态
新建(New)
就绪(Runnable)
阻塞(Blocked)
死亡(Dead)
线程的suspend()和resume()被废止了,因为可能导致死锁
stop方法也被废止了,因为它不释放线程获得的锁,并且如果线程处于不一致的状态(受损状态),其他任务可以在这种状态下浏览并修改它们
在Executor上调用shutdownNow(),那么它将发送一个interrupt()调用给它启动的所有线程
shutdown(),不再接收新任务,已有任务继续执行,通常跟 awaitTermitation()配合
shutdown(),不再接收新任务,已有任务继续执行,通常跟 awaitTermitation()配合
如果一个线程已经被被阻塞,或者试图执行一个阻塞操作,那么设置这个线程的中断状态将抛出InterruptedException
调用submit()而不是execute(),就可以获取线程的上下文,会获得一个泛型的Future<?>对象
可以在其之上调用cancel(true),那么它应付拥有在该线程上调用interrupte()以停止这个线程的权限
cancel()是一种中断由Executor启动的单个线程的方式
可以在其之上调用cancel(true),那么它应付拥有在该线程上调用interrupte()以停止这个线程的权限
cancel()是一种中断由Executor启动的单个线程的方式
你能够中断对sleep()的调用,或者任何要求抛出InterruptedException的调用,但是,你不能中断正在试图获取synchronized锁或者执行I/O操作的线程
nio提供了更个性化的I/O中断,被阻塞的nio通道会自动地响应中断
Java SE5并发类库中添加了一个特性,即在ReentrantLock上阻塞的任务具备可以被中断的能力,这与synchronized的阻塞不同
线程之间的协作
wait()与notifyAll()
不断的进行空循环(忙等待),通常是一个不良的CPU周期使用方式
wait()会在等待外部条件发生变化的时候将任务挂起
只有在notify()或者notifyAll()发生时,才会被唤醒并去检查所产生的变化
wait()提供了一种在任务之间对活动同步的方式
调用sleep()的时候锁并没有释放,调用yield()时也不会释放,但wait()将释放锁
只能在同步控制方法或同步控制块中调用 wait(), notify()和notifyAll()
当你调用wait()时,线程被挂起,而锁被释放
使用while循环包围wait()的意义,就是要检查所感兴趣的条件,并在条件不满足的情况下返回到wait()中
信号的错失导致死锁
notify()与notifyAll()
调用notifyAll()比调用notify()更安全
使用notify()是一种优化,但你必需保证被唤醒的是恰当的线程
与使用 nofityAll()相比,signalAll()是更安全的方式
Java SE5的java.util.concurrent类库中还有额外的显式工具:Lock, Condition
wait()和notify()方法以一种非常低级的方式解决了任务互操作的问题,即每次交互时都握手。在许多情况下,你可以瞄向更高的抽象级别,使用同步队列来解决任务协作问题,同步队列在任何时刻只允许一个任务插入和移除元素: BlockingQueue。
如果消费者任务试图从队列中获取对象,而该队列此时为空,那么这些队列还可以挂起消费者任务,当有更多的元素可用时恢复消费者任务。
因为队列的阻塞,使得任务的处理过程将被自由的挂起和恢复,由BlockingQueue产生的简化十分明显,在使用显式的wait()和notify()时存在的类和类之间的耦合被消除了,因为每个类都只和它的BlockingQueue通信。
死锁
Philosipher和Chopsticks的死锁问题
发生死锁的条件
互斥条件
至少有一个任务它必须持有一个资源且正在等待获取一个当前被别的任务持有的资源
资源不能被任务抢占
必有有循环等待
新类库的构件
CountDownLatch
CyclicBarrier
赛马小程序
数组
System.arrayCopy(),比for循环复制要快
排序
针对基本数据类型,使用快速排序
针对对象类型,使用稳定的归并排序
容器
容器填充
享元模式
Set和顺序存储
你必须为散列存储和树型存储都创建一个equals()方法,
但是 hashCode() 只有在这个类将会被置于 HashSet或者LinkedHashSet中时才是必须的。
但是对于良好的编程风格而言,你应该在覆盖equals()方法时,总是同时覆盖hashCode()。
但是 hashCode() 只有在这个类将会被置于 HashSet或者LinkedHashSet中时才是必须的。
但是对于良好的编程风格而言,你应该在覆盖equals()方法时,总是同时覆盖hashCode()。
HashSet以某种神秘的顺序保存所以元素
队列
PriorityQueue(优先级队列)
通常会在插入时排序,但也可能会在移除时选择最重要的元素
如果对象的优先级可以在队列中修改,那么算法的选择就很重要
Deque(双向队列,双端队列)
JDK1.6新增Deque接口
Map
映射表,也称关联数组
基本Map实现
HashMap
LinkedHashMap
TreeMap
WeakHashMap
ConcurrentHashMap
iIdentityHashMap
使用==代替equals()对键进行比较的散列映射。为解决特殊问题而设计。
散列与散列码
正确的equals()方法
自反性:对任意x, x.equals(x)一定返回true
对称性:对任意x和y,如果y.equals(x)返回true,则x.equals(y)也返回true
传递性:对任意x、y、z,如果x.equals(y)返回true,y.equals(z)返回true,则x.equals(z)一定返回true
一致性:对任意x和y,如果对象中用于等价比较的信息没有改变,那么无论调用x.equals(y)多少次,返回结果应该保持一致。
对任何非null的x,x.equals(null)一定返回false.
Object.hashCode()使用对象的地址计算散列码,Object.equals()只是比较对象的地址。
散列表中的槽位(slot)通常称为桶位(bucket),对象hashCode()生成的结果,经过处理后成为桶的下标。
List
迭代
ListIterator,允许反向遍历,允许遍历的过程中修改list,获取元素当前位置
最佳的做法是将ArrayList作为默认首选,只有你需要使用额外功能,或者程序性能经常从表中间插入和删除变差时,才去选择LinkedList。
如果使用的是固定数量的元素,那么既可以选择使用背后有数组支撑的List,也可以选择真正的数组。
如果使用的是固定数量的元素,那么既可以选择使用背后有数组支撑的List,也可以选择真正的数组。
持有引用
SoftReference
WeakReference
PhantomReference
收藏
0 条评论
下一页