赞
踩
目录
AQS AbstractQueuedSynchronized 抽象的队列同步器
Java ExecutorService中execute()和submit()方法的区别
Left join right join inner join
getClass() hashcode() toString() wait() notify() notifyAll() wait(long times) equals() wait(long timeout int nanous)
1.因为字符串是不可变的,所以是线程安全的
2.字符串是不可变的,字符串池才有可能实现,如果噶能变的话,
A和B指向同一个地址,A改变值,B也要跟着改变了。
3.字符串是不可变的,所以它在创建的时候hashcode就被缓存了,
不需要重新计算。如果可以变,比如用StringBuilder做模拟,
str1 = a ,str2 = ab, str3 = str1,str2+=b,
则set中的值为两个ab,显然违背了set的本意
若为多继承,那么当多个父类中有重复的属性或者方法时,子类的调用结果会含糊不清,因此用了单继承。
为什么是多实现呢。
通过实现接口拓展了类的功能,若实现的多个接口中有重复的方法也没关系,因为实现类中必须重写接口中的方法,所以调用时还是调用的实现类中重写的方法。那么各个接口中重复的变量又是怎么回事呢。
接口中,所有属性都是 static final修饰的,即常量,这个什么意思呢,由于JVM的底层机制,所有static final修饰的变量都在编译时期确定了其值,若在使用时,两个相同的常量值不同,在编译时期就不能通过。
volatile的本质是告诉jvm,当前变量在寄存器中的值是不确定的,需要从主存中读取。
Volatile可以禁止语义重排
Volatile的作用实例:很多线程用同一个标识符判断某件事是否执行,当一个线程改变这个标识的时候,能立即被其他线程看见
ArrayList 线程不安全 初始容量为10,1.5倍扩容 扩容后将老数组的值复制到新数组
在每次添加新的元素时,ArrayList都会检查是否需要进行扩容操作,扩容操作带来数据向新数组的重新拷贝,所以如果我们知道具体业务数据量, 在构造ArrayList时可以给ArrayList指定一个初始容量,这样就会减少扩容时数据的拷贝问题。
HashMap在jdk1.7是头插法,JDK1.8改成了尾插法,解决哈希冲突是用链表
hashmap继承自abstractmap, hashtable继承自Dictionary
HashMap可以有一个key为null,value可以为null, hashtable放入key和value都不能为null
HashMap不是现成安全的, Hashtable是线程安全的,他的所有的方法都用synchronized加锁了
简单翻译一下就是在理想情况下,使用随机哈希码,节点出现的频率在hash桶中遵循泊松分布,同时给出了桶中元素个数和概率的对照表
从上面的表中可以看到当桶中元素到达8个的时候,概率已经变得非常小,也就是说用0.75作为加载因子,每个碰撞位置的链表长度超过8个是几乎不可能的。
既需要key,又需要value,用entrySet。因为entryset会多一次查询map。更优雅的方式map.forEach
只需要key,可以用keyset。
同一消息可以根据发送对象的不同而采用多种不同的行为方式。
多态存在的三个必要条件
1.要有继承
2.要有重写
3.父类引用指向子类的对象
在编译之后,程序会采取去泛型化的措施。Java中的泛型,只在编译阶段有效。在编译过程中,正确检查泛型结果后,会将泛型的相关信息擦除。也就是说,泛型信息不会进入到运行阶段。
泛型的好处:
1.泛型可以知道一个对象的限定类型是什么,比较安全
2.消除了强制类型转换,使得代码可读性好,消除了出错的机会。
泛型:即参数化类型,所谓参数化类型,是指操作的数据类型在定义时被指定为一个参数,然后在使用时传入具体的类型。
泛型不能为基本类型
可以自定义泛型,泛型在Java集合类框架中被广泛的使用
方法覆盖:子类重新定义了父类的方法,有相同的类型、参数列表和返回类型
方法重载:同一个类有两个或者多个方法的方法名相同,但是参数不同
类可以实现多个接口,但是只能继承一个抽象类
Java接口中声名的变量默认是final类型的,抽象类可以包含非final类型变量
抽象类可以有默认的方法实现,接口完全抽象的,根本不存在方法的实现
抽象方法可以有public、protected、default这些修饰,接口方法默认修饰是public
AbstractMap是Map接口的实现类之一,也是HashMap、TreeMap、ConcurrentHashMap等的父类, AbstractMap提供了Map的基本实现,使得我们以后要实现一个Map不用从头开始,只需要继承AbstractMap。
反射机制是在运行状态中,对于任意一个类,都能知道这个类的所有属性和方法
getDeclaredField 获得一个类的所有字段
getField 获取类的public字段
getDeclaredMethods() 获取类的所有方法
getMethods() 返回某个类的所有公用方法
getDeclaredConstructor() 返回所有的构造器
getConstructor() 返回public构造函数
Java反射的作用:
在IDEA中输入一个类,按".",编译器就会自动列出他的属性和方法,这里就会用到反射。
代理模式是常用的Java设计模式,其特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息传给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象并不真正实现服务,而是通过调用委托类的对象的相关的方法,来提供特点的服务。
按照代理类创建的时期,代理类可分为两种。
静态代理类:由程序员创建或由特定工具生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
动态代理类:在程序运行时,运用反射机制动态创建而成。
Error 系统中的错误 Exception(异常)
checkedException try-catch捕获 比如IOexception,ClassNotFoundException
runtimeExeception 空指针,数组越界
NOClassDefFoundError是运行时报错,是一个错误(Error),
ClassNotFoundException 是一个异常,可捕获
NOClassDefFoundError 要查找的类在编译时是存在的,运行时却找不到了,new操作来创建一个新的对象但却找不到该对象的类。
ClassNotFoundException 编译的时候就找不到,class.forName动态加载
常见的Exception
1.nullpointerExeception 2.classnotfoundExcetion 3. .arithmeticexception(数据运算异常)4. java.lang.arrayindexoutofboundsexception (数组下标越界)
5.ClassCastException(类型强制转换异常)
对于try..catch捕获异常的形式来说,对于异常的捕获,可以有多个catch。对于try里面发生的异常,他会根据发生的异常和catch里面的进行匹配(怎么匹配,按照catch块从上往下匹配),当它匹配某一个catch块的时候,他就直接进入到这个catch块里面去了,后面在再有catch块的话,它不做任何处理,直接跳过去,全部忽略掉。如果有finally的话进入到finally里面继续执行。换句话说,如果有匹配的catch,它就会忽略掉这个catch后面所有的catch。对我们这个方法来说,抛出的是IOException,当执行etct.doSomething();时,可能会抛出IOException,一但抛出IOException,它首先进入到catch (Exception e) {}里面,先和Exception匹配,由于OExceptionextends Exception,根据多态的原则,IOException是匹配Exception的,所以程序就会进入到catch (Exception e) {}里面,进入到第一个catch后,后面的catch都不会执行了,所以catch (IOException e) {}永远都执行不到,就给我们报出了前面的错误:已捕捉到异常 java.io.IOException。
总结:在写异常处理的时候,一定要把异常范围小的放在前面,范围大的放在后面,Exception这个异常的根类一定要刚在最后一个catch里面,如果放在前面或者中间,任何异常都会和Exception匹配的,就会报已捕获到...异常的错误。
static
修饰变量:类加载时初始化,JVM只分配一次内存,所有类共享静态变量。
修饰方法:在类加载时就存在,不依赖任何实例:static方法必须实现,不能用abstract修饰。
修饰代码块:在类加载完之后会执行代码块中的内容。static代码块有多个,JVM会按照他们在类中出现的先后顺序依次执行它们,每个代码块只被执行一次。
static final用来修饰成员变量和成员方法,可简单理解为“全局变量”!
当需要一个方法一初始化就要运行的时候,就要用static来修饰。
序列化:将数据分解成字节流,以便存储在文件中或在网络上传输
反序列化:打开字节流并重构对象。
在运行反序列化时,JVM会将传来的字节流的SeriaVersionUID与本地相应实体(类)的seriaVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则会出现序列化版本不一致异常。
如果没有显示声名序列号,程序在编译时会自己生成这个版本的序列号。在存储文件中,如果在你更改实体类的时候又会重新生成一个序列号,在程序运行的时候,Java的序列化机制在运行时判断类的serialVersionUID来验证版本一致性的。
Java序列化排除序列化字段 transient
IO NIO
面向流 面向缓冲
阻塞IO 非阻塞IO
无 选择器
NIO的核心在于:通道和缓冲区(Buffer), 通道表示IO源到IO设备(例如:文件,套接字)的链接,Channel负责传输,Buffer负责存储
FileInputStream/FileOutPutStream字节流
FileReader/FileWriter字符流
值传递是指在调用函数时将实际数复制一份传递到函数中,这样在函数中如果对形参进行修改,将不会影响到实际参数。简单来说就是直接复制了一份数据过去,因为是直接复制,所以这种方式在复制时如果传递的数据非常大,运行效率将会降低,所以Java在传递数据量很小的时候是值传递,比如Java的基本类型:int,float,double,boolean等类型。
引用传递操作的是源数据,二维数组,List,Map等除了基本的数据类型都是引用传递。
当我们定义了一个实现Serializable接口的类之后,一般我们会手动在类内部定义一个private static final long serialVersionUID字段,用来保存当前类的序列版本号。这样做的目的就是唯一标识该类,其对象持久化之后这个字段将会保存到持久化文件中,当我们对这个类做了一些更改时,新的更改可以根据这个版本号找到已持久化的内容,来保证来自类的更改能够准确的体现到持久化内容中。而不至于因为未定义版本号,而找不到原持久化内容。
String这种 启动类加载器 jre/lib/rt.jar
扩展类加载器 jre/lib/ext/*.jar
自己写的 应用类加载器
自定义加载器
1.装载:将Java二进制代码导入jvm中,生成Class文件。
2. 连接过程
(1)验证:确保被加载类的正确性,即确保被加载的类符合javac编译的规范
(2)准备:为类的静态变量分配内存,并初始化为默认值
(3)解析:将类中的符号引用转化为直接引用
注:符号引用即一个Java源文件在被编译时,在不清楚被引用类实际内存地址的情况下,会使用能唯一识别并定位到目标的符号来代替。如A类引用了B类,编译时A并不知道B类实际的内存地址,故可以使用能唯一识别B的符号来代替。而当类加载时,编译后的.class文件实际已被调入内存,可知道A,B类的实际内存地址,当引用的目标已被加载入内存,则此时的引用为直接引用。
步骤三、初始化
初始化过程为类的静态变量赋予正确的初始值(与连接过程的准备阶段不同,如int类型的静态变量,JVM的默认值为0,遇到static int a = 3的代码时,准备阶段首先是赋值为0,初始化阶段才赋值为3
强引用和弱引用
引用类型 被垃圾回收的时间 用途 生存时间
强引用 从来不会 对象的一般状态 JVM停止时运行
软引用 内存不足时 对象缓存 内存不足时终止
弱引用 在垃圾回收时 对象缓存 GC运行后终止
双亲委派模式优势
采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。可能你会想,如果我们在classpath路径下自定义一个名为java.lang.SingleInterge类(该类是胡编的)呢?该类并不存在java.lang中,经过双亲委托模式,传递到启动类加载器中,由于父类加载器路径下并没有该类,所以不会加载,将反向委托给子类加载器加载,最终会通过系统类加载器加载该类。但是这样做是不允许,因为java.lang是核心API包,需要访问权限,强制加载将会报出如下异常
java.lang.SecurityException: Prohibited package name: java.lang
1.程序计数器:是一个数据结构,用于保存当前正常执行的程序的内存地址。Java虚拟机的多线程就是通过线程轮流切换并分配处理器时间来实现的, 为了线程切换后能恢复到正确的位置,每条线程都需要一个独立的程序计数器,互不影响,该区域为“线程私有”。
2.Java虚拟机栈:线程私有的,与线程生命周期相同,用于存储局部变量表,操作栈,方法返回值。局部变量表放着基本数据类型,还有对象的引用。
3.本地方法栈:跟虚拟机栈很像,不过它是为虚拟机使用到的Native方法服务。
hashcode方法是native的
wait(long times) notify notifyAll也是native的
4.Java堆:所有线程共享的一块内存区域,对象实例几乎都在这分配内存。
5.方法区:各个线程共享的区域,储存虚拟机加载的类信息,常量,静态变量,编译后的代码。
6.运行时常量池:代表运行时每个class文件中的常量表。包括几种常量:编译时的数字常量、方法或者域的引用。
新生代:复制算法 老年代:标记清除、标记整理
引用计数法 可达性算法
1.虚拟机栈中引用的对象、
2.方法区类静态属性引用的对象、
3.方法区常量池引用的对象、
4.本地方法栈JNI引用的对象
java 1.8 永久代消失了,由元空间取代
Eclipse内存泄露 MAT
Intelij Idea内存泄漏 JProfiler
JVM调优
-Xms 初始堆大小
-Xmx 最大堆大小
-Xmn 年轻代大小
-XX:NewSize 设置年轻代大小
-XX:+PrintGCDetails 打印出GC信息
1.同步方法,用synchronized关键字修饰的方法
2.同步代码块,用synchronized修饰的代码块
3.使用可重入锁RetrantLock实现同步
4.使用局部变量实现同步(ThreadLocal)
5.使用阻塞队列实现的同步(LinkedBlockingQueue)
6.使用原子变量实现同步
1、wait()、notify()机制
2、同步方法
3、while轮询
4、使用condition控制线程通信
sleep来自Thread类,wait来自Object类
sleep必须捕获异常,而wait、notify不需要捕获异常
sleep方法没有释放锁,wait释放锁,进入等待池等待,出让系统资源,需要其他线程调用notify方法将其唤醒
join 让主线程等待子线程结束后再运行
Java四种线程池
newFixedThreadPool 创建一个定长线程池,超出的线程会放在队列中等待
corePoolSize为nThread,maximumPoolSize为nThread
适用:执行长期的任务,性能好很多
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
corePoolSize为传递来的参数,maximumPoolSize为Integer.MAX_VALUE
适用:周期性执行任务的场景
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
适用:执行很多短期异步的小程序或者负载较轻的服务器
corePoolSize为0;maximumPoolSize为Integer.MAX_VALUE
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
corePoolSize为1,maximumPoolSize为1
· 通俗:创建只有一个线程的线程池,且线程的存活时间是无限的;当该线程正繁忙时,对于新任务会进入阻塞队列中(无界的阻塞队列)
相比new Thread,Java提供的四种线程池的好处在于
1.重用存在的线程,减少对象的创建、销毁的开销
2.提高响应速度,当任务到达时,任务可以不需要等待线程创建就可以立即执行。
3.提高线程的可管理性,线程是稀缺资源,如果无限的创建,不仅消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配、调优和监控。
核心线程----》工作队列---〉》非核心线程----〉》拒绝策略
1.线程池固定数目大小 CPU密集型
最佳线程数目=(线程等待时间/线程运行时间+1)*CPU数目
2.线程池的核心参数
corePollsize:核心线程数
maximunPoolSize:最大线程数
keepAliveTime:空闲的线程保留时间
TimeUnit: 空闲线程保留时间单位
BlockingQueue<Runnable>阻塞队列,存储等待执行的任务 ArrayBlockingQueue LinkedBlockingQueue SynchronousQueue可选
ThreadFactory:线程工厂,用来创建线程
RejectedExecutionHandler:队列已满
AbortPolicy 丢弃任务并抛出异常
DiscardPolicy 丢弃任务,不抛出异常
同步代码块(Synchronization)基于进入和退出管程(Monitor)对象实现。每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:
如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.
如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。
被 synchronized 修饰的同步方法并没有通过指令monitorenter和monitorexit来完成(理论上其实也可以通过这两条指令来实现),不过相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成
AQS的实现依赖内部同步队列(FIFO双向队列),如果当前线程获取同步状态失败,AQS会将该线程以及其等待状态信息构造成一个Node,将其加入同步队列器的尾部,同时阻塞当前线程,当同步状态释放时,唤醒队列的头结点。
private transient volatile Node head;
tail;
private volatile int state;
state=0 表示可用
release的同步状态相对简单,需要找到头结点的后继结点进行唤醒,若后继结点为空或处于cancel状态,从后向前遍历找寻一个正常结点,唤醒其对应的线程。
共享式:同一时刻可以有多个线程同时获取到同步状态,这也是"共享的"的意义所在,其待重写的尝试获取同步状态的方法tryAcquireShared返回值为int
1.当返回值大于0时,表示获取同步状态成功,同时还有剩余同步状态可供其他线程获取
2.当返回值等于0时,表示获取同步状态成功,但没有可用的同步状态了。
3.当返回值小于0时 ,表示同步获取失败。
释放同步锁-- releaseShared
AQS是JUC中很多同步组件的构建基础,简单来讲,它内部实现主要是状态变量state和一个FIFO队列来完成,同步队列的头结点是当前获取到同步状态的结点,获取同步状态state失败的线程,会被构造成一个结点(或共享式或独占式)加入到同步队列尾部(采用自旋CAS来保证此操作的线程安全),随后线程会阻塞;释放时唤醒头结点的后继结点,使其加入对同步状态的争夺中。
AQS为我们定义好了顶层的处理实现逻辑,我们在使用AQS构建符合我们需求的同步组件时,只需重写tryAcquire,tryAcquireShared,tryRelease,tryReleaseShared几个方法,来决定同步状态的释放和获取即可,至于背后复杂的线程排队,线程阻塞/唤醒,如何保证线程安全,都由AQS为我们完成了,这也是非常典型的模板方法的应用。AQS定义好顶级逻辑的骨架,并提取出公用的线程入队列/出队列,阻塞/唤醒等一系列复杂逻辑的实现,将部分简单的可由使用者决定的操作逻辑延迟到子类中去实现。
一个线程在获取了锁之后,再去获取同一个锁,这个时候仅仅是把状态值state进行累加,释放一次锁,状态减1,状态值为0的时候,才是线程把锁释放了,其他线程才有机会获取锁。
当线程A执行完之后,要唤醒线程B是需要时间的,而且线程B醒来后还要再次竞争锁,所以如果在切换过程当中,来了一个线程C,那么线程C是有可能获取到锁的,如果C获取到了锁,B就只能继续乖乖休眠了。
公平锁:就是很公平,在并发环境中,每个线程在获取锁的时候会先查看此锁维护的等待队列,如果为空,或者当前线程线程是等待队列的第一个,就占有锁,否则就会加入到等待队列中,以后会按照FIFO的规则从队列中取到自己
非公平锁比较粗鲁,上来就直接尝试占有锁,如果尝试失败,就再采用类似公平锁那种方式
extends Thread implements Runnable
public class CallableTest {
public static void main(String[] args) throws Exception{
Callable<Integer> callable = new Callable<Integer>() {
public Integer call() throws Exception {
return new Random().nextInt(100);
}
};
//extends Runnable, Future<V>
FutureTask<Integer> futureTask = new FutureTask<Integer>(callable);
//既可以作为Runnable被线程执行
new Thread(futureTask).start();
//又可以作为Future得到Callable的结果
System.out.println(futureTask.get());
}
}
FutureTasks实现了两个接口,Runnable和Future,所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的结果
方法execute()没有返回值,而submit()方法可以有返回值(通过Callable和Future接口)
方法execute()在默认情况下异常直接抛出(即打印堆栈信息),不能捕获,但是可以通过自定义ThreadFactory的方式进行捕获(通过setUncaughtExceptionHandler方法设置),而submit()方法在默认的情况下可以捕获异常
方法execute()提交的未执行的任务可以通过remove(Runnable)方法删除,而submit()提交的任务即使还未执行也不能通过remove(Runnable)方法删除
run()方法依旧只有主线程,start()方法会启动一个线程来执行
lock synchronized
接口 关键字
可以让等待锁的线程响应中断 一直等下去
可以知道有没有成功获取锁 不行
读写锁可以提高多个线程进行读操作的效率
需要收到在finally中unlock
①一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了,其他的线程都只能等待,换句话说,某一时刻内,只能有唯一一个线程去访问这些synchronized方法。
②锁的是当前对象this,被锁定后,其他线程都不能进入到当前对象的其他的synchronized方法。
③加个普通方法后发现和同步锁无关。
④换成静态同步方法后,情况又变化
⑤所有的非静态同步方法用的都是同一把锁 -- 实例对象本身,也就是说如果一个实例对象的非静态同步方法获取锁后,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁后才能获取锁,可是别的实例对象的非静态同步方法因为跟该实例对象的非静态同步方法用的是不同的锁,所以毋须等待该实例对象已经取锁的非静态同步方法释放锁就可以获取他们自己的锁。
⑥所有的静态同步方法用的也是同一把锁 -- 类对象本身,这两把锁是两个不同的对象,所以静态同步方法与非静态同步方法之间不会有竞争条件。但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,而不管是同一个实例对象的静态同步方法之间,还是不同的实例对象的静态同步方法之间,只要它们是同一个实例对象
无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的是锁是对象; 如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类。
当一个程序对一个线程安全的方法或者语句进行访问的时候,其他的不能再进行操作了
CountDownLatch:一个线程(或者多个),等待另外N个线程完成某个事情之后才能执行
CountDownLatch()最重要的方法是countDown()和await(),前者主要倒数一次,后者是等待倒数到达0,如果没有到达0,就只能等待了。
CyclicBarrier:N个线程相互等待,任何一个线程完成之前,所有线程都必须等待,任何一个线程完成之前,所有线程都必须等待。 await()调用一次加1
CountDownLatch,重点是那个一个线程,是它在等待,而另外那N的线程在把“某个事情”做完之后可以继续等待,可以终止。
而对于cyclicBarrier来说,重点是那N个线程,他们之间任何一个没有完成,所有的线程都必须等待。
CountDownLatch是计数器,线程完成一个记一个,就像报数一样,只不过是递减的。
cyclicBarrier更像一个水闸,线程执行就像水流,在水闸处都会堵住,等到水满(线程到齐)了,才开始泄流。
Semaphore 信号量
acquire()要么通过成功获取信号量(信号量减1),要么一直等待下去,直到有线程释放信号量或超时。release()释放会将信号量加1
Future接口,表示异步计算的结果
ThreadLocal采用的是以空间换时间的方式,为每个线程提供一份变量副本。每一个线程都可以独立的改变自己的副本。
ThreadLocal类中有一个map,用于存储每一个线程的变量副本,map中元素为的键为线程对象,而值为对应线程的变量副本。
同步机制是为了同步多个线程对相同资源的并发访问,是多个线程间进行通信的有效方式,
而ThreadLocal是隔离多个线程的数据共享,从根本上就不存在多个线程之间共享资源。
所有如果需要进行多个线程之间通信,用同步机制,如果要隔离多个线程之间的共享冲突,采用ThreadLocal
Public protected default private
Singleton:单例模式,只会创建该Bean的唯一实例
prototype:享元模式,每次请求都创建一个实例
request:一次Http请求,容器会返回该bean的同一实例
session:每次会话创建一个实例
globalsession:全局Httpsession中,容器会返回该bean的同一实例
1、实例化一个Bean--也就是我们常说的new;
2、按照Spring上下文对实例化的Bean进行配置--也就是IOC注入;
3、如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String)方法,此处传递的就是Spring配置文件中Bean的id值
4、如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现的setBeanFactory(setBeanFactory(BeanFactory)传递的是Spring工厂自身(可以用这个方式来获取其它Bean,只需在Spring配置文件中配置一个普通的Bean就可以);
5、如果这个Bean已经实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文(同样这个方式也可以实现步骤4的内容,但比4更好,因为ApplicationContext是BeanFactory的子接口,有更多的实现方法);
6、如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessBeforeInitialization(Object obj, String s)方法,BeanPostProcessor经常被用作是Bean内容的更改,并且由于这个是在Bean初始化结束时调用那个的方法,也可以被应用于内存或缓存技术;
7、如果Bean在Spring配置文件中配置了init-method属性会自动调用其配置的初始化方法。
8、如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法、;
注:以上工作完成以后就可以应用这个Bean了,那这个Bean是一个Singleton的,所以一般情况下我们调用同一个id的Bean会是在内容地址相同的实例,当然在Spring配置文件中也可以配置非Singleton,这里我们不做赘述。
9、当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用那个其实现的destroy()方法;
10、最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。
Spring的IOC实现原理就是工厂模式加反射机制,通俗来讲就是根据给出的类名(字符串方式)来动态地生成对象,这种编程方式可以让对象在生成时才被决定到底是哪一种对象。
把IOC容器的工作模式看做是工厂模式的升华,可以把IOC容器看作是一个工厂,这个工厂里要生产的对象都在配置文件中给出定义,然后利用编程语言提供的反射机制,根据配置文件中给出的类名生成相应的对象。
Spring支持三种依赖注入方式,分别是属性(Setter方法)注入,构造注入和接口注入。
IOC的缺点:
1)软件系统中由于引入了第三方IOC容器,生成对象的步骤变得有些复杂,本来是两者之间的事情,又凭空多出一道手续,所以,我们在刚开始使用IOC框架的时候,会感觉系统变得不太直观。所以,引入了一个全新的框架,就会增加团队成员学习和认识的培训成本,并且在以后的运行维护中,还得让新加入者具备同样的知识体系。
2)由于IOC容器生成对象是通过反射方式,在运行效率上有一定的损耗。如果你要追求运行效率的话,就必须对此进行权衡。
3)具体到IOC框架产品(比如Spring)来讲,需要进行大量的配制工作,比较繁琐,对于一些小的项目而言,客观上也可能加大一些工作成本。
4)IOC框架产品本身的成熟度需要进行评估,如果引入一个不成熟的IOC框架产品,那么会影响到整个项目,所以这也是一个隐性的风险。 我们大体可以得出这样的结论:一些工作量不大的项目或者产品,不太适合使用IOC框架产品。另外,如果团队成员的知识能力欠缺,对于IOC框架产品缺乏深入的理解,也不要贸然引入。最后,特别强调运行效率的项目或者产品,也不太适合引入IOC框架产品。
1.客户端请求提交到DispatcherServlet
2.DispatcherServlet查询HandlerMapping,找到处理请求的Controller
3.DispatcherServlet将请求提交到Controller
4.Controller处理请求,返回ModelAndView
5.Dispatcher查询一个或多个视图解析器,找到ModelAndView指定的视图
6.视图负责将结果显示到客户端
AOP(面向切面编程):将核心关注点和横切关注点分离开来.
核心关注点:业务主要流程
横切关注点:与业务关系不大.
AOP应用到项目中的好处,能够将与业务逻辑不相关的代码(如:日志、权限等)分离出来,减小相关业务类负担, 并能让一些通用需求(如:事务)得到更广泛的复用.
一些概念:
1.切面:日志.安全性框架等,总之和业务逻辑没关系的就可以看做切面.
2.通知:切面中的方法
3.切入点:只有符合切入点才能把通知和目标方法结合在一起.
4.连接点:客户端调用的方法
5.织入:形成代理方法的过程.
AOP中各种通知
1.前置通知: 在目标方法执行之前
2.后置通知:在目标方法执行之后,可以根据returning获取目标方法的返回值 public void commit(JoinPoint joinPoint,Object val) * 如果目标方法遇到异常,该通知不执行
最终通知 * 在目标方法执行之后 * 无论目标方法是否遇到异常,都执行 * 经常做一些关闭资源
异常通知 目的就是为了获取目标方法抛出的异常
环绕通知 能控制目标方法的执行,环绕通知还可以控制返回对象。
使用JDK动态代理,目标类必须实现某个接口,如果某个类没有实现接口,则不能生成动态代理对象。
CGlib必须依赖于CGlib的类库,CGlib的原理是针对目标类生成一个子类,覆盖其中的所有方法,所以目标类和方法不能声名为final类型。
1.如果mysql数据库引擎是maisam,则事物不起作用
2.该注解只能应用到public可见度的方法上,在proteted private 或者private方法上不起作用
aop本质决定的,必须是public
3.注解不能继承,因此不能放在接口上
1、对于一个web 应用,其部署在web 容器中,web 容器提供其一个全局的上下文环境,这个上下文就是 ServletContext ,其后面的spring IoC 容器提供宿主环境
2、在web.xml 中会提供有 contextLoaderListener。在web 容器启动时,会触发容器初始化事件,此时 contextLoaderListener 会监听到这个事件,其 contextInitialized 方法会被调用,在这个方法中,spring 会初始化一个启动上下文,这个上下文就被称为根上下文,即 WebApplicationContext ,这是一个借口类,确切的说,其实际实现类是 XmlWebApplicaitonContext 。这个就是Spring 的Ioc 容器,其对应的Bean 定义的配置由web.xml 中的 context-param 标签指定。在这个Ioc 容器初始化完毕后,spring 以WebApplicationContext.ROOTWEBAPPLICATIONEXTATTRIBUTE 为属性key,将其存储到 servletContext 中,便于获取
3、contextLoaderListener 监听器初始化完毕后,开始初始化web.xml 中配置的servlet ,这个servlet 可以配置多个,以最常见的DispatcherServlet 为例,这个servlet 实际上是一个标准的前端控制器,用以转发、匹配、处理每个servlet 请求。DispatcherServlet 上下文在初始化的时候会建立自己的Ioc 上下文,用以持有springmvc 相关的bean。在建立DispatherSrvlet 自己的Ioc 上下文时,会利用 WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE 先从ServletContext 中获取之前的根上下文(即 WebApplicationContext)作为自己上下文的parent 上下文,有了这个parent 上下文之后,再初始化自己持有的上下文。这个DispatcherServlet 初始化自己上下的工作在其 initStrategies 方法中可以看到,大概的工作就是初始化处理器映射、视图解析等,这个servlet 自己持有的上下文默认实现类也是 XmlWebApplicationContext。初始化完毕后,spring以与Servlet 的名字相关的属性为属性key,将其存到servletcontext 中,以便后续使用。这样每个Servlet 都持有自己的上下文,即拥有自己独立的bean 空间,同事各个servlet 共享相同的bean,即根上下文定义的那些bean
public class Singleton3 {
private Singleton3(){ }
private static Singleton3 singleton3 = null;
public Singleton3 getinstance(){
if(singleton3 == null){
synchronized (Singleton3.class){
if(singleton3 == null){
singleton3 = new Singleton3();
}
}
}
return singleton3;
}
}
枚举实现单例的原理:
1.多线程环境的安全性:enum最后会被编译器处理成static final的,并且在static模块进行初始化,因此它的实例化是在class被加载阶段完成的,是线程安全的。
2.不可被反射实例化
enum只能申明private的构造方法来来防止enum被使用new进行实例化,而且还限制了使用反射的方法不能通过Constructor来newInstance一个枚举实例。
3.序列化的唯一性
单例模式有以下特点
1.单例类只能有一个实例。
2.单例类必须自己创建自己唯一的实例
3.单例类必须给所有其他对象提供这一实例
适配器模式和装饰者模式
Adapter模式将一个类的接口转换成客户希望的另外一个接口。Adapter 模式使原本由于接口不兼容而不能一起工作的那些类可以一起工作。 适配器模式有类的适配器模式和对象的适配器模式两种不同的形式。
在上图中可以看出,Adaptee类并没有sampleOperation2()方法,而客户端则期待这个方法。为使客户端能够使用Adaptee类,提供一个中间环节, 即类Adapter,把Adaptee的API与Target类的API衔接起来。Adapter与Adaptee是继承关系,这决定了这个适配器模式是类的: 模式所涉及的角色有: ● 目标(Target)角色:这就是所期待得到的接口。注意:由于这里讨论的是类适配器模式,因此目标不可以是类。 ● 源(Adapee)角色:现在需要适配的接口。 ● 适配器(Adaper)角色:适配器类是本模式的核心。适配器把源接口转换成目标接口。显然,这一角色不可以是接口,而必须是具体类。
public class Adapter extends Adaptee implements Target
目标接口: 添加观察者,删除观察者,通知观察者notice,
在实现的时候用一个list来存储观察者
在notice里面,便利list,调用obsever.update方法
观察者 就一个update方法
(1)代理类与委托类实现同一接口
(2)在委托类中实现功能,在代理类的方法中中引用委托类的同名方法
(3)外部类调用委托类某个方法时,直接以接口指向代理类的实例,这正是代理的意义所在:屏蔽。
----
把不变的行为搬到超类,去除子类中重复的代码来体现他的优势;当不变的和可变的行为在子类实现中混合在一起的时候,
不变的行为就会在子类中重复实现,我们通过模板方法模式把这些行为搬移到单一的地方,这样就可以帮助子类摆脱重复不变行为的纠缠。
在lingxu中,抽象出一个AbstractCluster的概念,四层集群L4Cluster和七层集群L7Cluster继承AbstractCluster,实现各自的部分。
Where,group by,having,order by
1.显示学生姓名和平均分
Select s_name, avg(score) from student
Where score >= 60 group by s_name having (s_score)>=70 order by avg(s_score) desc
主键和唯一索引的区别
1.主键不允许空值,唯一索引允许空值
2.一个表中可以有多个唯一索引,但只能有一个主键
3.非唯一索引
4.聚集索引:在聚集索引中,表中行的物理顺序与键值的逻辑(索引)顺序相同
什么时候使用聚集索引
(1)含有大量非重复值的列
(2)使用Between 大于等返回一个范围的列
(3)连续被访问的列
(4)返回大型结果集的查询
1.where及order by涉及的列上建索引
2.尽量避免在where字句中对字段进行null值判断,否则将导致放弃使用索引
Select id from t where num is null,可以在null上设置默认值0,确保num列没有null,然后select id from t where num = 0,
3.尽量避免在null字句中用!=或者>或者<操作符。
4.尽量避免在where字句中使用or来连接,如果一个字段有索引,一个字段没索引,将导致引擎放弃索引而进行全表扫描。
select id from t where num=10 or Name = 'admin' 可优化为
select id from t where num = 10 union all select id from t where Name = 'admin'
5.in和not in也要慎用,
select id from t where num in(1,2,3)应改为
select id from t where num between 1 and 3
6. 尽量避免在 where子句中对字段进行表达式操作
select id from t where num/2 = 100,应该为
select id from t where num = 100*2
7. 应尽量避免在where子句中对字段进行函数操作
select id from t where substring(name,1,3) = ’abc’可以改为
select id from t where name like 'abc%'
limit千万级别优化,不直接使用limit,而是首先获取offset的id然后直接使用mysql limit size来获取数据
1)在我们平时使用limit 如 select * from A order by id limit 1,10; 这样在表数据很少的时候,看不出什么性能问题,倘若达到千万级,如 select * from A order by ID limit 10000000,10; 虽然都是只查询10记录,但是这个性能让人受不了, 2)可以这么优化,如 select * from A where id>=(select id from a limit 10000000,1)limit 10; 其实还可以这么写 Select * from A where id between 10000000 and 10000010
1,第一范式:字段的原子性,每一列都是不可分割的原子数据项
2.第二范式:确保表中的每列都和主键相关
产品编号与订单号并没有直接的关系
3.第三范式:任何非主属性不依赖于其他非主属性
上面的表,学号和姓名存在传递依赖,因为(学号,姓名)->成绩,学号->成绩,姓名->成绩。所以学号和姓名有一个冗余了,只需要保留一个。
mysql 可重复读, 大多数数据库默认隔离级别为读已提交
读未提交 脏读 不可重复读 幻读
读已提交 不可重复读 幻读
可重复读 幻读
可串行化
脏读:一个数据对事务进行了修改,但事务还没有提交。另一个事务可以“看到”
该事务没有提交的更新结果,如果第一个事务回滚,第二个事务在此之前看到的
就是一笔脏数据
不可重复读:同一个事务在整个过程中对同一笔数据进行读取,每次读取结果都不同。
如果事务1在事务2的更新操作之前读取一次数据,在事务2的更新操作之后再次读取同一笔数据数据,
两次结果是不同的.
假如A在取款机前取款,读到银行卡余额为5000,此时他老婆拿银行卡消费了2000元,结果他想取5000元显示余额不足。。。。
幻读针对的是多笔记录(针对其提交前后,读取数据条数的对比)
解决了不重复读,保证了同一个事务里,查询的结果都是事务开始时的状态(一致性)。但是,如果另一个事务同时提交了新数据,
本事务再更新时,就会“惊奇的”发现了这些新数据,貌似之前读到的数据是“鬼影”一样的幻觉。
读未提交:任何操作都不加锁
读提交:读操作不加锁,写操作加锁。读被加锁的数据时,读事务每次都读undo log的最近版本,因此可能对同一数据读到不同的版本(不可重复读),但能保证读到最新的数据
可重复读:第一次读数据的时候就将数据加行锁,使其他数据不能修改当前数据,即可实现可重复读。可是锁不住insert进来的数据,不能防止幻读
串行化:锁表,读锁和写锁阻塞。
A(Atomicity)原子性:数据库中事物执行的是原子操作,即不可再分,要么全部执行,要么全部不执行.
C(consistency)一致性: 只有合法的数据可以被写入数据库,否则事务应该将其回滚到最初状态。拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次,事务结束后两个用户的钱加起来应该还是5000,这就是事务的一致性。
I(Isolation)隔离性 事务的执行是互不干扰的,一个事务不可能看到其他事务运行中的某一刻的状态.
D(Durability)持久性 意味着事务完成以后,该事务对数据库所做的更改便持久的保持在数据库中.
为什么B+树适合做索引
1.索引很大,不可能全部存储在内存,往往以索引文件的形式存储在磁盘
2.索引查找过程产生磁盘I/O消耗,评价一个数据结构作为索引的优劣的重要指标就是尽量减少磁盘I/O
B-tree利用了磁盘快的特性进行构建,每个磁盘块一个节点(4K),每个节点只需一次I/O就可以完全载入。
每个节点包含了很多关键字。 所以层级比二叉树少很多
B+树的数据只存储在叶子结点,在B-树的基础上每个节点存储的关键字更多,数的层级更少所以查询数据更快。
所有关键字都存储在叶子结点,所以每次查找的次数相同所以查询效率更稳定。explain
通过explain可以知道mysql如何处理语句,分析出查询或是表结构的性能瓶颈。通过explain可以得到
1.表的读取顺序
2.那些索引可以被引用
3.哪些索引可以被实际引用
4.表之间的引用
5.每张表有多少行被优化器查询。
Mysql之间数据复制的基础是二进制文件(binary log file)。一台mysql数据库一旦启用二进制日志后,其作为mater,它的数据库中所有操作都会以“事件”的方式记录在二进制日志中,其他数据库作为slave通过一个I/O线程与主服务器保持通信,并监控master二进制日志文件的变化,如果发现master二进制日志文件发生变化,则会把变化复制到自己的后继日志中,然后slave的一个SQL线程会把相关的“事件”执行到自己的数据库中,以实现从数据库和主数据库的一致性,也就实现了主从复制。
Left join以左边的表作为基础,右边的表与左边的表能匹配的就匹配出来
Right join以右边的表为基础,左边的表与右边的表能匹配的就匹配出来
Inner join显示符合条件的记录
Select * from A right join B on A.id = B.id
内连接和外连接
内连接:只有两个表相匹配的行才在表中显现出来
外链接:包含表中的所有数据
MyISAM类型不支持事务处理等高级处理,而InnoDB类型支持。MyISAM类型的表强调的是性能,其执行数度比InnoDB类型更快,但是不提供事务支持,而InnoDB提供事务支持以及外部键等高级数据库功能。
MyIsam是表级锁,InnoDB是行级锁和表级锁都可以
对于MyISAM的表锁,主要讨论了以下几点:
(1)共享读锁(S)之间是兼容的,但共享读锁(S)与排他写锁(X)之间,以及排他写锁(X)之间是互斥的,也就是说读和写是串行的。
(2)在一定条件下,MyISAM允许查询和插入并发执行,我们可以利用这一点来解决应用中对同一表查询和插入的锁争用问题。
lock table table_name read local
当concurrent_insert设置为0时,不允许并发插入,设置为1时(默认为1),表中无空洞,可以在表尾插入。置位2时,无论有没有空洞,都可在表尾插入
(3)MyISAM默认的锁调度机制是写优先,这并不一定适合所有应用,用户可以通过设置LOW_PRIORITY_UPDATES参数,或在INSERT、UPDATE、DELETE语句中指定LOW_PRIORITY选项来调节读写锁的争用。
(4)由于表锁的锁定粒度大,读写之间又是串行的,因此,如果更新操作较多,MyISAM表可能会出现严重的锁等待,可以考虑采用InnoDB表来减少锁冲突。
对于InnoDB表,本文主要讨论了以下几项内容:
(1)InnoDB的行锁是基于索引实现的,如果不通过索引访问数据,InnoDB会使用表锁。
(2)介绍了InnoDB间隙锁(Next-key)机制,以及InnoDB使用间隙锁的原因。
在不同的隔离级别下,InnoDB的锁机制和一致性读策略不同。
在了解InnoDB锁特性后,用户可以通过设计和SQL调整等措施减少锁冲突和死锁,包括:
尽量使用较低的隔离级别; 精心设计索引,并尽量使用索引访问数据,使加锁更精确,从而减少锁冲突的机会;
选择合理的事务大小,小事务发生锁冲突的几率也更小;
给记录集显式加锁时,最好一次性请求足够级别的锁。比如要修改数据的话,最好直接申请排他锁,而不是先申请共享锁,修改时再请求排他锁,这样容易产生死锁;
不同的程序访问一组表时,应尽量约定以相同的顺序访问各表,对一个表而言,尽可能以固定的顺序存取表中的行。这样可以大大减少死锁的机会;
尽量用相等条件访问数据,这样可以避免间隙锁对并发插入的影响; 不要申请超过实际需要的锁级别;除非必须,查询时不要显示加锁;
对于一些特定的事务,可以使用表锁来提高处理速度或减少死锁的可能。
---------------------
作者:唐大麦
来源:CSDN
原文:https://blog.csdn.net/soonfly/article/details/70238902
版权声明:本文为博主原创文章,转载请附上博文链接!
悲观锁的读锁(共享锁)
读锁:也叫共享锁,共享数据对象上的锁权,大家都可以上锁,我上了一把读锁,你也可以上,但是只能上共享锁。可以使自己和别人不能修改数据,只能读取。
Select * from student Lock In Share Mode
写锁(排他锁)
Select * from student for update
Set autocommit = 0;
排他锁指的是一个事务在一行数据加上排他锁后,其他事务不能再在其上加其他的锁。mysql InnoDB引擎默认的修改数据语句,update,delete,insert都会自动给涉及到的数据加上排他锁,select语句默认不会加任何锁类型,如果加排他锁可以使用select ...for update语句,加共享锁可以使用select ... lock in share mode语句。所以加过排他锁的数据行在其他事务种是不能修改数据的,也不能通过for update和lock in share mode锁的方式查询数据,但可以直接通过select ...from...查询数据,因为普通查询没有任何锁机制。
开启慢查询日志,可以让mysql记录下查询超过指定时间的语句,通过定位分析性能的
瓶颈,更好的优化数据库系统的性能。
设置方法
方法一:全局变量设置
将 slow_query_log 全局变量设置为“ON”状态
mysql> set global slow_query_log='ON';
设置慢查询日志存放的位置
mysql> set global slow_query_log_file='/usr/local/mysql/data/slow.log';
查询超过1秒就记录
mysql> set global long_query_time=1;
redis的5种数据结构
String 字符串 Hash 字典 List 列表 Set 集合 Sorted Set 有序集合
持久化方案
rdb:可以设置多长时间保存一次
aof:实时保存
redis 设置过期时间 expire
redis单线程问题
单线程指的是网络请求模块使用了一个线程(所以不需考虑并发安全性),即一个线程处理所有网络请求,其他模块仍用了多个线程。
为什么说redis能够快速执行
redis的内部实现
内部实现采用epoll,采用了epoll+自己实现的简单的事件框架。epoll中的读、写、关闭、连接都转化成了事件,然后利用epoll的多路复用特性,绝不在io上浪费一点时间 这3个条件不是相互独立的,特别是第一条,如果请求都是耗时的,采用单线程吞吐量及性能可想而知了。应该说redis为特殊的场景选择了合适的技术方案。
Redis关于线程安全问题
redis实际上是采用了线程封闭的观念,把任务封闭在一个线程,自然避免了线程安全问题,不过对于需要依赖多个redis操作的复合操作来说,依然需要锁,而且有可能是分布式锁。
使用redis有哪些好处?
redis相比memcached有哪些优势?
redis持久化
Rdb:在指定时间间隔内将内存中的数据集快照写入磁盘
AOF:以日志的形式记录每个写操作,将redis执行过的所有写操作记录下来(读操作不记录),只许追加文件但不可以改写文件,redis重启会读取该文件重新构建数据。
进程间的通信方式:管道通信、消息传递、共享存储
Java线程间的通信方式:while轮询、 wait notify机制、 同步synchronized共享存储
产生死锁的四个必要条件:
(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
2xx (成功)表示成功处理了请求的状态码。
3xx (重定向) 要完成请求,需要进一步操作。通常,这些状态码用来重定向。
4xx(请求错误) 这些状态码表示请求可能出错,妨碍了服务器的处理。
5xx(服务器错误)这些状态码表示服务器在处理请求时发生内部错误
七层:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层
四层:网络接口层、网际层、运输层、应用层
五层:物理层、数据链路层、网络层、运输层、应用层
TCP UDP
传输单位 报文 用户数据包
是否连接 面向连接 面向非连接
传输可靠性 可靠 不可靠
应用场合 传输大量数据 传输少量数据
第一次握手:客户端发送syn包(syn=x)到服务器,并进入SYN_SEND状态,等待服务器确认; 第二次握手:服务器收到syn包,必须确认客户的SYN(ack=x+1),同时自己也发送一个SYN包(syn=y) 即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕, 客户端和服务器进入ESTABLISHED状态,完成三次握手。
第一次挥手:主动关闭方发送一个FIN,用来关闭主动方到被动关闭方的数据传送,也就是主动关闭方告诉被动关闭方: 我已经不会再给你发数据了(当然,在fin包之前发送出去的数据,如果没有收到对应的ack确认报文, 主动关闭方依然会重发这些数据),但是,此时主动关闭方还可 以接受数据。
第二次挥手:被动关闭方收到FIN包后,发送一个ACK给对方,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号)。
第三次挥手:被动关闭方发送一个FIN,用来关闭被动关闭方到主动关闭方的数据传送,也就是告诉主动关闭方, 我的数据也发送完了,不会再给你发数据了。
第四次挥手:主动关闭方收到FIN后,发送一个ACK给被动关闭方,确认序号为收到序号+1,至此,完成四次挥手。
为什么不能采用两次握手: 采用三次握手是为了防止失效的连接请求报文段突然又传送到主机B,因而产生错误。失效的连接请求报文段是指:主机A发出的连接请求没有收到主机B的确认,于是经过一段时间后,主机A又重新向主机B发送连接请求,且建立成功,顺序完成数据传输。考虑这样一种特殊情况,主机A第一次发送的连接请求并没有丢失,而是因为网络节点导致延迟达到主机B,主机B以为是主机A又发起的新连接,于是主机B同意连接,并向主机A发回确认,但是此时主机A根本不会理会,主机B就一直在等待主机A发送数据,导致主机B的资源浪费。
1.客户端浏览器通过DNS解析到www.baidu.com的IP地址为220.181.0.1,通过这个ip地址找到客户端到服务器的路径,客户端浏览器发起一个http会话到220.181.0.1,然后通过TCP进行封装数据包,输入到网络层。
2.在客户端的传输层,把HTTP会话请求分成报文段,添加源和目的端口,如服务器端用80端口监听客户端的请求,客户端由系统随机选择一个端口,如5000,与客户端进行交换,服务器把相应的请求返回给客户端的5000端口。然后使用ip层的ip地址查找目的端。
3.客户端的网络层不用关心应用层和传输层的东西,主要做的是通过查找路由表确定如何到达服务器,期间可能经过多个路由器。
4,。客户端的链路层,包通过链路层发送到路由器,通过邻居协议查找给定的ip地址和MAC地址,然后发送ARP请求查找目的地址,如果得到回应后就可以使用ARP的请求应答交换的ip数据包现在就可以传输了,然后发送Ip数据包到达服务器的地址。
(1)cookie数据存放在客户的浏览器上,session数据放在服务器上
(2)cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗,如果主要考虑到安全应当使用session
(3)session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,如果主要考虑到减轻服务器性能方面,应当使用COOKIE
(4)单个cookie在客户端的限制是3K,就是说一个站点在客户端存放的COOKIE不能3K。
(5)所以:将登陆信息等重要信息存放为SESSION;其他信息如果需要保留可以放在COOKIE中
sessionId存放在cookie中
cookie禁用时怎么使用session
当客户端禁用了cookie时,造成session无法访问,此时我们可以使用URL重写来解决这个问题
URL重写要求将站点中的所有超链接都进行改造,在超链接后用一个特殊的参数JSESSIONID保存当前浏览器对应session的编号,这样一来,当用户点击超链接访问服务器时,服务器可以从URL后的参数中分析出JSESSIONID,从而找到对应的sesison使用.
视图是用户看到并与之交互的界面
模型表示业务数据,并提供数据给视图
控制器接受用户的输入并调用模型和视图去完成用户请求
MVC模式的完整流程
所有终端用户请求被发送到控制器。
控制器依赖请求去加载哪个模型,并把模型加到对应的视图
附加了模型数据的最终视图作为响应发送给终端用户
1.通过Configuration config = new Configuration().confirure()//解析并读取hibernate.cfg.xml的配置文件
2.由hibernate.cfg.xml中的<mapping resource="com/xx/User.hbm.xml"/>读取并解析映射信息
3.通过SessionFactory sf = config.buildSessionFactory();//创建SessionFactory
4.Session session = sf.openSession();//打开Sesssion
5.Transaction tx = session.beginTransaction();//创建并启动事务Transation
6.persistent operate操作数据,持久化操作
7.tx.commit();//提交事务
8.关闭Session
9.关闭SesstionFactory
</if> </where> </foreach>
第一步:登录后台服务器/监控平台,查看系统资源是否达到上限,例如:CPU、内存、磁盘、I/O、网络带宽等,如果是这些问题,先将这些问题逐一解决:
如果是CPU的问题,则需要查看一下CPU占比比较高的进程,然后使用jstack命令生成进程的堆栈信息,看是否发生频繁Full GC,如果是的话,还需要看一下内存快照,分析一下内存情况(可以使用java自带的或第三方工具);如果是磁盘空间满了,及时清理磁盘;如果是带宽满了,联系网络工程师解决。如果以上这些问题都没有,则进行第二步。
第二步:检查应用服务器(Jboss/Tomcat)的线程池配置是否合理,看一下请求的排队现象是否严重,如果严重则需要重新设置合理的线程池。同样,检查一下数据库的连接池设置是否合理,增大连接池设置,同时检查一下是否有慢sql,如果有慢sql,则进行优化(优化方案是查看执行计划,设置合理的索引等)。
第三步:查看访问慢的服务的调用链,查看一下调用链中的每一步响应时间是否合理,如果不合理,则联系相关系统的负责人进行排查和解决。
第四步:检查web服务器的请求日志,看一下是否存在Doss攻击,如果有Doss攻击,则将攻击者的IP添加到防火墙的黑名单里。
maxThreads(最大线程数):每一次HTTP请求到达Web服务,tomcat都会创建一个线程来处理该请求,那么最大线程数决定了Web服务可以同时处理多少个请求,默认200.
accepCount(最大等待数):当调用Web服务的HTTP请求数达到tomcat的最大线程数时,还有新的HTTP请求到来,这时tomcat会将该请求放在等待队列中,这个acceptCount就是指能够接受的最大等待数,默认100.如果等待队列也被放满了,这个时候再来新的请求就会被tomcat拒绝(connection refused)。
maxConnections(最大连接数):这个参数是指在同一时间,tomcat能够接受的最大连接数。一般这个值要大于maxThreads+acceptCount。
增加线程是有成本的,JVM中默认情况下在创建新线程时会分配大小为1M的线程栈,所以更多的线程异味着更多的内存;
更多的线程会带来更多的线程上下文切换成本。
最佳线程数:性能压测的情况下,起初随着用户数的增加,QPS会上升,当到了一定的阀值之后,用户数量增加QPS并不会增加,或者增加不明显,同时请求的响应时间却大幅增加。这个阀值我们认为是最佳线程数。
分布式锁应该具备的条件
1.在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行。
2.高可用的获取与释放锁
3.高性能的获取与释放锁
4.具备可重入特性
5.具备锁失效机制,防止死锁
6.具备非阻塞锁特性,即没有获取到锁将直接返回索取锁失效。
数据库实现分布式锁
创建一个表,表里面包含方法名等字段,并且方法名字段加唯一索引。
因为加了唯一索引,多个请求同时提交同一条数据的话,只有一条会成功。
成果插入数据后,将表中数据删除,释放相应的数据,
注意:1.提高数据库的可用性,集群部署
2.不可重入的特性: 新增一列,记录获取锁的机器和线程信息。
3.没有失效机制,可能出现成功插入数据后,服务器宕机,对应的数据没有被删除,当服务器恢复后,
一直获取不到锁。 新增一列用于记录失效时间,用定时任务清楚这些失效的数据。
节点角色说明:
Provider: 暴露服务的服务提供方。
Consumer: 调用远程服务的服务消费方。
Registry: 服务注册与发现的注册中心。
Monitor: 统计服务的调用次调和调用时间的监控中心。
Container: 服务运行容器。
调用关系说明:
0. 服务容器负责启动,加载,运行服务提供者。
1. 服务提供者在启动时,向注册中心注册自己提供的服务。
2. 服务消费者在启动时,向注册中心订阅自己所需的服务。
3. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
4. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
5. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
交换器相当于投递包裹到邮箱,RoutingKey相当于填写在包裹上的地址,BindingKey相当于包裹的目的地,当填写在包裹上的地址和实际想要投递的地址相匹配时,那么这个包裹会被正确的投递。
(1)生产者连接到RabbitMQ Broker,建立一个连接(connection),开启一个信道(channel)
(2)生产者申明一个交换器,并设置相关属性,比如是否排他 是否持久化。
(3)生产者申明一个队列并设置相关属性,比如是否排他 是否持久化 是否自动删除等
(4)生产者通过路由键将交换器和队列绑定起来
(5)生产者发送消息至RabbitMQ Broker,其中包含路由键/交换器等信息。
(6)相应的交换器根据接收到的路由键查找相匹配的队列。
(7)如果找到,则将从生产者发送过来的消息存入相应的队列中。
(8)如果没找到,则根据生产者配置的属性选择丢弃还是回退给生产者。
(9)关闭信道
(10)关闭连接
消费者接受消息的过程
(1)消费者连接到RabbitMQ Broker,建立一个连接(connection),开启一个信道(channel)
(2)消费者向RabbitMQ Broker请求消费相应队列的消息,可能会设置相应回调函数,以及做一些准备工作
(3)等待RabbitMQ Broker回应并投递相应队列中的消息,消费者接受消息。
(4)消费者确认(ack)接收到的消息
(5)Rabbit MQ从队列中删除相应已经被确认的消息
(6)关闭信道
(7)关闭连接
如果consumer接受了消息、在发送ack之前断开连接,Rabbitmq会认为这条消息没有被deliver,
consumer再次连接的时候,这条消息黑背redeliever
如果consumer接受了消息,但是程序中有bug,忘记ack,rabbitmq不会重发送消息
RabbitMQ,遵循AMQP协议,主要用在实时性要求比较高的消息传递上。
Kafka遵从一般MQ结构,主要用于处理活跃的流式数据,大数据量的数据处理上。
Kafka内部采用消息的批量处理,具有很高的吞吐量。
RabbitMQ在吞吐方面比kafka逊色一点。 出发点主要在消息的可靠传递。
ps -aux 显示包含其他使用者的线程
process status
ps 与grep 常用组合用法,查找特定进程
命令:ps -ef|grep java
linux中如何查看某个端口是否被占用
netstat -anp |grep 端口号
anp all number process
ping可以测试到目的主机的连通性
telnet 可以测试指定端口是否开放 telnet IP 8080 测试目标IP的8080端口是否开放
free 查看内存的使用情况
free -m m表示以M为单位
df -hl 查看磁盘的使用情况
(1)每个节点或者是黑色,或者是红色。
(2)根节点是黑色。
(3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
(4)如果一个节点是红色的,则它的子节点必须是黑色的。
(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。[这里指到叶子节点的路径]
1.将初始待排序关键字序列构建成大根堆(从下往上调整)
2.将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,。。。。。Rn-1)和新的有序区(Rn)
3.由于交换后的新堆R[1]可能违反堆的性质,因此需要对当前无序的(R1,R2,。。。。。Rn-1)调整为新堆,然后将R[1]与无序区的最后一个元素交换,得到新的无序区(R1,R2,。。。。。Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的
元素个数为n-1,则整个排序过程完成。
注意:刚开始从下往上调整,后来从上往下调整。
分而治之(divide - conquer);每个递归过程涉及三个步骤
第一, 分解: 把待排序的 n 个元素的序列分解成两个子序列, 每个子序列包括 n/2 个元素.
第二, 治理: 对每个子序列分别调用归并排序MergeSort, 进行递归操作
第三, 合并: 合并两个排好序的子序列,生成排序结果.
随机选定一个元素作为轴值,利用该轴值将数组分为左右两部分,左边元素都比轴值小,右边元素都比轴值大,但他们不是完全排序的.在此基础上,分别对左右两部分调用快速排序,使得左右部分完全排序.
------------------------------------------------------------
1 bit = 1个2进制数,0或者1
1byte = 8 bit
1字符 = 1 byte
1汉字 = 2 byte
int 占4个字节,每个字节8位,因此是32位,大小为-2的32次方到2的32次方-1
long 是8个字节
----------------------------------------------------------------
JAVA中Object类中 有几个方法
Object类一共定义了九个方法:
equals(Object obj);
getClass();
hashCode();
notify();
notifyAll();
toString();
wait();
wait(long timeout);
wait(long timeout, int nanos);
-----------------------------------------------------------------------------
多线程同步的几种方式
1.同步方法
2.同步代码块
3.使用特殊域变量(volatile)实现线程同步
volatile不能保证原子操作,因此volatile不能代替synchronized。
它的原理是每次要访问volatile修饰的变量时都说从内存中取出。
而不是缓存中读取,因此每个线程访问到的变量值都说一样的,这样就保证
了同步。
4.使用reentrantlock
使用reentrantlock记得即时释放锁,不然会死锁
5.使用原子类
---------------------------------------------------------------------------------------------------
Static:
修饰变量:静态变量随着类加载时被完成初始化,内存中只有一个,且JVM也只会为它分配一次内存,所有类共享静态变量。
修饰方法:在类加载时就存在,不依赖任何实例:static方法必须实现,不能用abstract修饰。静态方法可以直接通过类名调用,
不能直接访问所属类的实例变量和实例方法,只能访问所属类的静态成员变量和方法。
修饰代码块:在类加载完之后会执行代码块中的内容。static代码块有多个,JVM将按照他们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。
被static修饰的成员变量和成员方法独立于该类的任何对象。
static final用来修饰成员变量和成员方法,可简单理解为“全局常量”!
对于变量,表示一旦给值就不可修改,并且通过类名可以访问。
对于方法,表示不可覆盖,并且可以通过类名直接访问。
---------------------------------------------------------------------------------
volatile与synchronized的区别
同步:如用synchronized关键字,或者使用锁对象
使用volatile关键字:用一句话概括volatile,它能够使变量在值发生改变时能尽快地让其他线程知道。
1)volatile本质是在告诉jvm当前变量在寄存器中的值是不确定的,需要从主存中读取,synchronized则是锁定当前变量
,只有当前线程可以访问该变量,其他线程被阻塞住.violate还能禁止语义重排。
2)volatile仅能使用在变量级别,synchronized则可以使用在变量,方法.
3)volatile仅能实现变量的修改可见性,而synchronized则可以保证变量的修改可见性和原子性.
4)volatile不会造成线程的阻塞,而synchronized可能会造成线程的阻塞.
violate的作用实例:
很多线程用同一个标识判断某件事是否执行,当一个线程改变这个标识的时候,能立即被其他标识看见
------------------------------------------------------------------------------------------
一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。
另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
二、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。
三、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
四、第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,
它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。-------------------------------------------------------------------------------------------------------------------------
并发程序正确地执行,必须要保证原子性、可见性以及有序性。只要有一个没有被保证,就有可能会导致程序运行不正确。
原子性:一个操作或多个操作要么全部执行完成且执行过程不被中断,要么就不执行。
可见性:当多个线程同时访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
有序性:程序执行的顺序按照代码的先后顺序执行。
对于单线程,在执行代码时jvm会进行指令重排序,处理器为了提高效率,可以对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证保存最终执行结果和代码顺序执行的结果是一致的。
在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断,要么执行,要么不执行。
X=10; //原子性(简单的读取、将数字赋值给变量)
Y = x; //变量之间的相互赋值,不是原子操作
X++; //对变量进行计算操作
X = x+1;
语句2实际包括两个操作,它先要去读取x的值,再将y值写入,两个操作分开是原子性的。合在一起就不是原子性的。
语句3、4:x++ x=x+1包括3个操作:读取x的值,x+1,将x写入
注:可以通过 synchronized和Lock实现原子性。因为synchronized和Lock能够保证任一时刻只有一个线程访问该代码块。
2、可见性
Java提供了volatile关键字保证可见性。
当一个共享变量被volatile修饰时,它会保证修改的值立即被其他的线程看到,即修改的值立即更新到主存中,当其他线程需要读取时,它会去内存中读取新值。
Synchronized和Lock也可以保证可见性,因为它们可以保证任一时刻只有一个线程能访问共享资源,并在其释放锁之前将修改的变量刷新到内存中,
3、有序性
在Java里面,可以通过volatile关键字来保证一定的“有序性”。另外可以通过synchronized和Lock来保证有序性,很显然,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。
Java内存模型:每个线程都有自己的工作内存(类似于前面的高速缓存)。线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作。
并且每个线程不能访问其他线程的工作内存。
Java内存模型具备一些先天的“有序性”,即不需要通过任何手段就能够得到保证的有序性,这个通常也称为happens-before 原则。如果两个操作的执行次序无法从happens-before原则推导出来,那么它们就不能保证它们的有序性,虚拟机可以随意地对它们进行重排序。
指令重排序:java语言规范规定JVM线程内部维持顺序化语义。即只要程序的最终结果与它顺序化情况的结果相等,那么指令的执行顺序可以与代码顺序不一致,此过程叫指令的重排序。
指令重排序的意义:JVM能根据处理器特性(CPU多级缓存系统、多核处理器等)适当的对机器指令进行重排序,使机器指令能更符合CPU的执行特性,最大限度的发挥机器性能。
下面就来具体介绍下happens-before原则(先行发生原则):
程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作
volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作。如果一个线程先去写一个变量,然后一个线程去进行读取,那么写入操作肯定会先行发生于读操作。
传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C。
线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始
----------------------------------------------------------------------------------------------------------------------
多线程:一个程序运行时产生了不止一个线程。
并行:多个CPU或者多台机器同时执行一段处理逻辑,是真正的同时。
并发:通过CPU调度算法,让用户看上去同时执行,实际上从CPU操作层面不是真正的同时。
线程安全:在并发情况下,该代码经过线程的使用,线程的调度顺序不影响任何结果。
线程不安全:线程的调度顺序会影响结果。
同步:Java中的同步指的是通过人为的控制和调度,保证共享资源的多线程访问成为线程安全,来保证结果的准确。
-------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------
对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,
让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
1.当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
2.在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。
3.当然ThreadLocal并不能替代同步机制,两者面向的问题领域不同。同步机制是为了同步多个线程对相同资源的并发访问,是为了多个线程之间进行通信的有效方式;而ThreadLocal是隔离多个线程的数据共享,从根本上就不在多个线程之间共享资源(变量),这样当然不需要对多个线程进行同步了。所以,如果你需要进行多个线程之间进行通信,则使用同步机制;如果需要隔离多个线程之间的共享冲突,可以使用ThreadLocal.
那么到底ThreadLocal类是如何实现这种“为每个线程提供不同的变量拷贝”的呢?先来看一下ThreadLocal的set()方法的源码是如何实现的:
- public void set(T value) {
- Thread t = Thread.currentThread();
- ThreadLocalMap map = getMap(t);
- if (map != null)
- map.set(this, value);
- else
- createMap(t, value);
- }
线程隔离的秘密,就在于ThreadLocalMap这个类。ThreadLocalMap是ThreadLocal类的一个静态内部类,它实现了键值对的设置和获取(对比Map对象来理解),每个线程中都有一个独立的ThreadLocalMap副本,它所存储的值,只能被当前线程读取和修改。ThreadLocal类通过操作每一个线程特有的ThreadLocalMap副本,从而实现了变量访问在不同线程中的隔离。因为每个线程的变量都是自己特有的,完全不会有并发错误。还有一点就是,ThreadLocalMap存储的键值对中的键是this对象指向的ThreadLocal对象,而值就是你所设置的对象了。
为了加深理解,我们接着看上面代码中出现的getMap和createMap方法的实现:
- ThreadLocalMap getMap(Thread t) {
- return t.threadLocals;
- }
- void createMap(Thread t, T firstValue) {
- t.threadLocals = new ThreadLocalMap(this, firstValue);
- }
再来看setInitialValue()方法:
- private T setInitialValue() {
- T value = initialValue();
- Thread t = Thread.currentThread();
- ThreadLocalMap map = getMap(t);
- if (map != null)
- map.set(this, value);
- else
- createMap(t, value);
- return value;
- }
获取和当前线程绑定的值时,ThreadLocalMap对象是以this指向的ThreadLocal对象为键进行查找的,这当然和前面set()方法的代码是相呼应的。
进一步地,我们可以创建不同的ThreadLocal实例来实现多个变量在不同线程间的访问隔离,为什么可以这么做?因为不同的ThreadLocal对象作为不同键,当然也可以在线程的ThreadLocalMap对象中设置不同的值了。通过ThreadLocal对象,在多线程中共享一个值和多个值的区别,就像你在一个HashMap对象中存储一个键值对和多个键值对一样,仅此而已。
-------------------------------------------------------------------------------------------
所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,
而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,
必须在由程序运行期间才能决定。
多态的三要素:1.继承 2.重写 3.父类引用指向子类对象
-------------------------------------------------------------------------------------------
运行时异常及处理方法
把 Exception 看成 一块 石头
throws Exception 就是把石头丢出去
try catch 就是拿个网兜在那里接石头
(1)如果你不想编写捕获异常的具体代码的话,你可以使用 throws Exception 的形式,
把异常再次抛出,交给JVM(Java虚拟机)可以捕获。这是一种比较省事的办法。
(2)如果你想亲编写处理异常的代码的话,可以使用try{ }catch(){ }的形式,进行捕获,
一旦程序发生异常,它就会安照你catch{ }块编写的代码去执行。
-------------------------------------------------------------------------------------
什么时候用接口,什么时候用抽象类
当描述一组方法的时候使用接口 当描述一个虚拟的物体的时候使用抽象类
1.抽象类可以有构造方法,接口中不能有构造方法。
2.抽象类中可以有普通成员变量,接口中没有普通成员变量!!!!!!!
3.抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。
4. 抽象类中的抽象方法的访问类型可以是public,protected和(默认类型,虽然eclipse下不报错,但应该也不行),
但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型。
5. 抽象类中可以包含静态方法(static),接口中不能包含静态方法.
6. 抽象类和接口中都可以包含静态成员变量(static),抽象类中的静态成员变量的访问类型可以任意,
但接口中定义的变量只能是public static final类型,并且默认即为public static final类型。
7. 一个类可以实现多个接口,但只能继承一个抽象类。
------------------------------------------------------------------------------------
IO常用类
文件流:FileInputStream/FileOutputStream, FileReader/FileWriter
前面两个是操作字节流,后面两个是操作字符流。
StringReader/StringWriter 需要处理字符串的时候,可以将字符串保存为字符数组
PrintStream/PrintWriter 用来包装FileOutputStream 对象,方便直接将String字符串写入文件
Scanner 用来包装System.in流,很方便地将输入的String字符串转换成需要的数据类型
InputStreamReader/OutputStreamReader , 字节和字符的转换桥梁,在网络通信或者处理键盘输入的时候用
BufferedReader/BufferedWriter , BufferedInputStream/BufferedOutputStream , 缓冲流用来包装字节流后者字符流,提升IO性能,
BufferedReader还可以方便地读取一行,简化编程。
-----------------------------------------------------------------------------------------
线程同步方式
1.syncronized关键字修饰
2.同步代码块
3.使用violate实现线程同步
4.使用reentrantlock
5.使用局部变量threadlocal
-------------------------------------------------------------------------------------
ArrayList是实现List接口的,底层采用数组实现
ArrayList提供了三个构造函数:
ArrayList():默认构造函数,提供初始容量为10的空列表。
ArrayList(int initialCapacity):构造一个具有指定初始容量的空列表。
在每次添加新的元素时,ArrayList都会检查是否需要进行扩容操作,扩容操作带来数据向新数组的重新拷贝,所以如果我们知道具体业务数据量,
在构造ArrayList时可以给ArrayList指定一个初始容量,这样就会减少扩容时数据的拷贝问题。
ArrayList(Collection<? extends E> c):构造一个包含指定 collection 的元素的列表,这些元素是按照该 collection 的迭代器返回它们的顺序排列的。
多个线程同时访问一个ArrayList实例:List list = Collections.synchronizedList(new ArrayList(...));
--------------------------------------------------------------------------
HashSet是基于HashMap来实现的,底层采用HashMap来保存元素
public Iterator<E> iterator() { return map.keySet().iterator(); }
iterator()方法返回对此 set 中元素进行迭代的迭代器。返回元素的顺序并不是特定的。底层调用HashMap的keySet返回所有的key,
这点反应了HashSet中的所有元素都是保存在HashMap的key中
---------------------------------------------------------------------------
hashmap基于哈希表的 Map 接口的实现,以key-value的形式存在。在HashMap中,key-value总是会当做一个整体来处理,
系统会根据hash算法来来计算key-value的存储位置.
- public V put(K key, V value) {
- if (key == null)
- return putForNullKey(value);
- int hash = hash(key.hashCode());
- int i = indexFor(hash, table.length);
- for (Entry<K,V> e = table[i]; e != null; e = e.next) {
- Object k;
- //判断当前确定的索引位置是否存在相同hashcode和相同key的元素,如果存在相同的hashcode和相同的key的元素,那么新值覆盖原来的旧值,并返回旧值。
- //如果存在相同的hashcode,那么他们确定的索引位置就相同,这时判断他们的key是否相同,如果不相同,这时就是产生了hash冲突。
- //Hash冲突后,那么HashMap的单个bucket里存储的不是一个 Entry,而是一个 Entry 链。
- //系统只能必须按顺序遍历每个 Entry,直到找到想搜索的 Entry 为止——如果恰好要搜索的 Entry 位于该 Entry 链的最末端
- //(该 Entry 是最早放入该 bucket 中),
- //那系统必须循环到最后才能找到该元素。
- if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
- V oldValue = e.value;
- e.value = value;
- return oldValue;
- }
- }
- modCount++;
- addEntry(hash, key, value, i);
- return null;
- }
我们知道在Java中最常用的两种结构是数组和模拟指针(引用),几乎所有的数据结构都可以利用这两种来组合实现,HashMap也是如此。
实际上HashMap是一个“链表散列”,如下是它数据结构:
1.定义:HashMap实现了Map接口,继承AbstractMap
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
2.构造函数
HashMap提供了三个构造函数:
HashMap():构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空 HashMap。
HashMap(int initialCapacity):构造一个带指定初始容量和默认加载因子 (0.75) 的空 HashMap。
HashMap(int initialCapacity, float loadFactor):构造一个带指定初始容量和加载因子的空 HashMap。
其中容量表示哈希表中桶的数量,初始容量是创建哈希表时的容量,加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度,
它衡量的是一个散列表的空间的使用程度,负载因子越大表示散列表的装填程度越高,反之愈小。
下面是java8的HashMap的数据结构,跟之前版本不一样的是当table达到一定的阀值(8)时,bucket就会由链表转换为红黑树的方式进行存储,
应该是考虑到链表的查找效率为O(n),红黑树的查找效率为O(lgn)
----------------------------------------------------------------------------
CorrentHashMap的工作原理?
jdk 1.6版: ConcurrenHashMap可以说是HashMap的升级版,ConcurrentHashMap是线程安全的,但是与Hashtablea相比,实现线程安全的方式不同。Hashtable是通过对hash表结构进行锁定,是阻塞式的,当一个线程占有这个锁时,其他线程必须阻塞等待其释放锁。ConcurrentHashMap是采用分离锁的方式,它并没有对整个hash表进行锁定,而是局部锁定,也就是说当一个线程占有这个局部锁时,不影响其他线程对hash表其他地方的访问。
具体实现: ConcurrentHashMap内部有一个Segment数组, 该Segment对象可以充当锁。Segment对象内部有一个HashEntry数组,于是每个Segment可以守护若干个桶(HashEntry),每个桶又有可能是一个HashEntry连接起来的链表,存储发生碰撞的元素。
每个ConcurrentHashMap在默认并发级下会创建包含16个Segment对象的数组,每个数组有若干个桶,当我们进行put方法时,通过hash方法对key进行计算,得到hash值,找到对应的segment,然后对该segment进行加锁,然后调用segment的put方法进行存储操作,此时其他线程就不能访问当前的segment,但可以访问其他的segment对象,不会发生阻塞等待。
jdk 1.8版 在jdk 8中,ConcurrentHashMap不再使用Segment分离锁,而是采用一种乐观锁CAS算法来实现同步问题,但其底层还是“数组+链表->红黑树”的实现。
Concurren,tHashMap源码分析(JDK8版本)
-
-------------------------------------------------------
------------------------------------------------------------------
接口的成员变量都是public static final 修饰的是常量, 而抽象类则可以是各种类型。
抽象类可以有构造方法,接口中不能有构造方法
--------------------------------------------------------------------------------------------------------------------------
方法是可以和类名同名的,和构造方法唯一的区别就是,构造方法没有返回值
构造方法每次都是构造出新的对象,不存在多个线程同时读写同一对象中的属性的问题,所以不需要同步 。
如果父类中的某个方法使用了synchronized关键字,而子类中也覆盖了这个方法,默认情况下子类中的这个方法并不是同步的,
必须显示的在子类的这个方法中加上synchronized关键字才可。当然,也可以在子类中调用父类中相应的方法,这样虽然子类中的方法并不是同步的,
但子类调用了父类中的同步方法,也就相当子类方法也同步了
构造方法是一种特殊的方法,具有以下特点。
(1)构造方法的方法名必须与类名相同。
(2)构造方法没有返回类型,也不能定义为void,在方法名前面不声明方法类型。
(3)构造方法的主要作用是完成对象的初始化工作,它能够把定义对象时的参数传给对象的域。
(4)一个类可以定义多个构造方法,如果在定义类时没有定义构造方法,则编译系统会自动插入一个无参数的默认构造器,这个构造器不执行任何代码。
(5)构造方法可以重载,以参数的个数,类型,顺序。
-----------------------------------------------------------------------------------------------------------------------------
下面哪些是interface中合法方法定义?
boolean setFlags(Boolean [] results)
private float get(int x)
static int getCount()
static方法在interface中要有body
private修饰的方法不可以出现在interface中
选择第一个
--------------------------------------------------------------------------------------------------------------------------------------------
Java 序列化 (Serializable) 的作用
序列化就是将一个对象的状态(各个属性量)保存起来,然后在适当的时候再获得。
序列化分为两大部分:序列化和反序列化。序列化是这个过程的第一部分,将数据分解成字节流,以便存储在文件中或在网络上传输。
反序列化就是打开字节流并重构对象。对象序列化不仅要将基本数据类型转换成字节表示,有时还要恢复数据。恢复数据要求有恢复数据的对象实例
序列化的什么特点:
如果某个类能够被序列化,其子类也可以被序列化。声明为static和transient类型的成员数据不能被序列化。因为static代表类的状态,
transient代表对象的临时数据。
----------------------------------------------------------------------------------------------------------------------------
JAVA序列化与反序列化三种格式存取(默认格式、XML格式、JSON格式)
1.默认格式是二进制(需要对象实现Seralizable接口)
2.xml文件格式
3.json格式
-----------------------------------------------------------------------------------------------------------------------------------
引起内存溢出的原因有很多种,常见的有以下几种:
l 内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
l 集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;
l 代码中存在死循环或循环产生过多重复的对象实体;
l 使用的第三方软件中的BUG;
l 启动参数内存值设定的过小;
内存溢出的解决
第一步,就是修改JVM启动参数,直接增加内存。这一点看上去似乎很简单,但很容易被忽略。JVM默认可以使用的内存为64M,
Tomcat默认可以使用的内存为128MB,对于稍复杂一点的系统就会不够用。在某项目中,就因为启动参数使用的默认值,经常报“OutOfMemory”错误。
因此,-Xms,-Xmx参数一定不要忘记加。
第二步,检查错误日志,查看“OutOfMemory”错误前是否有其它异常或错误。在一个项目中,使用两个数据库连接,
其中专用于发送短信的数据库连接使用DBCP连接池管理,用户为不将短信发出,有意将数据库连接用户名改错,使得日志中有许多数据库连接异常的日志,
一段时间后,就出现“OutOfMemory”错误。经分析,这是由于DBCP连接池BUG引起的,数据库连接不上后,没有将连接释放,
最终使得DBCP报“OutOfMemory”错误。经过修改正确数据库连接参数后,就没有再出现内存溢出的错误。
查看日志对于分析内存溢出是非常重要的,通过仔细查看日志,分析内存溢出前做过哪些操作,可以大致定位有问题的模块。
第三步,安排有经验的编程人员对代码进行走查和分析,找出可能发生内存溢出的位置。重点排查以下几点:
l 检查代码中是否有死循环或递归调用。
l 检查是否有大循环重复产生新对象实体。
l 检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,
在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。
l 检查List、MAP等集合对象是否有使用完后,未清除的问题。List、MAP等集合对象会始终存有对对象的引用,使得这些对象不能被GC回收。
第四步,使用内存查看工具动态查看内存使用情况。某个项目上线后,每次系统启动两天后,就会出现内存溢出的错误。
这种情况一般是代码中出现了缓慢的内存泄漏,用上面三个步骤解决不了,这就需要使用内存查看工具了。
原因有很多种,比如:
1.数据量过于庞大;死循环 ;静态变量和静态方法过多;递归;无法确定是否被引用的对象;
---------------------------------------------------------------------------------------------------------------------------------------
子类继承父类的方法是,控制符必须大于或等于父类的访问控制符
类a继承类b并重写b类的protected方法func时,a中func方法的访问修饰符可以是?
protected/public
----------------------------------------------------------------------------------------------------------------------------
hashCode与equals的区别与联系
hashCode是用于查找使用的,而equals是用于比较两个对象的是否相等的
1、hashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,hashCode是用来在散列存储结构中确定对象的存储地址的;
2、如果两个对象相同,就是适用于equals(Java.lang.Object) 方法,那么这两个对象的hashCode一定要相同;
3、如果对象的equals方法被重写,那么对象的hashCode也尽量重写,并且产生hashCode使用的对象,一定要和equals方法中使用的一致,
否则就会违反上面提到的第2点;
4、两个对象的hashCode相同,并不一定表示两个对象就相同,也就是不一定适用于equals(java.lang.Object) 方法,
只能够说明这两个对象在散列存储结构中,如Hashtable,他们“存放在同一个篮子里”。
-----------------------------------------------------------------------------------
“==”比较的是值【变量(栈)内存中存放的对象的(堆)内存地址】
equal用于比较两个对象的值是否相同【不是比地址】
---------------------------------------------------------------------------------------
22.快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?
1.什么是同步修改?
当一个或多个线程正在遍历一个集合Collection,此时另一个线程修改了这个集合的内容(添加,删除或者修改)。这就是并发修改
2.什么是 fail-fast 机制?
fail-fast机制在遍历一个集合时,当集合结构被修改,会抛出Concurrent Modification Exception。
fail-fast会在以下两种情况下抛出ConcurrentModificationException
(1)单线程环境
集合被创建后,在遍历它的过程中修改了结构。
注意 remove()方法会让expectModcount和modcount 相等,所以是不会抛出这个异常。
(2)多线程环境
当一个线程在遍历这个集合,而另一个线程对这个集合的结构进行了修改。
3. fail-fast机制是如何检测的?
迭代器在遍历过程中是直接访问内部数据的,因此内部的数据在遍历的过程中无法被修改。为了保证不被修改,迭代器内部维护了一个标记 “mode” ,
当集合结构改变(添加删除或者修改),标记"mode"会被修改,而迭代器每次的hasNext()和next()方法都会检查该"mode"是否被改变,
当检测到被修改时,抛出ConcurrentModification Exception
4. fail-safe机制
fail-safe任何对集合结构的修改都会在一个复制的集合上进行修改,因此不会抛出ConcurrentModificationException
fail-safe机制有两个问题
(1)需要复制集合,产生大量的无效对象,开销大
(2)无法保证读取的数据是目前原始数据结构中的数据。
6. fail-fast和 fail-safe 的区别
Fail Fast Iterator Fail Safe Iterator
Throw ConcurrentModification Exception Yes No
Clone object No Yes
Memory Overhead No Yes
Examples HashMap,Vector, CopyOnWriteArrayList,
ArrayList,HashSet ConcurrentHashMap
---------------------------------------------------------------------------------------------------------------------------------------------------------------
Java中,什么是构造函数?什么是构造函数重载?
1、构造函数在新对象被创建的时候会调用。每一个类都有构造函数,构造函数的名字跟类名相同,没有返回值,每个类有一个默认的无参构造函数,
2、构造函数的重载:跟方法的重载类似,唯一的不同就是功能不一样,构造函数的重载,增加不同参数,来达到初始化属性的目的
构造函数的作用
构造函数主要用来在创建对象时完成对对象属性的一些初始化等操作, 当创建对象时, 对象会自动调用它的构造函数。一般来说, 构造函数有以下三个方面的作用:
■ 给创建的对象建立一个标识符;
■ 为对象数据成员开辟内存空间;
■ 完成对象数据成员的初始化。
- public class test extends B{
- test(){
- System.out.print("t");
- A s = new B();
- }
- public static void main(String[] args) {
- test t = new test();
- }
- }
- class A{
- A(){
- System.out.print("AA");
- }
- }
- class B extends A{
- B(){
- System.out.print("b");
- A a = new A();
- }
- }
输出:AAbAAtAAbAA
原则:子类创建的时候先创建父类
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------
java项目中经常遇到的异常
1. java.lang.nullpointerexception
这个异常大家肯定都经常遇到,异常的解释是"程序遇上了空指针",简单地说就是调用了未经初始化的对象或者是不存在的对象,
2. java.lang.classnotfoundexception
3. java.lang.arithmeticexception
这个异常的解释是"数学运算异常",比如程序中出现了除以零这样的运算就会出这样的异常,对这种异常,
大家就要好好检查一下自己程序中涉及到数学运算的地方,公式是不是有不妥了。
4. java.lang.arrayindexoutofboundsexception
异常的解释是"数组下标越界",,看自己调用的下标是不是超出了数组的范围
5.类型强制转换异常:ClassCastException
-----------------------------------------------------------------------------------------
304、404、200、304等HTTP状态
2xx (成功)表示成功处理了请求的状态码。
3xx (重定向) 要完成请求,需要进一步操作。通常,这些状态码用来重定向。
4xx(请求错误) 这些状态码表示请求可能出错,妨碍了服务器的处理。
5xx(服务器错误)这些状态码表示服务器在处理请求时发生内部错误
100 服务请求中;
200 服务请求成功;
304 没有被修改,读取的内容为缓存;
401 未授权
403 禁止访问(Forbidden);
404 没有找到要访问的内容(Not Found);
500 内部服务器错误。
-----------------------------------------------------------------
Cookies 和 Session的区别
(1)cookie数据存放在客户的浏览器上,session数据放在服务器上
(2)cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗,如果主要考虑到安全应当使用session
(3)session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,如果主要考虑到减轻服务器性能方面,应当使用COOKIE
(4)单个cookie在客户端的限制是3K,就是说一个站点在客户端存放的COOKIE不能3K。
(5)所以:将登陆信息等重要信息存放为SESSION;其他信息如果需要保留,可以放在COOKIE中
缓存的目的是为了避免重复计算
常用的缓存替换算法
1.Least-Recently-Used(LRU) - 最近最少使用
替换掉最近被请求最少的文档。这一传统策略在实际中应用最广。在CPU缓存淘汰和虚拟内存系统中效果很好。然而直接应用与代理缓存效果欠佳,因为Web访问的时间局部性常常变化很大。
2.Least-Frequently-Used(LFU) - 最不经常使用
替换掉访问次数最少的。这一策略意图保留最常用的、最流行的对象,替换掉很少使用的那些。然而,有的文档可能有很高的使用频率,但之后再也不会用到。传统 的LFU策略没有提供任何移除这类文件的机制,因此会导致“缓存污染(Cache Pollution)”,即一个先前流行的缓存对象会在缓存中驻留很长时间,这样,就阻碍了新进来可能会流行的对象对它的替代。
3.SIZE
替换size最大的对象。这一策略通过淘汰一个大对象而不是多个小对象来提高命中率。不过,可能有些进入缓存的小对象永远不会再被访问。SIZE策略没有提供淘汰这类对象的机制,也会导致“缓存污染”。
4.LRU-Threshold
不缓存超过某一size的对象,其它与LRU相同。
---------------------------------------------------------
14.同步方法和同步代码块的区别是什么?
同步方法默认用this或者当前类class对象作为锁;
同步代码块可以选择以什么来加锁,比同步方法要更细颗粒度,我们可以选择只同步会发生同步问题的部分代码而不是整个方法;
同步方法锁的范围比较大,而同步代码块范围要小点,一般同步的范围越大,性能就越差,一般需要加锁进行同步的时候,肯定是范围越小越好,这样性能更好*。
--------------------------------------------------------------------
--------------------------------------------------------------------------------------------------
Java NIO和IO之间的主要差别
IO NIO
面向流 面向缓冲
阻塞IO 非阻塞IO
无 选择器
阻塞与非阻塞IO
同步和异步关注的是消息通信机制
同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。按照这个定义,其实绝大多数函数都是同步调用
异步的概念和同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。
阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.
阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。
举个通俗的例子:
你打电话问书店老板有没有《分布式系统》这本书,如果是同步通信机制,书店老板会说,你稍等,”我查一下",然后开始查啊查,
等查好了(可能是5秒,也可能是一天)告诉你结果(返回结果)。
而异步通信机制,书店老板直接告诉你我查一下啊,查好了打电话给你,然后直接挂电话了(不返回结果)。然后查好了,
他会主动打电话给你。在这里老板通过“回电”这种方式来回调。
你打电话问书店老板有没有《分布式系统》这本书,你如果是阻塞式调用,你会一直把自己“挂起”,直到得到这本书有没有的结果,
如果是非阻塞式调用,你不管老板有没有告诉你,你自己先一边去玩了, 当然你也要偶尔过几分钟check一下老板有没有返回结果。
在这里阻塞与非阻塞与是否同步异步无关。跟老板通过什么方式回答你结果无关。
选择器(Selectors)
Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,
直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。
Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。
而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,
但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,
所以一个单独的线程现在可以管理多个输入和输出通道(channel)。
Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:
这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。
----------------------------------------------------------
---------------------------------------------------------------
反射机制的定义:
是在运行状态中,对于任意的一个类,都能够知道这个类的所有属性和方法,对任意一个对象都能够通过反射机制调用一个类的任意方法,
这种动态获取类信息及动态调用类对象方法的功能称为java的反射机制。
反射的作用:
1、动态地创建类的实例,将类绑定到现有的对象中,或从现有的对象中获取类型。
2、应用程序需要在运行时从某个特定的程序集中载入一个特定的类。
java中有三种方式可以获得一个类的信息,一般采用第二种Class.forName方法
public static void main(String[] args) throws ClassNotFoundException {
Person p = new Person();
//根据一个类的实例获得
Class pclazz = p.getClass();
System.out.println(pclazz.getName());
//根据一个Class类的forName()方法获得 参数时这个类的权限定名称,也就是要带上具体的包名
Class pclazz1 = Class.forName("up9527.com.Person");
System.out.println(pclazz.getName());
//通过类名.class获得
Class pclazz2 = Person.class;
System.out.println(pclazz.getName());
}
newInstance getConstrot getDeclaredConstructor 方法的区别
1. newInstance 只能获得无参构造函数
2. Constructor<T> getConstructor(Class<?>... parameterTypes)
根据传入的参数,获得 非私有的构造函数
3.public Constructor<?> getDeclaredConstructors
根据传入的参数 ,可以获得私有的构造函数
实例
package Wangyi;
import java.lang.reflect.Constructor;
public class Person{
private int id;
Person(){
}
private Person(int id){
this.id = id;
}
public int getId(){
return this.id;
}
public void setId(int id){
this.id = id;
}
public void print(){
System.out.println("我的Id 是"+id);
}
public static void main(String[] args) throws Exception {
//根据一个Class类的forName()方法获得 参数时这个类的权限定名称,也就是要带上具体的包名
Class clazz = Class.forName("Wangyi.Person");
//通过反射来的 Person类的 Class类型的实例 创建一个 Person类的实例
//Class.newInstance() 只能够调用无参的构造函数,即默认的构造函数;
Person p = (Person) clazz.newInstance();
p.print();
//getDeclaredConstructor方法可以得到私有的构造函数,还有个getConstructor 这个不能得到私有的构造函数
Constructor<Person> pc = clazz.getDeclaredConstructor(int.class);
Person p1 =pc.newInstance(2);
p1.print();
}
}
输出:我的Id 是0
我的Id 是2
-------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
下面代码输出的结果是?
- public class NULL {
-
- public static void print(){
- System.out.println(“MTDP”);
- }
- public static void main(String[] args) {
- try{
- ((NULL)null).print();
- }catch(NullPointerException e){
- System.out.println("NullPointerException");
- }
- }
- }
答案:MTDP
null不是对象,它可以看成是指向不确定对象的引用。
赋值时,基本类型赋初值不能为null,如int=0只能是这种,换而言之,int也没有为空这一说法,如果非要勉强说有,那就是0。
而对象赋初值可以将其设为null。
本列,null是java的关键字,故不用事先声明它,直接把null作为NULL对象的一个引用,将其实例化,故而有输出。
----------------------------------------------------------------------------------------------------------------------------
线程池的作用:
在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。
常用线程池:ExecutorService 是主要的实现类,其中常用的有常见线程池
①newSingleThreadExecutor
单个线程的线程池,即线程池中每次只有一个线程工作,单线程串行执行任务
②newFixedThreadExecutor(n)
固定数量的线程池,没提交一个任务就是一个线程,直到达到线程池的最大数量,然后后面进入等待队列直到前面的任务完成才继续执行
③newCacheThreadExecutor(推荐使用)
可缓存线程池,当线程池大小超过了处理任务所需的线程,那么就会回收部分空闲(一般是60秒无执行)的线程,当有任务来时,又智能的添加新线程来执行。
④newScheduleThreadExecutor
大小无限制的线程池,支持定时和周期性的执行线程
线程池的主要工作流程如下图
首先线程池判断“基本线程池”(corePoolSize)是否已满?没满,创建一个工作线程来执行任务。满了,则进入下个流程。
- 其次线程池判断工作队列(workQueue)是否已满?没满,则将新提交的任务存储在工作队列里。满了,则进入下个流程。
- 最后线程池判断整个线程池的线程数是否已超过maximumPoolSize?没满,则创建一个新的工作线程来执行任务,满了,则交给拒绝策略来处理这个任务。
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
java concurrent包下的4个类
Semaphore:类,控制某个资源可被同时访问的个数;
ReentrantLock:类,具有与使用synchronized方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大;
Future:接口,表示异步计算的结果;
CountDownLatch: 类,可以用来在一个线程中等待多个线程完成任务的类。
----------------------------------------------------------------
Lock与synchronized 的区别
1、lock是一个接口,而synchronized是一个关键字。
2、Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
3、通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
4、Lock可以提高多个线程进行读操作的效率。
ReentrantLock 拥有Synchronized相同的并发性和内存语义,此外还多了 锁投票,定时锁等候和中断锁等候
线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的锁定,
如果使用 synchronized ,如果A不释放,B将一直等下去,不能被中断
如果 使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事情
ReentrantLock获取锁定与三种方式:
a) lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁
b) tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;
c)tryLock(long timeout,TimeUnit unit), 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,
如果获取了锁定,就返回true,如果等待超时,返回false;
d) lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断
5、synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,
但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中
6、在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,
但是ReetrantLock的性能能维持常态;
7、Lock要手动在finally里释放锁,而syncronized不需要
----------------------------------------------------------------------------------------------------------------
synchronized的底层实现主要依靠Lock-Free的队列,基本思路是自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
CAS是乐观锁:每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,
那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。
-----------------------------------------------------------------------------------------
自旋锁
自旋锁是采用让当前线程不停地的在循环体内执行实现的,当循环的条件被其他线程改变时 才能进入临界区。如下
- public class SpinLock {
-
- private AtomicReference<Thread> sign =new AtomicReference<>();
-
- public void lock(){
- Thread current = Thread.currentThread();
- while(!sign .compareAndSet(null, current)){
- }
- }
-
- public void unlock (){
- Thread current = Thread.currentThread();
- sign .compareAndSet(current, null);
- }
- }
使用了CAS原子操作,lock函数将owner设置为当前线程,并且预测原来的值为空。unlock函数将owner设置为null,并且预测值为当前线程。
当有第二个线程调用lock操作时由于owner值不为空,导致循环一直被执行,直至第一个线程调用unlock函数将owner设置为null,第二个线程才能进入临界区。
由于自旋锁只是将当前线程不停地执行循环体,不进行线程状态的改变,所以响应速度更快。但当线程数不停增加时,性能下降明显,
因为每个线程都需要执行,占用CPU时间。如果线程竞争不激烈,并且保持锁的时间段。适合使用自旋锁。
注:该例子为非公平锁,获得锁的先后顺序,不会按照进入lock的先后顺序进行。
-----------------------------------------------------------------------------------------------------------------
类加载器工作机制:
1.装载:将Java二进制代码导入jvm中,生成Class文件。
2.连接:a)校验:检查载入Class文件数据的正确性 b)准备:给类的静态变量分配存储空间 c)解析:将符号引用转成直接引用
3:初始化:对类的静态变量,静态方法和静态代码块执行初始化工作。
双亲委派模型:类加载器收到类加载请求,首先将请求委派给父类加载器完成
用户自定义加载器->应用程序加载器->扩展类加载器->启动类加载器。
------------------------------------------------------------------------------------------------------------
.常用jVM调有工具有哪些(Jstatus,JStack,Jmap等)
--------------------------------------------------------------------------------------------------
Java内存模型:
Java虚拟机规范中将Java运行时数据分为六种。
1.程序计数器:是一个数据结构,用于保存当前正常执行的程序的内存地址。Java虚拟机的多线程就是通过线程轮流切换并分配处理器时间来实现的,
为了线程切换后能恢复到正确的位置,每条线程都需要一个独立的程序计数器,互不影响,该区域为“线程私有”。
2.Java虚拟机栈:线程私有的,与线程生命周期相同,用于存储局部变量表,操作栈,方法返回值。局部变量表放着基本数据类型,还有对象的引用。
3.本地方法栈:跟虚拟机栈很像,不过它是为虚拟机使用到的Native方法服务。
4.Java堆:所有线程共享的一块内存区域,对象实例几乎都在这分配内存。
5.方法区:各个线程共享的区域,储存虚拟机加载的类信息,常量,静态变量,编译后的代码。
6.运行时常量池:代表运行时每个class文件中的常量表。包括几种常量:编译时的数字常量、方法或者域的引用。
堆为什么要分代
让新创建的对象都在young gen里创建,然后频繁收集young gen,则大部分垃圾都能在young GC中被收集掉。
由于young gen的大小配置通常只占整个GC堆的较小部分,而且较高的对象死亡率(或者说较低的对象存活率)让它非常适合使用copying算法来收集,
这样就不但能降低单次GC的时间长度,还可以提高GC的工作效率。
JDK1.8中JVM做了那些改变(主要是撤销了永久代,引入元空间)
-------------------------------------------------------------------------------------------------------------------
JVM中什么时候会进行垃圾回收
首先需要知道,GC又分为minor GC 和 Full Gc(也称为Major GC)。Java 堆内存分为新生代和老年代,新生代中又分为1个Eden区域 和两个 Survivor区域。
那么对于 Minor GC 的触发条件: 大多数情况下,直接在 Eden 区中进行分配 。如果 Eden区域没有足够的空间,那么就会发起一次 Minor GC;
对于 Full GC(Major GC)的触发条件:也是如果老年代没有足够空间的话,那么就会进行一次 Full GC。
----------------------------------------------------------------------------------------------------------------------------
java垃圾回收机制:
根据对象的存活周期,将内存划分为几块。一般是把 Java 堆分为新生代和老年代,这样就可以根据各个年代的特点,采用最适当的收集算法。
新生代:每次垃圾收集时会有大批对象死去,只有少量存活,所以选择复制算法,只需要少量存活对象的复制成本就可以完成收集。
复制算法:将内存划分为8:1:1三部分,较大那份内存交Eden区,其余是两块较小的内存区叫Survior区。每次都会优先使用Eden区,若Eden区满,
就将对象复制到第二块内存区上,
然后清除Eden区,如果此时存活的对象太多,以至于Survivor不够时,会将这些对象通过分配担保机制复制到老年代中。
老年代:对象存活率高、没有额外空间对它进行分配担保,必须使用“标记-清除”或“标记-整理”算法进行回收。
标记-清除:它的思想就是标记哪些要被回收的对象,然后统一回收。这种方法很简单,但是会有两个主要问题:1.效率不高,标记和清除的效率都很低;
2.会产生大量不连续的内存碎片,导致以后程序在分配较大的对象时,由于没有充足的连续内存而提前触发一次GC动作。
标记-整理:该算法主要是为了解决标记-清除,产生大量内存碎片的问题;当对象存活率较高时,也解决了复制算法的效率问题。
它的不同之处就是在清除对象的时候现将可回收对象移动到一端,然后清除掉端边界以外的对象,这样就不会产生内存碎片了。
Minor GC:新生代 GC,指发生在新生代的垃圾收集动作,因为 Java 对象大多死亡频繁,所以 Minor GC 非常频繁,一般回收速度较快。
Full GC:老年代 GC,也叫 Major GC,速度一般比 Minor GC 慢 10 倍以上。
判断一个对象是否存活有两种方法:
1. 引用计数法
所谓引用计数法就是给每一个对象设置一个引用计数器,每当有一个地方引用这个对象时,就将计数器加一,引用失效时,计数器就减一。
当一个对象的引用计数器为零时,说明此对象没有被引用,也就是“死对象”,将会被垃圾回收.
引用计数法有一个缺陷就是无法解决循环引用问题,也就是说当对象A引用对象B,对象B又引用者对象A,那么此时A,B对象的引用计数器都不为零,
也就造成无法完成垃圾回收,所以主流的虚拟机都没有采用这种算法。
2.可达性算法(引用链法)
该算法的思想是:从一个被称为GC Roots的对象开始向下搜索,如果一个对象到GC Roots没有任何引用链相连时,则说明此对象不可用。
虽然这些算法可以判定一个对象是否能被回收,但是当满足上述条件时,一个对象比不一定会被回收。当一个对象不可达GC Root时,
这个对象并不会立马被回收,而是出于一个死缓的阶段,若要被真正的回收需要经历两次标记
--------------------------------------------------------------------------------------------------------------------------
java内存模型(JMM)是线程间通信的控制机制.JMM定义了主内存和线程之间抽象关系。线程之间的共享变量存储在主内存(main memory)中,
每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。
从上图来看,线程A与线程B之间如要通信的话,必须要经历下面2个步骤:
1. 首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去。
2. 然后,线程B到主内存中去读取线程A之前已更新过的共享变量
--------------------------------------------------------------------------------------------------------------------------
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、
备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
----------------------------------------------------------------------------------------------------------------
Java IO中涉及到哪些设计模式
1、适配器模式
//file 为已定义好的文件流
FileInputStream fileInput = new FileInputStream(file);
InputStreamReader inputStreamReader = new InputStreamReader(fileInput);
以上就是适配器模式的体现,FileInputStream是字节流,而并没有字符流读取字符的一些api,因此通过InputStreamReader将其转为Reader子类,
因此有了可以操作文本的文件方法。
2、装饰者模式
BufferedReader bufferedReader=new BufferedReader(inputStreamReader);
构造了缓冲字符流,将FileInputStream字节流包装为BufferedReader过程就是装饰的过程,刚开始的字节流FileInputStream只有read一个字节的方法,
包装为inputStreamReader后,就有了读取一个字符的功能,在包装为BufferedReader后,就拥有了read一行字符的功能。
--------------------------------------------------------------------------------------------------------------------------------------------------------
单例模式有以下特点:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
饿汉式和懒汉式区别
饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,
以后不再改变,所以天生是线程安全的。
一、饿汉式单例
- //饿汉式单例类.在类初始化时,已经自行实例化
- public class Singleton1 {
- private Singleton1() {}
- private static final Singleton1 single = new Singleton1();
- //静态工厂方法
- public static Singleton1 getInstance() {
- return single;
- }
- }
而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。
二、懒汉式单例
- //懒汉式单例类.在第一次调用的时候实例化自己
- public class Singleton {
- private Singleton() {}
- private static Singleton single=null;
- //静态工厂方法
- public static Singleton getInstance() {
- if (single == null) {
- single = new Singleton();
- }
- return single;
- }
- }
它是线程不安全的,并发环境下很可能出现多个Singleton实例,要实现线程安全,有以下三种方式
1、在getInstance方法上加同步
- public static synchronized Singleton getInstance() {
- if (single == null) {
- single = new Singleton();
- }
- return single;
- }
2、双重检查锁定
- public static Singleton getInstance() {
- if (singleton == null) {
- synchronized (Singleton.class) {
- if (singleton == null) {
- singleton = new Singleton();
- }
- }
- }
- return singleton;
- }
3、静态内部类
- public class Singleton {
- private static class LazyHolder {
- private static final Singleton INSTANCE = new Singleton();
- }
- private Singleton (){}
- public static final Singleton getInstance() {
- return LazyHolder.INSTANCE;
- }
- }
---------------------------------------------------------------------------------------------------------------
原型模式的结构
原型模式要求对象实现一个可以“克隆”自身的接口,这样就可以通过复制一个实例对象本身来创建一个新的实例。这样一来,通过原型实例创建新的对象,
就不再需要关心这个实例本身的类型,只要实现了克隆自身的方法,就可以通过这个方法来获取新的对象,而无须再去通过new来创建。
---------------------------------------------------------------------------------------------------------------------------------
观察者模式中,一个被观察者管理所有相依于它的观察者物件,并且在本身的状态改变时主动发出通知。这通常通过呼叫各观察者所提供的方法来实现。
此种模式通常被用来实现事件处理系统。
-------------------------------------------------------------------------------------------------------------------------------------
(Abstract Factory)抽象工厂模式的Java实现
抽象工厂模式(Abstract Factory):为创建一组相关或者互相依赖的对象提供一个接口,而无需指定它们对应的具体类。
缺点:在添加新的产品对象时,难以扩展抽象工厂来生产新种类的产品,这是因为在抽象工厂角色中规定了所有可能被创建的产品集合,
要支持新种类的产品就意味着要对该接口进行扩展,而这将涉及到对抽象工厂角色及其所有子类的修改,显然会带来较大的不便。
------------------------------------------------------------------------------------------------------------
作用:将一个复杂对象的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
Builder模式是把复杂对象的创建和部件的创建分别开来,分别用Builder类和Director类来表示。
一个复杂的对象,不但有很多大量组成部分,如汽车,有很多部件:车轮、方向盘、发动机,还有各种小零件等等,部件很多,但远不止这些,
如何将这些部件装配成一辆汽车,这个装配过程也很复杂(需要很好的组装技术),Builder模式就是为了将部件和组装过程分开。
-------------------------------------------------------------------------------------------------------------
Adapter模式将一个类的接口转换成客户希望的另外一个接口。Adapter 模式使原本由于接口不兼容而不能一起工作的那些类可以一起工作。
适配器模式有类的适配器模式和对象的适配器模式两种不同的形式
类的适配器模式
在上图中可以看出,Adaptee类并没有sampleOperation2()方法,而客户端则期待这个方法。为使客户端能够使用Adaptee类,提供一个中间环节,
即类Adapter,把Adaptee的API与Target类的API衔接起来。Adapter与Adaptee是继承关系,这决定了这个适配器模式是类的:
模式所涉及的角色有:
● 目标(Target)角色:这就是所期待得到的接口。注意:由于这里讨论的是类适配器模式,因此目标不可以是类。
● 源(Adapee)角色:现在需要适配的接口。
● 适配器(Adaper)角色:适配器类是本模式的核心。适配器把源接口转换成目标接口。显然,这一角色不可以是接口,而必须是具体类。
对象适配器模式
与类的适配器模式一样,对象的适配器模式把被适配的类的API转换成为目标类的API,与类的适配器模式不同的是,
对象的适配器模式不是使用继承关系连接到Adaptee类,而是使用委派关系连接到Adaptee类。
从上图可以看出,Adaptee类并没有sampleOperation2()方法,而客户端则期待这个方法。为使客户端能够使用Adaptee类,
需要提供一个包装(Wrapper)类Adapter。这个包装类包装了一个Adaptee的实例,从而此包装类能够把Adaptee的API与Target类的API衔接起来。
Adapter与Adaptee是委派关系,这决定了适配器模式是对象的。
----------------------------------------------------------------------------------------------------------------------------
Bridge(桥接)模式将抽象化与实现化脱耦,使得二者可以独立的变化就是说将他们之间的强关联变成弱关联,
也就是指在一个软件系统的抽象化和实现化之间使用组合/聚合关系而不是继承关系,从而使两者可以独立的变化。
传统做法
桥接模式做法
---------------------------------------------------------------------------------------------------------------------------------------------
定义:动态给一个对象添加一些额外的职责,就象在墙上刷油漆
下面这个例子有助于理解 装饰的流程和作用
现在需要一个汉堡,主体是鸡腿堡,可以选择添加生菜、酱、辣椒等等许多其他的配料,这种情况下就可以使用装饰者模式。
------------------------------------------------------------------------------------------------
代理模式(proxy) 给某一个对象提供一个代理,并由代理对象控制对原对象的引用。
----------------------------------------------------------------------------------------------------------
为子系统中的一组接口提供一个统一接口。Facade模式定义了一个更高层的接口,使子系统更加容易使用。
-------------------------------------------------------------------------------------------------------------------
命令模式(commond)将请求封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式支持可撤销的操作。
-----------------------------------------------------------------------------------------------------------------------
中介者模式(Mediator Pattern)定义一个中介对象来封装系列对象之间的交互。中介者使各个对象不需要显示地相互引用,
从而使其耦合性松散,而且可以独立地改变他们之间的交互。
原始交流方式
引入中介QQ的交流方式
--------------------------------------------------------------------------------------------------------------------------------
模板方法template模式定义一个操作中的算法的骨架,而将步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义算法的某些特定步骤。
----------------------------------------------------------------------------------------------------------------------------
责任链模式避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,
直到有对象处理它为止,这就是职责链模式。
----------------------------------------------------------------------------------------------------------------------------------------------------
组合模式组合多个对象形成树形结构以表示“整体-部分”的结构层次。
----------------------------------------------------------------------------------------------------------------------------------------------------
Strategy(策略)模式也叫策略模式是行为模式之一,它对一系列的算法加以封装,为所有算法定义一个抽象的算法接口,
并通过继承该抽象算法接口对所有的算法加以封装和实现,具体的算法选择交由客户端决定(策略)。Strategy模式主要用来平滑地处理算法的切换
------------------------------------------------------------------------------------------------------------------------------------------
Iterator模式也叫迭代模式,是行为模式之一,它把对容器中包含的内部对象的访问委让给外部类,使用Iterator按顺序进行遍历访问的设计模式。
简单地说,Iterator模式提供一种有效的方法,可以屏蔽聚集对象集合的容器类的实现细节,而能对容器内包含的对象元素按顺序进行有效的遍历访问。
--------------------------------------------------------------------------------------------------------------------------------------------------
response.setContentType()方法来指定jsp页面显示的编码格式
---------------------------------------------------------------------------------------------------------------
Ajax(Asynchronous javascript and xml)主要目的:不断刷新页面的情况下通过与服务器进行少量数据交互来提高页面的交互性,减少相应时间,从而改善用户的体验.
--------------------------------------------------------------------------------------------------------------------------
当forward方式可以满足需求时,尽可能使用forward方式,但在有些情况.例如,需要跳转到一个其他服务器资源,则必须用redirect
---------------------------------------------------------------------------------------------------------------------------------
get主要用来获取服务器端的资源信息,post同时还可以向服务器上传数据.
不用get传数据的两个原因
1.上传大小限制在1024byte左右
2.get方法上传的数据添加在url中,因此上传的数据被彻底暴漏.
如果请求是get,调用doget()方法,
如果请求是post,调用dopost()方法
---------------------------------------------------------------------------------------------------------------------------------
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。