第一章:分布式Java应用
线程是Java语言不可避免的特性,它把复杂,异步的代码转换为更简单,更直观的代码,从而简化复杂系统的开发。线程是控制和利用多核CPU计算能力的最简单的方式。
线程是系统调度器的基本单位。
线程在GUI应用程序中很重要,可以用来改进用户接口的响应性,并且在服务器应用中,用于提高资源的利用率和吞吐量。
网络通信:
* TCP/IP |
* UDP/IP |
* Multicast |
Io:
* BIO
* NIO
* AIO
分布式Java应用,两种典型发方法:
1.基于消息方式实现系统间的通信
2.基于远程调用方式实现系统间的通信
常用的实现系统间的通信协议有:TCP/IP & UDP/IP 协议。
TCP/IP协议:是一种可靠的网络传输协议,TCP/IP 要求双方首先建立连接,之后再进行数据传输。TCP/IP 负责保证数据传输的可靠性,包括数据的可到达,到达的顺序等。
缺点:TCP/IP 要保证连接和数据的传输可靠性,因此性能上差一些。
UDP/IP协议:是一种不保证数据一定到达的网络数据传输协议。UDP/IP 并不直接给通信的双方建立连接,而是发送到网络上进行传递。由于UDP/IP 不建立连接,并且不能保证数据传输的可靠,因此性能上要好些。
缺点:UDP/IP 不能保证数据到达的可靠性以及顺序可能会乱序。
TCP/IP & UDP/IP 协议 都可以用作数据的传输,但完成系统间的通信,需要对数据进行数据。例如读取和写入。按照POSIX 标准分为同步IO和异步IO两种,
同步IO : BIO (blocking io) 和 NIO (non-blocking io)。
从程序角度:BIO :当发起 IO 的读或写,均为阻塞方式,只有当程序读取到了流或将流写入操作系统后,才会释放资源。
基于事件驱动思想:NIO ,实现上通常采用reactor 模式。当发起IO 的读或写操作,是非阻塞的; 当socket 有流可读或可写入socket 时,操作系统会相应的通知应用程序进行处理,应用再将流读取到缓冲区或写入操作系统。
网络IO 操作动作主要有:连接建立,流读取和流写入三种事件。Linux 2.6以后采用epoll 实现NIO。
AIO 是异步IO 方式,基于事件驱动思想,实现上通常采用Proactor 模式。当进行读写操作时,只需要直接调用API 的 read 或 write
并发的原因:
1.公平性
2.便利性
3.资源利用率
线程也被称为轻量级的进程,线程是操作系统中的基本单位。
线程可以有效的降低程序的开发和维护成本,提升复杂应用程序的性能。
降低代码复杂度,使代码更容易编写,阅读和维护。
应用程序中,线程可以提高用户界面的响应灵敏度
服务器应用程序中,可以提升资源利用率以及系统吞吐率。
对程序员的技术要求高
2.安全性问题
3.活跃性问题:在开发并发代码时,一定要注意线程安全性是不可破坏的。
注意:线程还会导致一些在单线程程序中不会出现的问题。
4.性能问题:线程带来不同程度的运行时开销。在多线程程序中,当线程调度器临时挂起活跃线程并转而运行另一个线程时,就会频繁出现上下文切换操作,这种操作将会带来极大的开销;保存和恢复执行上下文,丢失局部性,并且CPU 时间将更多的花在线程调度而不是线程运行上。当线程共享数据时,必须使用同步机制。而这些机制通常会抑制某些编译器优化,使内存缓存区中的数据无效,以及增加共享内存总线的同步流量。
5.线程无处不在:程序中没有显示的创建线程,但框架中仍可能会创建线程。例如AWT(abstract window toolkit 抽象窗口工具库) 和swing 。servlet 和 RMI (remote method invocation),都会创建线程池并调用这些线程中的方法。
线程不安全实例:
Private int value;
Private int getValue(){{
Return value ++;
}
UnsafeSequence 类中说明的是一种常见的并发安全问题,称为竞态条件(race condition)。在多线程下,getValue是否返回唯一值,取决于运行时对线程中操作的交替执行方式。
由于多个线程要共享相同的地址空间,并且是并发运行,因此他们会访问或修改其他线程正在使用的变量。
线程安全实例:
Private int value;
Private synchronized int getValue(){{
Return value ++;
}
Timer ,timer 类的作用是使任务在稍后的时刻运行,或运行一次,或周期性的运行。引入timer 会使串行程序变得复杂,因为timertask 将在timer 管理的线程中执行,而不是由应用程序来管理。如果某个timertask 访问了应用程序中其他线程访问的数据,那么不仅timertask 需要以线程安全的方式来访问数据,其他类也必须采用线程安全的方式来访问该数据。
Servlet 和 JSP ,servlet 框架用于部署网页应用程序以及分发来自HTTP客户端的请求,到达服务器的请求可能会通过一个过滤器链被分发到正确的servlet 或JSP,每个servlet 都表示一个程序逻辑组件,在高吞吐率的网站中,多个客户端可能会同时请求同一个servlet 的服务,所以servlet 需要是线程安全的。
RMI 远程方法调用,使代码能够调用在其他JVM 中运行的对象,当通过调用某个远程方法时,传递的方法参数必须被打包(列集)到一个字节流中,通过网络传输给远程JVM,然后由远程JVM拆包并传递给远程方法。所以也必须线程安全。
第二章:线程安全性。
可变:变量的值在其生命周期内可以发生变化。
共享:多个线程可以同时访问。一个对象是否需要线程安全,取决于是否被多个线程访问。要使对象是线程安全的,需要采用同步机制来协同对对象可变状态的访问。
当多个线程访问某个状态变量并且其中一个线程执行写入操作时,必须采用同步机制来协同这些线程对变量的访问,Java 中主要同步机制是synchronized ,它提供了一种独占的加锁方式,还包括volatile 类型的变量,显式锁explicit lock 以及原子变量。
无状态对象一定是线程安全的。
原子性:
竞态条件 race condition :存在多个竞态条件,从而结果变得不可靠,当某个计算正确性取决于多个线程的交替执行时序是,那么就会发生竞态矫健。【比如发布和查看发布版本】,最常见的竞态条件就是先检查后执行:check-then -action。
不安全的:
private Singleton instance = null;
Public Singleton getInstance (){
if(instance == null){
Instance = new Singleton();
Return instance;
}
}
安全的:
public class Counting implements Servlet{
Private final AtomicLong value = new AtomicLong(0);
Public long getValue(){
Return value.get();
}
Public void service (ServletRequest req,ServletResponse resp){
Value.incrementAndGet();
}
}
内置锁:Java 提供了一种内置锁机制来支持原子性,同步代码块 synchronized block。
volatile 变量: Java 提供了一种稍弱的同步机制:volatile 变量,用来确保将变量的更新操作通知到其他线程。当把变量声明为volatile 类型后,编译器与运行时都会注意到这个变量是共享的。
在访问volatile 变量时不会执行加锁操作,因此就不会使执行线程阻塞,所以volatile 是比synchronized 更轻量级的同步机制。volatile 变量可见性对线程。
加锁机制既可以确保可见性又可以确保包原子性,而volatile 变量只能确保可见性。
volatile 使用场景:
1.对变量的写入操作不依赖变量的当前值,或者能够确保只有单个线程更新变量的值。
2.该变量不会与其他状态变量一起纳入不变性的条件中。
3.在访问变量时不需要加锁。