一、阻塞非阻塞
1.1 同步阻塞IO
在Java应用中,默认情况下所有的Socket连接的IO都是同步阻塞的
例如在java发起的一个socket的read操作大致流程如下:
1.Java启动io的read读操作开始,用户线程就进入阻塞状态
2.系统接收到read调用,开始准备数据,如果内核数据没有准备好,则内核进行等待。
3.内核一直等待完整的数据到达,就会将数据从内核缓冲区复制到用户缓冲区,返回内核结果.
4.内核返会结果后,用户线程才会解除阻塞的状态,重新运行。
特点:在内核执行IO两个点阶段,用户线程都被阻塞了。
优点:开发程序简单,阻塞期间,用户线程不占用CPU。
缺点:每个连接配备独立的线程,高并发场景下,需要大量的线程维护网络连接,内存,线程切换开销会非常大。
1.2 同步非阻塞NIO
1.内核中没有数据,系统会直接返回调用失败的结果,用户线程需要不断地发起IO调用。
2.内核中有数据,发生阻塞,知道数据从内核中复制到用户线程中,复制完成后,系统调用返回成功,解除阻塞状态,用户空间进行处理。
特点:用户线程需要不断的轮询IO系统调用,轮询数据是否已经准备好。
优点:每次发起IO调用,可以立马反悔一个结果,用户线程不会阻塞,实时性较好。
缺点:不断轮询,占用CPU,效率低下。
二、同步、异步
同步方法调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为。
异步方法调用更像一个消息传递,一旦开始,方法调用就会立即返回,调用者就可以继续后续的操作。而,异步方法通常会在另外一个线程中,“真实”地执行着。整个过程,不会阻碍调用者的工作.
三、FutureTask结合Callable接口创建线程【异步】
Future是JDK用于处理多线程环境下异步问题的一种模式,FutureTask就是Future的一种实现。
他最大的好处就是:客户端发出请求后,可以立马得到一个返回结果,而不用一直等待这服务器来处理。
在Callable接口中,call()方法有一个类型的泛型返回值,返回值可以是任意类型,就可以通过FutureTask的get()方法来接受返回值。get方法是一个闭锁式的阻塞方法,此方法会一直等待,知道call方法执行完毕return返回值为止。
例如:Callable创建线程计算 1-100之和并返回个Main线程的sum变量:
public class CallableTest {
public static void main(String[] args) {
MyCallableThread thread = new MyCallableThread();
FutureTask<Integer> task = new FutureTask<>(thread);
new Thread(task).start();
try {
Integer sum = task.get();
System.out.println(sum);
}catch (Exception e){
}
}
}
class MyCallableThread implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("线程开始计算 1 + ... +100");
int sum = 0;
for (int i = 1; i <=100; i++) {
sum =sum + i;
}
return sum;
}
}