进程
我们都知道计算机的核心是CPU,它承担了所有的计算任务,而操作系统是计算机的管理者,它负责任务的调度,资源的分配和管理,统领整个计算机硬件;应用程序是具有某种功能的程序,程序是运行于操作系统之上的。
进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。进程是一种抽象的概念,从来没有统一的标准定义。进程一般由程序,数据集合和进程控制块三部分组成。程序用于描述进程要完成的功能,是控制进程执行的指令集;数据集合是程序在执行时所需要的数据和工作区;程序控制块包含进程的描述信息和控制信息,是进程存在的唯一标志
进程具有的特征:
动态性:进程是程序的一次执行过程,是临时的,有生命期的,是动态产生,动态消亡的;
并发性:任何进程都可以同其他进行一起并发执行;
独立性:进程是系统进行资源分配和调度的一个独立单位;
结构性:进程由程序,数据和进程控制块三部分组成
线程
在早期的操作系统中并没有线程的概念,进程是拥有资源和独立运行的最小单位,也是程序执行的最小单位。任务调度采用的是时间片轮转的抢占式调度方式,而进程是任务调度的最小单位,每个进程有各自独立的一块内存,使得各个进程之间内存地址相互隔离。
后来,随着计算机的发展,对CPU的要求越来越高,进程之间的切换开销较大,已经无法满足越来越复杂的程序的要求了。于是就发明了线程,线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间(也就是所在进程的内存空间)。一个标准的线程由线程ID,当前指令指针PC,寄存器和堆栈组成。而进程由内存空间(代码,数据,进程空间,打开的文件)和一个或多个线程组成。
寄存器是CPU内部用来存放数据的一些小型存储区域,用来暂时存放参与运算的数据和运算结果
进程与线程的区别
线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位;
一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线
进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段,数据集,堆等)及一些进程级的资源(如打开文件和信号等),某进程内的线程在其他进程不可见;
调度和切换:线程上下文切换比进程上下文切换要快得多
举个简单的例子:
计算机的核心是CPU,它承担了所有的计算任务,它就像一座工厂,时刻在运行。假设工厂的电力有限,一次只能给一个车间使用。也就是说,一个车间开工的时候,其他车间都必须停工。背后的意义就是单个CPU一次只能运行一个任务.进程就好比工厂的车间,它代表CPU所能处理的单个任务。任一时刻,CPU总是运行一个进程,其他进程处于非运行状态。一个车间里,可以有很多工人。他们协同完成一个任务。线程就好比车间里的工人。一个进程可以包括多个线程。车间的空间是工人们共享的,比如许多房间是每个工人都可以进出的。这象征一个进程的内存空间是共享的,每个线程都可以使用这些共享内存。可是,每间房间的大小不同,有些房间最多只能容纳一个人,比如厕所。里面有人的时候,其他人就不能进去了。这代表一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。
创建线程的几种方式
/**
* 通过继承Thread实现线程
*/
public class ThreadTest extends Thread{ private int i = 0 ;
@Override
public void run() {
for(;i<50;i++){
System.out.println(Thread.currentThread().getName() + " is running " + i );
}
}
public static void main(String[] args) {
for(int j=0;j<50;j++){if(j=20){
new ThreadTest().start() ;
new ThreadTest().start() ;
}
}
}
}
/**
* 通过实现Runnable接口实现的线程类
*/
public class RunnableTest implements Runnable {
private int i ;
@Override
public void run() {
for(;i<50;i++){
System.out.println(Thread.currentThread().getName() + " -- " + i);
}
}
public static void main(String[] args) {
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName() + " -- " + i);
if(i==20){
RunnableTest runnableTest = new RunnableTest() ;
new Thread(runnableTest,"线程1").start() ;
new Thread(runnableTest,"线程2").start() ;
}
}
}
}
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
* 实现Callable接口call方法 用FutureTask进行包装
*/
public class CallableTest {
public static void main(String[] args) {
CallableTest callableTest = new CallableTest() ;
//因为Callable接口是函数式接口,可以使用Lambda表达式
FutureTask task = new FutureTask((Callable)()->{
int i = 0 ;
for(;i<100;i++){
System.out.println(Thread.currentThread().getName() + "的循环变量i的值 :" + i);
}
return i;
});
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+" 的循环变量i : + i");
if(i==20){
new Thread(task,"有返回值的线程").start();
}
}
try{
System.out.println("子线程返回值 : " + task.get());
}catch (Exception e){
e.printStackTrace();
}
}
}
同步
就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。也就是必须一件一件做、等前一件事做完了才能做下一件事.就像早上起床后,先洗涮,然后才能吃饭,不能在洗涮没有完成时,就开始吃饭
简单的说就是:一定要等任务执行完了,得到结果,才执行下一个任务。
异步
异步的概念和同步相对。调用在发出之后,这个调用就直接返回了,所以就没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出之后,被调用者通过“状态”、“通知”、“回调”三种途径通知调用者。
简单的说就是:不等任务执行完,直接执行下一个任务。
阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。
线程池
为什么要用?
多线程的情况下确实可以最大限度发挥多核处理器的计算能力,提高系统的吞吐量和性能。但是如果随意使用线程,对系统的性能反而有不利影响。
当线程数量太大时,反而会耗尽cpu和内存资源。比如说,创建和销毁线程也需要时间,如果创建和销毁的时间远大于线程执行的时间,反而得不偿失。其次线程也需要占用内存空间,大量的线程会抢占宝贵的内存资源,可能会导致out of memory异常。且大量的线程回收也会给GC带来很大的压力,延长GC的停顿时间。
因此,为了避免频繁的创建和销毁线程,让创建的线程进行复用,就有了线程池的概念。线程池(Thread Pool)是一种基于池化思想管理线程的工具,采用了池化技术,所谓的池化技术是为了最大化收益并最小化风险,而将资源统一在一起管理的一种思想。线程池里会维护一部分活跃线程,如果有需要,就去线程池里取线程使用,用完即归还到线程池里,免去了创建和销毁线程的开销,且线程池也会线程的数量有一定的限制。
线程池优势:
降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗。
提高响应速度:任务到达时,无需等待线程创建即可立即执行。
提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。
提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。
线程池的工作过程如下:
线程池刚创建时,里面没有一个线程(当然也可以通过prestartCoreThread进行预热)。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会 马上执行它们。 当调用 execute() 方法添加一个任务时,线程池会做如下判断: 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务; 如果正在运行的线程数量大于或等于 corePoolSize,那么将这 个任务放入队列; 如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要创建非核 心线程立刻运行这个任务; 如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池 会抛出异常RejectExecutionException。 当一个线程完成任务时,它会从队列中取下一个任务来执行。 当一个线 程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于 corePoolSize, 那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。
参数:
corePollSize: 核心线程数。在创建了线程池后,线程中没有任何线程,等到有任务到来时才创建线程去执行任 务。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务, 当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中。
maximumPoolSize:最大线程数。表明线程中最多能够创建的线程数量。 keepAliveTime:空闲的线程保留的时间。 TimeUnit:空闲线程的保留时间单位。
BlockingQueue:阻塞队列,存储等待执行的任务。参数有ArrayBlockingQueue、LinkedBlockingQueue、 SynchronousQueue可选。
ThreadFactory:线程工厂,用来创建线程
TimeUnit:空闲线程的保留时间单位
keepAliveTime: 线程空闲时间
RejectedExecutionHandler:队列已满,而且任务量大于最大线程的异常处理策略。有以下取值
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:当触发拒绝策略,只要线程池没有关闭的话,则使用调用线程直接运行任务。一般并发比较小,性能要求不高,不允许失败。但是,由于调用者自己运行任务,如果任务提交速度过快,可能导致程序阻塞,性能效率上必然的损失较大
@Service
@Slf4j
public class ThreadPoolUtils {
/**
* 核心线程数
*/
private int coreCoolSize = 5;
/**
* 最大线程数
*/
private int maxNumPoolSize = 20;
/**
* 线程池维护线程所允许的空闲时间
*/
private long keepAliveTime = 60;
/**
* 单例线程池
*/
private ThreadPoolExecutor threadPoolExecutor;
/**
* 定时调度线程池
*/
private ScheduledExecutorService scheduledExecutor;
/**
* 缓冲队列大小
*/
private int queueSize = 1000;
/**
* 初始化标记
*/
private volatile boolean inited = false;
/**
* 当线程池满时,是否阻塞住
*/
private boolean blockWhenFull = true;
private BlockingQueue workQueue;
private RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();
public void init(){
if (inited) {
return;
}
synchronized (this) {
if (inited) {
return;
}
workQueue = new ArrayBlockingQueue<>(queueSize);
this.threadPoolExecutor = new ThreadPoolExecutor(coreCoolSize, maxNumPoolSize, keepAliveTime, TimeUnit.SECONDS, workQueue, handler);
ThreadFactory threadFactory = new ScheduledThreadFactory("schedule-pool-%d-%s");
this.scheduledExecutor = new ScheduledThreadPoolExecutor(coreCoolSize, threadFactory, handler);
this.threadPoolExecutor.allowCoreThreadTimeOut(true);
inited = true;
addShutdownHook();
}
}
/**
* 添加任务
* @param task
*/
public void addTask(Runnable task) {
if (task == null) {
return;
}
log.info("ThreadPool add task : thread hashcode: {}", task.hashCode());
if (!inited) {
init();
}
threadPoolExecutor.execute(task);
}
/**
* 向线程池中添加循环运行的任务
* @param task 任务(必须实现Runnable接口)
* @param interval 时间间隔,单位毫秒
*/
void loopTask(Runnable task, long interval) {
loopTask(task, interval);
}
/**
* 向线程池中添加循环运行的任务
* @param task 任务(必须实现Runnable接口)
* @param interval 时间间隔,单位毫秒
* @param delayTime 延迟执行的时间,单位毫秒,表示任务在delay ms后开始被定时调度
*/
public void addDelayTask(Runnable task, long interval, long delayTime) {
if (task == null) {
return;
}
log.info("ScheduledThreadPool add task : thread hashcode: {}", task.hashCode());
if (!inited) {
init();
}
scheduledExecutor.scheduleAtFixedRate(TtlRunnable.get(task), delayTime, interval, TimeUnit.MILLISECONDS);
}
public void stop() {
threadPoolExecutor.shutdownNow();
scheduledExecutor.shutdown();
}
/**
* 获取线程池中正在执行的线程数目
*/
public int getActiveCount() {
return threadPoolExecutor.getActiveCount();
}
private class RejectedHandler implements RejectedExecutionHandler {
/**
* define the reject policy when executor queue is full
*
* @see RejectedExecutionHandler
* #rejectedExecution(java.lang.Runnable, java.util.concurrent.ThreadPoolExecutor)
*/
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
if (blockWhenFull) {
try {
executor.getQueue().put(r);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
}
}
/**
* 注册hook,在server shutodwn的时候可以记录线程信息
*/
private void addShutdownHook(){
// 在重启的时候遍历没有执行完的线程,打印出hashCode(在add的时候记录了该线程的hashCode),便于后续问题查找
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
if (threadPoolExecutor != null) {
Iterator iterator = workQueue.iterator();
StringBuffer stringBuffer = new StringBuffer();
while (iterator.hasNext()) {
stringBuffer.append(iterator.next().hashCode());
stringBuffer.append("-");
}
if (stringBuffer.length() > 1) {
log.error("server is shutting down, thread info: {}", stringBuffer);
}
}
// stop pool
ThreadPoolUtils.this.stop();
}
});
}
static class ScheduledThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
ScheduledThreadFactory(String namePrefix) {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
this.namePrefix = String.format(namePrefix,
poolNumber.getAndIncrement(), "%d");
}
String getThreadName() {
return String.format(namePrefix,
threadNumber.getAndIncrement());
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
getThreadName(),
0);
if (!t.isDaemon()) {
t.setDaemon(true);
}
if (t.getPriority() != Thread.NORM_PRIORITY) {
t.setPriority(Thread.NORM_PRIORITY);
}
return t;
}
}
public void setCorePoolSize(int corePoolSize) {
this.coreCoolSize = corePoolSize;
}
public void setMaximumPoolSize(int maximumPoolSize) {
this.maxNumPoolSize = maximumPoolSize;
}
public void setKeepAliveTime(long keepAliveTime) {
this.keepAliveTime = keepAliveTime;
}
public void setQueueSize(int queueSize) {
this.queueSize = queueSize;
}
public void setBlockWhenFull(boolean blockWhenFull) {
this.blockWhenFull = blockWhenFull;
}
}
状态机
状态机是有限状态自动机的简称,是现实事物运行规则抽象而成的一个数学模型。
先来解释什么是“状态”( State )。现实事物是有不同状态的,例如一个自动门,就有 open 和 closed 两种状态。我们通常所说的状态机是有限状态机,也就是被描述的事物的状态的数量是有限个,例如自动门的状态就是两个 open 和 closed 。
有限状态机是一种用来进行对象行为建模的工具,其作用主要是描述对象在它的生命周期内所经历的状态序列,以及如何响应来自外界的各种事件。在计算机科学中,有限状态机被广泛用于建模应用行为、硬件电路系统设计、软件工程,编译器、网络协议、和计算与语言的研究
其实我们在编程时实现相关业务逻辑时经常需要处理各种事件和状态切换,写各种switch/case 和if/else ,所以我们其实可能一直都在跟有限状态机打交道,只是可能没有意识到。在处理一些业务逻辑比较复杂的需求时,可以先看看是否适合用一个有限状态机来描述,如果可以把业务模型抽象成一个有限状态机,那么代码就会逻辑特别清晰,结构特别规整。
为啥要用?
有限状态机是一种对象行为建模工具,适用对象有一个明确并且复杂的生命流(一般而言三个以上状态),并且在状态变迁存在不同的触发条件以及处理行为。从我个人的使用经验上,使用状态机来管理对象生命流的好处更多体现在代码的可维护性、可测试性上,明确的状态条件、原子的响应动作、事件驱动迁移目标状态,对于流程复杂易变的业务场景能大大减轻维护和测试的难度。
1、状态机的要素状态机可归纳为4个要素,即现态、条件、动作、次态。“现态”和“条件”是因,“动作”和“次态”是果。详解如下:
①现态:是指当前所处的状态。
②条件:又称为“事件”。当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。
③动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。
④次态:条件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。
主订单状态机
子订单状态机
逆向状态机
public StateMachine build(BeanFactory beanFactory) {
StateMachineBuilder.Builder builder = StateMachineBuilder.builder();
try {
// 构建配置工厂
builder.configureConfiguration()
.withConfiguration()
.machineId(TRADE_MAIN_ORDER_MACHINE_ID)
.beanFactory(beanFactory);
// 构建状态
builder.configureStates()
.withStates()
.initial(TradeMainOrderStateEnum.INITIAL)
.choice(TradeMainOrderStateEnum.PAY_GUARD)
.end(TradeMainOrderStateEnum.MAIN_ORDER_COMPLETED)
.states(EnumSet.allOf(TradeMainOrderStateEnum.class));
// 构建事件
builder.configureTransitions()
// 100 --> 110
.withExternal()
.source(TradeMainOrderStateEnum.INITIAL).target(TradeMainOrderStateEnum.MAIN_ORDER_CREATED)
.event(TradeMainOrderEventEnum.CREATE_MAIN_ORDER_EVENT).action(tradeMainOrderCreateAction)
// 110 --> 120支付判断中
.and().withExternal()
.source(TradeMainOrderStateEnum.MAIN_ORDER_CREATED).target(TradeMainOrderStateEnum.PAY_GUARD)
.event(TradeMainOrderEventEnum.PAYMENT_EVENT).action(tradeMainOrderPayAction)
.and().withChoice()
.source(TradeMainOrderStateEnum.PAY_GUARD)
.first(TradeMainOrderStateEnum.PAY_PROCESSING, tradeMainOrderPayGuard, tradeMainOrderPayingAction)
.last(TradeMainOrderStateEnum.PAY_FINISHED, tradeMainOrderPayedAction)
// 220 --> 120
.and().withExternal()
.source(TradeMainOrderStateEnum.PAY_PROCESSING).target(TradeMainOrderStateEnum.PAY_GUARD)
.event(TradeMainOrderEventEnum.PAYMENT_EVENT).action(tradeMainOrderPayAction)
.and().withChoice()
.source(TradeMainOrderStateEnum.PAY_GUARD)
.first(TradeMainOrderStateEnum.PAY_PROCESSING, tradeMainOrderPayGuard, tradeMainOrderPayingAction)
.last(TradeMainOrderStateEnum.PAY_FINISHED, tradeMainOrderPayedAction)
// 240 --> 300 已交付
.and().withExternal()
.source(TradeMainOrderStateEnum.PAY_FINISHED).target(TradeMainOrderStateEnum.DELIVER_FINISHED)
.event(TradeMainOrderEventEnum.START_DELIVER_EVENT).action(tradeMainOrderDeliverAction)
// 300 --> 500 已交付,使用中
.and().withExternal()
.source(TradeMainOrderStateEnum.DELIVER_FINISHED).target(TradeMainOrderStateEnum.ORDER_IN_USING)
.event(TradeMainOrderEventEnum.START_USING_CAR_EVENT).action(tradeMainOrderUsingAction)
// 500 --> 900 已完成
.and().withExternal()
.source(TradeMainOrderStateEnum.ORDER_IN_USING).target(TradeMainOrderStateEnum.MAIN_ORDER_COMPLETED)
.event(TradeMainOrderEventEnum.COMPLETE_MAIN_ORDER).action(tradeMainOrderCompleteAction)
// 支付完成,直接走到交易结束
.and().withExternal()
.source(TradeMainOrderStateEnum.PAY_FINISHED).target(TradeMainOrderStateEnum.MAIN_ORDER_COMPLETED)
.event(TradeMainOrderEventEnum.COMPLETE_MAIN_ORDER).action(tradeMainOrderCompleteAction)
// 构建关闭、取消事件,关闭不走状态机,直接走逻辑代码
// 支付不走交易,而是直接状态同步接口的场景SYNC_MAIN_ORDER
.and().withExternal()
.source(TradeMainOrderStateEnum.MAIN_ORDER_CREATED).target(TradeMainOrderStateEnum.PAY_FINISHED)
.event(TradeMainOrderEventEnum.SYNC_MAIN_ORDER)
// 110-->300
.and().withExternal()
.source(TradeMainOrderStateEnum.MAIN_ORDER_CREATED).target(TradeMainOrderStateEnum.DELIVER_FINISHED)
.event(TradeMainOrderEventEnum.START_DELIVER_EVENT).action(tradeMainOrderDeliverAction)
// 110-->500
.and().withExternal()
.source(TradeMainOrderStateEnum.MAIN_ORDER_CREATED).target(TradeMainOrderStateEnum.ORDER_IN_USING)
.event(TradeMainOrderEventEnum.START_USING_CAR_EVENT).action(tradeMainOrderUsingAction)
// 110-->900
.and().withExternal()
.source(TradeMainOrderStateEnum.MAIN_ORDER_CREATED).target(TradeMainOrderStateEnum.MAIN_ORDER_COMPLETED)
.event(TradeMainOrderEventEnum.COMPLETE_MAIN_ORDER).action(tradeMainOrderCompleteAction)
;
return builder.build();
} catch (Exception e) {
log.error("MainStateMachine Builder error:", e);
return null;
}
}
反射
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
原理
反射首先是能够获取到Java中的反射类的字节码,然后将字节码中的方法,变量,构造函数等映射成 相应的 Method、Filed、Constructor 等类
反射的功能
在运行时判定任意一个对象所属的类;
在运行时构造任意一个类的对象;
在运行时判定任意一个类所具有的成员变量和方法;
在运行时调用任意一个对象的方法;
反射的应用
反射是框架设计的灵魂。
在我们平时的项目开发过程中,基本上很少会直接使用到反射机制,但这不能说明反射机制没有用,实际上有很多设计、开发都与反射机制有关,例如模块化的开发,通过反射去调用对应的字节码;动态代理设计模式也采用了反射机制,还有我们日常使用的 Spring、Hibernate 等框架也大量使用到了反射机制。
例子:
1.比如在spring中,我们将所有的类Bean交给spring容器管理,无论是XML配置Bean还是注解配置,当我们从容器中获取Bean来依赖注入时,容器会读取配置,而配置中给的就是类的信息,spring根据这些信息,需要创建那些Bean,spring就动态的创建这些类。
2.创建数据库链接时,这句代码Class tc = Class.forName(“com.java.dbtest.TestConnection”)就是告诉JVM去加载这个类,而加载的过程是在程序执行过程中动态加载的。通过类的全类名让jvm在服务器中找到并加载这个类,而如果是使用别的数据库,那就要换一个类了,如果是传统写死的方法创建,就要修改原来类的代码,而对于反射,则只是传入的参数就变成另一个了而已,可以通过修改配置文件,而不是直接修改代码。
3.再比如我们有两个程序员,一个程序员在写程序的时候,需要使用第二个程序员所写的类,但第二个程序员并没完成他所写的类。那么第一个程序员的代码能否通过编译呢?这是不能通过编译的。利用Java反射的机制,就可以让第一个程序员在没有得到第二个程序员所写的类的时候,来完成自身代码的编译。只是如果这个类还没有,获取时会获取不到,但不会导致编译错误,更不会导致程序的崩溃。
public class ReflectClass {
private final static String TAG = "peter.log.ReflectClass";
// 创建对象
public static void reflectNewInstance() {
try {
Class> classBook = Class.forName("com.android.peter.reflectdemo.Book");
Object objectBook = classBook.newInstance();
Book book = (Book) objectBook;
book.setName("Android进阶之光");
book.setAuthor("刘望舒");
Log.d(TAG,"reflectNewInstance book = " + book.toString());
} catch (Exception ex) {
ex.printStackTrace();
}
}
// 反射私有的构造方法
public static void reflectPrivateConstructor() {
try {
Class> classBook = Class.forName("com.android.peter.reflectdemo.Book");
Constructor> declaredConstructorBook = classBook.getDeclaredConstructor(String.class,String.class);
declaredConstructorBook.setAccessible(true);
Object objectBook = declaredConstructorBook.newInstance("Android开发艺术探索","任玉刚");
Book book = (Book) objectBook;
Log.d(TAG,"reflectPrivateConstructor book = " + book.toString());
} catch (Exception ex) {
ex.printStackTrace();
}
}
// 反射私有属性
public static void reflectPrivateField() {
try {
Class> classBook = Class.forName("com.android.peter.reflectdemo.Book");
Object objectBook = classBook.newInstance();
Field fieldTag = classBook.getDeclaredField("TAG");
fieldTag.setAccessible(true);
String tag = (String) fieldTag.get(objectBook);
Log.d(TAG,"reflectPrivateField tag = " + tag);
} catch (Exception ex) {
ex.printStackTrace();
}
}
// 反射私有方法
public static void reflectPrivateMethod() {
try {
Class> classBook = Class.forName("com.android.peter.reflectdemo.Book");
Method methodBook = classBook.getDeclaredMethod("declaredMethod",int.class);
methodBook.setAccessible(true);
Object objectBook = classBook.newInstance();
String string = (String) methodBook.invoke(objectBook,0);
Log.d(TAG,"reflectPrivateMethod string = " + string);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
class 的方法
getName():获得类的完整名字。 getFields():获得类的public类型的属性。
getDeclaredFields():获得类的所有属性。
getMethods():获得类的public类型的方法。
getDeclaredMethods():获得类的所有方法。
getMethod(String name, Class[] parameterTypes):获得类的特定方法,name参数指定方法的名字,parameterTypes参数指定方法的参数类型。
getConstrutors():获得类的public类型的构造方法。
getConstrutor(Class[] parameterTypes):获得类的特定构造方法,parameterTypes参数指定构造方法的参数类型。
newInstance():通过类的不带参数的构造方法创建这个类的一个对象。
反射机制的优缺点:
优点:运行期类型的判断,动态加载类,极大地提高了应用程序的扩展性。
缺点:
1.性能第一:反射包括了一些动态类型,所以 JVM 无法对这些代码进行优化。因此,反射操作的 效率要比那些非反射操作低得多。我们应该避免在经常被 执行的代码或对性能要求很高的程 序中使用反射。
2.安全限制:使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行,如 Applet,那么这就是个问题了
3.内部暴露:由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用--代码有功能上的错误,降低可移植性。 反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。
对象创建过程
new 一个 Object 发生了什么?
1.虚拟机检查new指令,首先会检查这个指令的参数是否能在常量池定位到一个类的符号引用
2.检查这个符号引用代表的类是否已被加载、解析、初始化
3.如果没有,先执行相应的类加载过程
4.进行逃逸分析,能否栈上分配、标量替换
5.分配内存 指针碰撞 or 空闲列表 采用压缩算法的收集器 一般使用指针碰撞,使用标记清除的理论上采用空闲 列表
6.虚拟机将分配到的内存空间初始化为零值,保证对象的实例字段可以不设置初始值就能直接使用
7.对象进行必要的设置,设置对象头信息,如属于哪个类的实例klass pointer, 哈希码, GC分代年龄,是否使用偏向锁
8.执行构造函数,按照程序员的意愿对对象进行初始化
简单理解:
分配对象内存
调用构造器方法,执行初始化
将对象引用赋值给变量。
类加载过程
加载
在加载阶段,Java虚拟机需要完成以下三件事情:
1)通过一个类的全限定名来获取定义此类的二进制字节流。
2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入 口。
验证
验证是连接阶段的第一步,这一阶段的目的是确保Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求,保证这些信息被当作代码运行后不会危害虚拟机自身的安全。
文件格式的验证
第一阶段要验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理。这一阶段可能包括下面这些验证点:
·是否以魔数0xCAFEBABE开头。
·主、次版本号是否在当前Java虚拟机接受范围之内。
·常量池的常量中是否有不被支持的常量类型(检查常量t ag标志)。
·指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量。
·CONSTANT_Utf8_info型的常量中是否有不符合UTF-8编码的数据。
·Class文件中各个部分及文件本身是否有被删除的或附加的其他信息。
该验证阶段的主要目的是保证输入的字节流能正确地解析并存储于方法区之内,格式上符 合描述一个Java类型信息的要求。这阶段的验证是基于二进制字节流进行的,只有通过了这个阶段的 验证之后,这段字节流才被允许进入Java虚拟机内存的方法区中进行存储,所以后面的三个验证阶段 全部是基于方法区的存储结构上进行的,不会再直接读取、操作字节流了。
元数据验证
第二阶段是对字节码描述的信息进行语义分析,以保证其描述的信息符合《Java语言规范》的要求,这个阶段可能包括的验证点如下:
·这个类是否有父类(除了java.lang.Object之外,所有的类都应当有父类)。
·这个类的父类是否继承了不允许被继承的类(被final修饰的类)。
·如果这个类不是抽象类,是否实现了其父类或接口之中要求实现的所有方法。
·类中的字段、方法是否与父类产生矛盾(例如覆盖了父类的final字段,或者出现不符合规则的方 法重载,例如方法参数都一致,但返回值类型却不同等)。
第二阶段的主要目的是对类的元数据信息进行语义校验,保证不存在与《Java语言规范》定义相 悖的元数据信息。
字节码验证
第三阶段是整个验证过程中最复杂的一个阶段,主要目的是通过数据流分析和控制流分析,确定 程序语义是合法的、符合逻辑的。在第二阶段对元数据信息中的数据类型校验完毕以后,这阶段就要 对类的方法体(Class文件中的Code属性)进行校验分析,保证被校验类的方法在运行时不会做出危害 虚拟机安全的行为,例如:
·保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作,例如不会出现类似于“在操作 栈放置了一个int类型的数据,使用时却按long类型来加载入本地变量表中”这样的情况。
·保证任何跳转指令都不会跳转到方法体以外的字节码指令上。
·保证方法体中的类型转换总是有效的,例如可以把一个子类对象赋值给父类数据类型,这是安全 的,但是把父类对象赋值给子类数据类型,甚至把对象赋值给与它毫无继承关系、完全不相干的一个 数据类型,则是危险和不合法的。
符号引用验证
最后一个阶段的校验行为发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在 连接的第三阶段——解析阶段中发生。符号引用验证可以看作是对类自身以外(常量池中的各种符号 引用)的各类信息进行匹配性校验,通俗来说就是,该类是否缺少或者被禁止访问它依赖的某些外部 类、方法、字段等资源。本阶段通常需要校验下列内容:
·符号引用中通过字符串描述的全限定名是否能找到对应的类。
·在指定类中是否存在符合方法的字段描述符及简单名称所描述的方法和字段。
·符号引用中的类、字段、方法的可访问性(private、protected、public、)是否可被当 前类访问。
·......
符号引用验证的主要目的是确保解析行为能正常执行,如果无法通过符号引用验证,Java虚拟机 将会抛出异常 , 典 型 的 如 : java.lang.IllegalAccessError、java.lang.NoSuchFieldError、java.lang.NoSuchM ethodError等。
准备
准备阶段是正式为类中定义的变量(即静态变量,被static修饰的变量)分配内存并设置类变量初 始值的阶段,从概念上讲,这些变量所使用的内存都应当在方法区中进行分配,但必须注意到方法区 本身是一个逻辑上的区域
解析
解析阶段是Java虚拟机将常量池内的符号引用替换为直接引用的过程,符号引用在第6章讲解Class 文件格式的时候已经出现过多次,在Class文件中它以CONSTANT_Class_info、 CONSTANT_Fieldref_info、CONSTANT_M ethodref_info等类型的常量出现
符号引用(Symbolic References):符号引用以一组符号来描述所引用的目标,符号可以是任何 形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引 用的目标并不一定是已经加载到虚拟机内存当中的内容。各种虚拟机实现的内存布局可以各不相同, 但是它们能接受的符号引用必须都是一致的,因为符号引用的字面量形式明确定义在《Java虚拟机规 范》的Class文件格式中。
直接引用(Direct References):直接引用是可以直接指向目标的指针、相对偏移量或者是一个能 间接定位到目标的句柄。直接引用是和虚拟机实现的内存布局直接相关的,同一个符号引用在不同虚 拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经在虚拟机 的内存中存在。
初始化
直到初始化阶段,Java虚拟机才真正开始执行类中编写的Java程序代码,将主导权移交给应用程 序。
初始化 将一个类中所有被static关键字标识的代码统一执行一遍,如果执行的是静态变量,那么就会使用用户 指定的值覆盖之前在准备阶段设置的初始值;如果执行的是static代码块,那么在初始化阶段,JVM就会执行static 代码块中定义的所有操作
RPC
什么是RPC?
RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP/IP或UDP,为通信程序之间携带信息数据。RPC将原来的本地调用转变为调用远端的服务器上的方法,给系统的处理能力和吞吐量带来了近似于无限制提升的可能。在OSI网络通信模型中,RPC跨域了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。
简单地说,RPC是指远程过程调用,也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。
RPC架构
一个完整的RPC架构里面包含了四个核心的组件,分别是Client,Client Stub,Server以及Server Stub,这个Stub可以理解为存根。
客户端(Client),服务的调用方。
客户端存根(Client Stub),存放服务端的地址消息,再将客户端的请求参数打包成网络消息,然后通过网络远程发送给服务方。
服务端(Server),真正的服务提供者。
服务端存根(Server Stub),接收客户端发送过来的消息,将消息解包,并调用本地的方法。
一次RPC过程
(1) 客户端(client)以本地调用方式(即以接口的方式)调用服务;
(2) 客户端存根(client stub)接收到调用后,负责将方法、参数等组装成能够进行网络传输的消息体(将消息体对象序列化为二进制);
(3) 客户端通过sockets将消息发送到服务端;
(4) 服务端存根( server stub)收到消息后进行解码(将消息对象反序列化);
(5) 服务端存根( server stub)根据解码结果调用本地的服务;
(6) 本地服务执行并将结果返回给服务端存根( server stub);
(7) 服务端存根( server stub)将返回结果打包成消息(将结果消息对象序列化);
(8) 服务端(server)通过sockets将消息发送到客户端;
(9) 客户端存根(client stub)接收到结果消息,并进行解码(将结果消息发序列化);
(10) 客户端(client)得到最终结果。RPC的目标是要把2、3、4、7、8、9这些步骤都封装起来。
注意:无论是何种类型的数据,最终都需要转换成二进制流在网络上进行传输,数据的发送方需要将对象转换为二进制流,而数据的接收方则需要把二进制流再恢复为对象。
常用的rpc框架:dubbo、Spring Cloud、grpc
Dubbo
dubbo 是什么
Apache Dubbo 是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
1.透明化的远程方法调用,就像调用本地方法一样调用远程方法,只需简单配置,没有任何API侵入。
2.软负载均衡及容错机制,可在内网替代F5等硬件负载均衡器,降低成本,减少单点。
3.服务自动注册与发现,不再需要写死服务提供方地址,注册中心基于接口名查询服务提供者的IP地址,并且能够平滑添加或删除服务提供者。
面向接口代理的高性能RPC调用
服务自动注册与发现
运行期流量调度
智能负载均衡
高度可扩展能力
可视化的服务治理与运维
架构
节点角色说明
节点 | 角色说明 |
---|---|
Provider |
暴露服务的服务提供方 |
Consumer |
调用远程服务的服务消费方 |
Registry |
服务注册与发现的注册中心 |
Monitor |
统计服务的调用次数和调用时间的监控中心 |
Container |
服务运行容器 |
调用关系说明
服务容器负责启动,加载,运行服务提供者。
服务提供者在启动时,向注册中心注册自己提供的服务。
服务消费者在启动时,向注册中心订阅自己所需的服务。
注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
Dubbo 架构具有以下几个特点,分别是连通性、健壮性、伸缩性
连通性
注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小
监控中心负责统计各服务调用次数,调用时间等,统计先在内存汇总后每分钟一次发送到监控中心服务器,并以报表展示
服务提供者向注册中心注册其提供的服务,并汇报调用时间到监控中心,此时间不包含网络开销
服务消费者向注册中心获取服务提供者地址列表,并根据负载算法直接调用提供者,同时汇报调用时间到监控中心,此时间包含网络开销
注册中心,服务提供者,服务消费者三者之间均为长连接,监控中心除外
注册中心通过长连接感知服务提供者的存在,服务提供者宕机,注册中心将立即推送事件通知消费者
注册中心和监控中心全部宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表
注册中心和监控中心都是可选的,服务消费者可以直连服务提供者
健壮性
监控中心宕掉不影响使用,只是丢失部分采样数据
数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务
注册中心对等集群,任意一台宕掉后,将自动切换到另一台
注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯
服务提供者无状态,任意一台宕掉后,不影响使用
服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复
伸缩性
注册中心为对等集群,可动态增加机器部署实例,所有客户端将自动发现新的注册中心
服务提供者无状态,可动态增加机器部署实例,注册中心将推送新的服务提供者信息给消费者
ThreadLocal 原理