1、线程阻塞的概念
处于阻塞状态的线程的共同特征是: 放弃CPU, 暂停运行, 只有等到导致阻塞的原因消除, 才能恢复运行; 或者被其他线程中断, 该线程会退出阻塞状态, 并且抛出 InterruptedException.

1.1 线程阻塞的原因

1) 线程执行了 Thread.sleep(int n) 方法,线程放弃 CPU,睡眠 n 毫秒,然后恢复运行。

2) 线程要执行一段同步代码, 由于无法获得相关的同步锁, 只好进入阻塞状态, 等到获得了同步锁, 才能恢复运行.

3) 线程执行了一个对象的 wait() 方法, 进入阻塞状态, 只有等到其他线程执行了该对象的 notify() 和 notifyAll() 方法, 才可能将其呼醒.

4) 线程执行 I/O 操作或进行远程通信时, 会因为等待相关的资源而进入阻塞状态. 例如, 当线程执行 System.in.read() 方法时, 如果用户没有向控制台输入数据, 则该线程会一直等读到了用户的输入数据才从 read() 方法返回.

进行远程通信时, 在客户程序中, 线程在以下情况可能进入阻塞状态.

1) 请求与服务器建立连接时, 即当线程执行 Socket 的带参数构造方法, 或执行 Socket 的 connect() 方法时, 会进入阻塞状态, 直到连接成功, 此线程才从 Socket 的构造方法或 connect() 方法返回.

2) 线程从 Socket 的输入流读入数据时, 如果没有足够的数据, 就会进入阻塞状态, 直到读到了足够的数据,或者到达输入流的末尾, 或者出现了异常, 才从输入流的 read() 方法返回或异常中断. 输入流中有多少数据才算足够呢? 这要看线程执行的 read() 方法的类型.

> int read(): 只要输入流中有一个字节, 就算足够.

> int read( byte[] buff): 只要输入流中的字节数目与参数buff 数组的长度相同, 就算足够.

> String readLine(): 只要输入流中有一行字符串, 就算足够. 值得注意的是, InputStream 类并没有 readLine() 方法, 在过滤流 BufferedReader 类中才有此方法.

3) 线程向 Socket 的输出流写一批数据时, 可能会进入阻塞状态, 等到输出了所有的数据, 或者出现异常, 才从输出流 的 write() 方法返回或异常中断.

4) 调用 Socket 的setSoLinger() 方法设置了关闭 Socket 的延迟时间, 那么当线程执行 Socket 的 close() 方法时, 会进入阻塞状态, 直到底层 Socket 发送完所有剩余数据, 或者超过了 setSoLinger() 方法设置的延迟时间, 才从 close() 方法返回.

服务器程序中, 线程在以下情况下可能会进入阻塞状态.

1) 线程执行 ServerSocket 的 accept() 方法, 等待客户的连接, 直到接收到了客户连接, 才从 accept() 方法返回.

2) 线程从 Socket 的输入流读入数据时, 如果输入流没有足够的数据, 就会进入阻塞状态.

3) 线程向 Socket 的输出流写一批数据时, 可能会进入阻塞状态, 等到输出了所有的数据, 或者出现异常, 才从输出流的 write() 方法返回或异常中断.

由此可见, 无论在服务器程序还是客户程序中, 当通过 Socket 的输入流和输出流来读写数据时, 都可能进入阻塞状态. 这种可能出现阻塞的输入和输出操作被称为阻塞 I/O. 与此对照, 如果执行输入和输出操作时, 不会发生阻塞, 则称为非阻塞 I/O.

——————————————————–

服务器程序用多线程来处理阻塞I/O,尽管能满足同时响应多个客户的需求,但是有以下局限:

(1)Java虚拟机会为每个线程分配独立的堆栈空间,工作线程数目越多,系统开销就越大,而且增加了Java虚拟机调度线程的负担,增加了线程之间同步的复杂性,提高了线程死锁的可能性;

(2)工作线程的许多时间都浪费在阻塞I/O操作上,Java虚拟机需要频繁地转让CPU的使用权,使进入阻塞状态的线程放弃CPU,再把CPU分配给处于可运行状态的线程。