读书笔记: 大型网站系统与Java中间件实践(1)

1 分布式系统简介

本章主要对分布式系统做了一个大概的介绍,重点在于以下两个小节。

1.1 线程的执行模式

(1) 无需通信的多线程模式

线程之间不需要处理共享的数据,也不需要进行动作协调,这是最简单的多线程执行模式。各个线程独立处理自己的工作,不涉及线程安全。

(2) 基于共享容器协同的多线程模式

在一些场景中多个线程需要对共享数据进行处理,如经典的生产者-消费者模式。Producer生产的产品放在队列中,Consumer线程并发地访问这个队列进行消费。

对于这种在多线程环境下对同一份数据的访问,需要有保护机制以保证访问的正确性。

存储共享数据的容器或对象,有线程安全和不安全之分。对于线程不安全对象,可通过加锁方式来控制并发访问。对于线程安全的容器和对象(线程安全对象可参考博主的“线程安全对象简介”一文),可以在多线程环境下直接使用。

值得一提的是,并不是所有场景下使用线程安全容器就可以保证线程安全,要根据具体处理逻辑分析。举个例子,假设有多个线程对共享变量Map中的count做+1操作。如果使用synchronize关键字,那么没有什么问题。Code如下:

private static final Map<String, Integer> map = new HashMap<>();
//private static final ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();

public synchronized void add() {
    Integer value = map.get("count");
    if (value == null) {
        map.put("count", 1);
    } else {
        map.put("count", value + 1);
    }
}

@Test
public void testA() throws InterruptedException {
    for (int i = 0; i < 200; i++) {
        new Thread(this::add).start(); // java8的lambda表达式写法
    }
    Thread.sleep(2000); // 主线程sleep让子线程执行完
    System.out.println(map.get("count"));
}

如果不使用synchronize关键字,而是使用线程安全容器ConcurrentHashMap呢?
输出的最终结果并不是预期的200,说明使用ConcurrentHashMap是线程不安全的。原因在于add方法中的逻辑:先读后写,这不是一个原子操作。使用同步锁时,将整个add方法锁住了,相当于将add里面的逻辑变成了一个原子操作。而使用ConcurrentHashMap同时去掉synchronize之后,读和写就分离开了。假设初始值为0,A线程读取值,对该值加1,然后cpu时间给到线程B,线程B做同样的操作,并将1写入。然后线程A恢复,同样将1写入。预期结果是2,实际结果却是1。

这样看来,ConcurrentHashMap好像并没有起到线程安全的作用,那为什么说它是线程安全的呢?事实上,ConcurrentHashMap的线程安全是相对于HashMap来说的,它的每个方法单独调用(如put方法)是线程安全的(通过分段锁来保证原子操作),但是使用ConcurrentHashMap的业务代码的线程安全性是需要开发者自己来保证的。而HashMap之所以线程不安全,是由于其方法单独调用也是不安全的,如put方法,底层包含了很多其他逻辑,在执行put的底层逻辑时,随时会被其他线程打断。

(3)事件协同的多线程模式
除了对线程的并发访问的控制之外,还存在着协调的需求。如哲学家进餐问题,如果没有协调好,就会造成死锁。

1.2 网络通信基础

1.2.1 网络模型

ISO的OSI七层模型包括:物理层,数据链路层,网络层,传输层,会话层,持久层,应用层。
TCP/IP五层模型包括:物理层,数据链路层,网络层,传输层,应用层。这种划分平时接触得较多。

1.2.2 网络IO实现方式

网络通信模型主要可以分为三种:BIO、NIO、AIO。

BIO

BIO即Blocking IO,采用阻塞的方式实现。每个TCP连接进来服务端都要创建一个线程来建立连接并进行消息处理。如果中间发生了阻塞(如建立连接、读数据、写数据发生阻塞),线程就会一直阻塞等待。在并发量大时,就需要较多的线程来完成连接工作,消耗服务器资源。JDK1.0发布的java.io包中类都属于BIO。

NIO

NIO是JDK1.4发布的,非阻塞IO。NIO基于事件驱动的思想,采用的是Reactor模式。相较于BIO,NIO使用一个线程来管理所有的Socket通道,也就是使用Selector机制,当查询到事件时(连接,接受连接,读/写),就将事件转发给相应的处理线程(handler)。NIO原理如下图所示:
在这里插入图片描述

AIO

AIO(Asynchronous IO)是JDK1.7提出的,也就是异步IO。AIO采用Proactor模式,如下图所示:
在这里插入图片描述

与NIO不同,AIO需要一个连接注册读写事件和回调方法。当进行读写操作时,只要直接调用API的read或write方法即可,并立即返回。这两种方法都是异步的,真正的读写操作由系统来完成,并在完成之后再通知应用程序。此时用户进程只需对通知进行处理,不需要进行实际的IO操作,因为实际IO操作已经由系统完成了。

适用场景

BIO适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,简单易懂。

NIO适用于连接数目多且连接比较短的架构,并发局限于应用中,编程比较复杂,通常用封装后的框架,如Netty。

AIO适用于连接数据多且连接比较长的架构,比如相册服务器,充分调用OS参与并发操作,编程较为复杂,JDK7开始支持。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值