Maven 是 Apache 下的一个纯 Java 开发的项目管理工具,可以对 Java 项目进行构建、依赖管理。

约定配置

Maven 提倡使用一个共同的标准目录结构,Maven 使用约定优于配置的原则,大家尽可能的遵守这样的目录结构。如下所示:

目录 作用
${basedir} 存放pom.xml和所有的子目录
${basedir}/src/main/java 项目的java源代码
${basedir}/src/main/resources 项目的资源,比如说property文件,springmvc.xml
${basedir}/src/test/java 项目的测试类,比如说Junit代码
${basedir}/src/test/resources 测试用的资源
${basedir}/src/main/webapp/WEB-INF web应用文件目录,web项目的信息,比如存放web.xml、本地图片、jsp视图页面
${basedir}/target 打包输出目录
${basedir}/target/classes 编译输出目录
${basedir}/target/test-classes 测试编译输出目录
阅读全文 »

整个流程

  1. 客户端 http 请求服务器 80 端口,该端口被映射到 Nginx 容器 80 端口,进入 Nginx 处理。
  2. Nginx 分析请求,如果是静态资源,直接服务器读取内容;如果是 PHP 脚本,通过 PHP 容器调用服务器获取脚本,然后 FastCGI 处理。
  3. FastCGI 解析 PHP 脚本,必要时访问 MySQL 容器读写数据。
阅读全文 »

图解SpringMVC执行流程:

SpringMVC执行流程

SpringMVC执行流程:

  1. 用户发送请求至前端控制器DispatcherServlet
  2. DispatcherServlet收到请求调用处理器映射器HandlerMapping。
  3. 处理器映射器根据请求url找到具体的处理器,生成处理器执行链HandlerExecutionChain(包括处理器对象和处理器拦截器)一并返回给DispatcherServlet。
  4. DispatcherServlet根据处理器Handler获取处理器适配器HandlerAdapter执行HandlerAdapter处理一系列的操作,如:参数封装,数据格式转换,数据验证等操作
  5. 执行处理器Handler(Controller,也叫页面控制器)。
  6. Handler执行完成返回ModelAndView
  7. HandlerAdapter将Handler执行结果ModelAndView返回到DispatcherServlet
  8. DispatcherServlet将ModelAndView传给ViewReslover视图解析器
  9. ViewReslover解析后返回具体View
  10. DispatcherServlet对View进行渲染视图(即将模型数据model填充至视图中)。
  11. DispatcherServlet响应用户。

前言

在Java对象的创建时,单例模式使用尤其多,同时也是个面试必问的基础题。很多时候面试官想问的无非是懒汉式的双重检验锁。但是其实还有两种更加直观高效的写法,也是《Effective Java》中所推荐的写法。

静态内部类(static nested class)

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton {

private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}

private Singleton() {}

public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}

}

这种写法仍然使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。

阅读全文 »

作用

  1. Semaphore就是一个信号量,它的作用是限制某段代码块的并发数。
  2. Semaphore有一个构造函数,可以传入一个int型整数n,表示某段代码最多只有n个线程可以访问
  3. 如果超出了n,那么请等待,等到某个线程执行完毕这段代码块,下一个线程再进入。
  4. 由此可以看出如果Semaphore构造函数中传入的int型整数n=1,相当于变成了一个synchronized了。

Semaphore类位于java.util.concurrent包下,它提供了2个构造器:

1
2
3
4
5
6
7
8
//参数permits表示许可数目,即同时可以允许多少线程进行访问
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
//这个多了一个参数fair表示是否是公平的,即等待时间越久的越先获取许可
public Semaphore(int permits, boolean fair) {
sync = (fair)? new FairSync(permits) : new NonfairSync(permits);
}

Semaphore类中比较重要的几个方法,首先是acquire()、release()方法:

  1. acquire()用来获取一个许可,若无许可能够获得,则会一直等待,直到获得许可。
  2. release()用来释放许可。注意,在释放许可之前,必须先获获得许可。

acquire()方法会被阻塞,如果想立即得到执行结果,可以使用下面几个方法:

1
2
3
4
5
6
7
8
9
10
//尝试获取一个许可,若获取成功,则立即返回true,若获取失败,则立即返回false
public boolean tryAcquire() { };
//尝试获取一个许可,若在指定的时间内获取成功,则立即返回true,否则则立即返回false
public boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException { };
//尝试获取permits个许可,若获取成功,则立即返回true,若获取失败,则立即返回false
public boolean tryAcquire(int permits) { };
//尝试获取permits个许可,若在指定的时间内获取成功,则立即返回true
public boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException { };
//得到当前可用的许可数目
public int availablePermits();
阅读全文 »

线程池的优点

  1. 线程是稀缺资源,使用线程池可以减少创建和销毁线程的次数,每个工作线程都可以重复使用。

  2. 可以根据系统的承受能力,调整线程池中工作线程的数量,防止因为消耗过多内存导致服务器崩溃。

线程池的原理

当我们把一个Runnable交给线程池去执行的时候,这个线程池处理的流程是这样的:

  1. 当线程数小于corePoolSize时,创建线程执行任务。
  2. 当线程数大于等于corePoolSize并且workQueue没有满时,放入workQueue中
  3. 线程数大于等于corePoolSize并且当workQueue满时,新任务新建线程运行,线程总数要小于maximumPoolSize
  4. 当线程总数等于maximumPoolSize并且workQueue满了的时候执行handler的rejectedExecution。也就是拒绝策略。

任务拒接策略(RejectedExecutionHandler)

当队列和线程池都满了的时候,再有新的任务到达,就必须要有一种办法来处理新来的任务。Java线程池中提供了以下四种策略:

  1. AbortPolicy: 直接抛异常
  2. CallerRunsPolicy:让调用者帮着跑这个任务
  3. DiscardOldestPolicy:会抛弃任务队列中最旧的任务,再把这个新任务添加进去。
  4. DiscardPolicy:不处理,直接扔掉
阅读全文 »

概述

BIO是面向字节流和字符流的,数据从流中顺序获取

NIO是面向通道和缓冲区的,数据总是从通道中读到buffer缓冲区内,或者从buffer缓冲区内写入通道

Channel通道和Buffer缓冲区是NIO的核心,几乎在每一个IO操作中使用它们,Selector选择器则允许单个线程操作多个通道,对于高并发多连接很有帮助。

操作系统的IO一般分为两个阶段,等待和就绪操作,比如读可以分为等待系统可读和真正的读,写可以分为等待系统可写和真正的写,在传统的BIO中是这两个阶段都会阻塞,在NIO中第一个阶段不是阻塞的,第二个阶段是阻塞的,如下图,BIO是阻塞IO,NIO是非阻塞IO

阅读全文 »

Happens-before 关系

happens-before 关系保证:如果线程 A 与线程 B 满足 happens-before 关系,则线程 A 执行动作的结果对于线程 B 是可见的。如果两个操作未按 happens-before 排序,JVM 将可以对他们任意重排序。

下面介绍几个与理解 ConcurrentHashMap 有关的 happens-before 关系法则:

  1. 程序次序法则:如果在程序中,所有动作 A 出现在动作 B 之前,则线程中的每动作 A 都 happens-before 于该线程中的每一个动作 B。
  2. 监视器锁法则:对一个监视器的解锁 happens-before 于每个后续对同一监视器的加锁。
  3. Volatile 变量法则:对 Volatile 域的写入操作 happens-before 于每个后续对同一 Volatile 的读操作。
  4. 传递性:如果 A happens-before 于 B,且 B happens-before C,则 A happens-before C。

Java 7基于分段锁的ConcurrentHashMap

数据结构

Java 7中的ConcurrentHashMap 类中包含两个静态内部类 HashEntry 和 Segment。HashEntry 用来封装映射表的键 / 值对;Segment 用来充当锁的角色,每个 Segment 对象守护整个散列映射表的若干个桶。每个桶是由若干个 HashEntry 对象链接起来的链表。一个 ConcurrentHashMap 实例中包含由若干个 Segment 对象组成的数组。

  1. 最大的分段(segment)数为2的16次方,每一个segment的HashEntry[]的最大容量为2的30次方。
  2. 默认的分段数和每个segment的HashEntry[]的初始容量均为16。segment的默认加载因子为0.75。
  3. 定位segment段需要用的两个参数:segmentMask,segmentShift。

整体数据结构如下图所示:

java7数据结构

阅读全文 »

什么是线程

一个线程是进程的一个顺序执行流。同类的多个线程共享一块内存空间和一组系统资源,线程本身有一个供程序执行时的堆栈。线程在切换时负荷小,因此,线程也被称为轻负荷进程。一个进程中可以包含多个线程。

进程与线程的区别

一个进程至少有一个线程。线程的划分尺度小于进程,使得多线程程序的并发性高。另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。

线程在执行过程中与进程的区别在于每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用来实现进程的调度和管理以及资源分配。

并发原理

多个线程或进程”同时”运行只是我们感官上的一种表现。事实上进程和线程是并发运行的,OS的线程调度机制将时间划分为很多时间片段(时间片),尽可能均匀分配给正在运行的程序,获取CPU时间片的线程或进程得以被执行,其他则等待。而CPU则在这些进程或线程上来回切换运行。微观上所有进程和线程是走走停停的,宏观上都在运行,这种都运行的现象叫并发,但是不是绝对意义上的“同时发生。

阅读全文 »

摘要

HashMap是Java程序员使用频率最高的用于映射(键值对)处理的数据类型。随着JDK(Java Developmet Kit)版本的更新,JDK1.8对HashMap底层的实现进行了优化,例如引入红黑树的数据结构和扩容的优化等。本文结合JDK1.7和JDK1.8的区别,深入探讨HashMap的结构实现和功能原理。

简介

Java为数据结构中的映射定义了一个接口java.util.Map,此接口主要有四个常用的实现类,分别是HashMap、Hashtable、LinkedHashMap和TreeMap,类继承关系如下图所示:

类继承关系

下面针对各个实现类的特点做一些说明:

  1. HashMap:它根据键的hashCode值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的。 HashMap最多只允许一条记录的键为null,允许多条记录的值为null。HashMap非线程安全,即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致。如果需要满足线程安全,可以用 Collections的synchronizedMap方法使HashMap具有线程安全的能力,或者使用ConcurrentHashMap。

  2. Hashtable:Hashtable是遗留类,很多映射的常用功能与HashMap类似,不同的是它承自Dictionary类,并且是线程安全的,任一时间只有一个线程能写Hashtable,并发性不如ConcurrentHashMap,因为ConcurrentHashMap引入了分段锁。Hashtable不建议在新代码中使用,不需要线程安全的场合可以用HashMap替换,需要线程安全的场合可以用ConcurrentHashMap替换。

  3. LinkedHashMap:LinkedHashMap是HashMap和双向链表合二为一的HashMap一个子类,保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的,也可以在构造时带参数,按照访问次序排序。

  4. TreeMap:TreeMap实现SortedMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator遍历TreeMap时,得到的记录是排过序的。如果使用排序的映射,建议使用TreeMap。在使用TreeMap时,key必须实现Comparable接口或者在构造TreeMap传入自定义的Comparator,否则会在运行时抛出java.lang.ClassCastException类型的异常。

对于上述四种Map类型的类,要求映射中的key是不可变对象。不可变对象是该对象在创建后它的哈希值不会被改变。如果对象的哈希值发生变化,Map对象很可能就定位不到映射的位置了。

通过上面的比较,我们知道了HashMap是Java的Map家族中一个普通成员,鉴于它可以满足大多数场景的使用条件,所以是使用频度最高的一个。下文我们主要结合源码,从存储结构、常用方法分析、扩容以及安全性等方面深入讲解HashMap的工作原理。

阅读全文 »
0%