之前大的系统结构有些知识点总结得并不好,总是将自己看的想的拼凑在一起,准备调整一下先对一些知识点整理一下再从一个面上去叙说,在继续准备秋招的路上遇到没有理解清楚的东西或者看到了有了新的认识的知识,再写一次。希望自己理解并记住。
目录
(一)什么是多态
(二)String字符串常量 StringBuffer线程安全 StringBuilder线程非安全
(三)进程与线程
(四)HTTP四种方法 Put(增),Delete(删),Post(改),Get(查)
(五)JAVA的事件机制
(五)回调机制
(6)为什么要用线程池 线程池的作用 线程池常见的使用场景及类型
(7)线程池都有哪几种工作队列,怎么理解无界队列与有界队列、线程池中的几种重要的参数及流程说明
多态是同一个行为具有不同表现形式和形态的能力,多态就是同一个接口使用不同实例而执行不同的操作。
举例比如同样的打印东西,彩印的打印效果是彩色的,而黑白打印机的打印效果是黑白的。(取决于对象的不同)(多态:动态绑定是指在执行期间判断锁应用对象的实际类型,根据其实际的类型调用其相应方法。)
多态存在的三个必要条件:1、要有继承、2、要有重写、3、父类引用指向子类对象。
JAVA中实现多态的方式:通过接口实现、继承父类进行方法重写、同一个类中进行方法重载。
String类型与StringBuffer类型主要性能区别其实就在与String是不可变对象,因为再每次对String对象进行改变的时候,其实都等同生成了一个新的String对象,然后将指针指向新的String对象,所以经常改变内容的字符串最好不要用string,因为这样每次生成的对象对系统性能都会产生影响,特别当内存无用引用多了之后,JVM的GC就开始工作。而StringBuffer类不同,他每次都是会对自身进行操作,而不是生成新的对象,再改变对象引用。所以一般情况下我们推荐使用StringBuffer,特使stringbuffer经常改变的情况下。
例如 String S1 = “This is only a” + “ simple” + “ test”;
StringBuffer Sb = new StringBuilder(“This is only a”).append(“ simple”).append(“ test”);
S1的生成速度比stringbuffer快很多,这是因为JVM将其认为是String的一个对象而不是三个,如果分别将其用
String S2 = “This is only a”;
String S3 = “ simple”;
String S4 = “ test”;
String S1 = S2 +S3 + S4; 这个时候string创建的方式就并不占优势 了。
Stringbuffer就是线程安全的可变字符序列,类似于Stirng的字符串缓冲区。
StringBuilder一个类似于Stringbuffer的api,但是不保证同步。在单线程条件下取消了stringbuffer中的synchronize的重量级锁操作。建议优先使用这种。
几乎所有的操作系统都支持多个任务,通常一个任务就是一个程序,而一个程序就是一个进程。当一个进程运行时,内部可能包含多个顺序执行流,每个书序执行流就一个线程。
进程:进程是指处于运行过程中的程序,并且具有一定的独立的功能。进程是系统进行资源分配和调用的基本单位。
1、独立性:进程在独立存在的实体,他可以独立拥有资源,每一个进程都有自己独立的地址空间,没有进程本身的运行,用户进程不可以直接访问其他进程的地址空间,
2、动态性:进程有自己生命周期和各种不同状态。
3、并发性:多个进程可以在单个处理上并发执行。(多个进程命令被快速的轮换执行,是的宏观上多个进程同时执行的效果)
线程:
线程是进程的组成部分,一个进程可以拥有多个线程,而一个线程必须拥有一个父进程。线程可以拥有自己的堆栈和程序计数器以及局部变量。但不能拥有系统资源。 线程特点:可以完成一定任务,和其他线程共享父进程的环境变量和部分环境协作完成任务。线程是独立运行的,其不知道线程中是否还有其他线程存在。线程的执行时抢占式的,就是说线程可以被随机挂起,以便另一个线程运行。
(1)就绪状态,除了CPU其他资源以及九尾,可以随时立即执行。
(2)执行状态,正在运行中
(3)阻塞状态正在执行的进程,由于等待某个事件而无法执行。
阻塞的三种情况:(1)等待阻塞:运行的线程执行wait方法,该线程会释放占有的资源,JVM会将其放入等待池中,这里状态下不能自动唤醒,需要其他线程的notify或者notifyall才可以。
(2)同步阻塞:运行的线程获取对象的同步锁,若该同步锁被其他线程所占用,JVM会把该线程放入锁池。
(3)其他阻塞:运行线程执行sleep或者join,JVM会把其置为阻塞状态,当sleep状态超时,join等待线程终止或者超时,线程重新转入就绪状态。
临界区:保证同一个时刻只有一个线程能够访问数据的简便方法,在任意时刻只允许一个线程对共享资源进行访问。如果多个线程访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。
互斥量;互斥量跟临界区很相似,只有拥有互斥对象的线程才具有访问资源的权限,由于互斥对象只有一个,因此就决定了任何情况下此共享资源都不会同时被多个线程所访问。当前占据资源的线程在任务处理完后应将拥有的互斥对象交出,以便其他线程在获得后得以访问资源。互斥量比临界区复杂。因为使用互斥不仅仅能够在同一应用程序不同线程中实现资源的安全共享,而且可以在不同应用程序的线程之间实现对资源的安全共享。
信号量:信号量对象对线程的同步方式与前面几种方法不同,信号允许多个线程同时使用共享资源,这与操作系统中的PV操作相同。它指出了同时访问共享资源的线程最大数目。它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。在用CreateSemaphore()创建信号量时即要同时指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就会减1,只要当前可用资源计数是大于0的,就可以发出信号量信号。但是当前可用计数减小到0时则说明当前占用资源的线程数已经达到了所允许的最大数目,不能在允许其他线程的进入,此时的信号量信号将无法发出。线程在处理完共享资源后,应在离开的同时通过ReleaseSemaphore()函数将当前可用资源计数加1。在任何时候当前可用资源计数决不可能大于最大资源计数。
事件:事件对象也可以通过通知操作的方式来保持线程的同步。并且可以实现不同进程中的线程同步操作。
总结:
1. 互斥量与临界区的作用非常相似,但互斥量是可以命名的,也就是说它可以跨越进程使用。所以创建互斥量需要的资源更多,所以如果只为了在进程内部是用的话使用临界区会带来速度上的优势并能够减少资源占用量。因为互斥量是跨进程的互斥量一旦被创建,就可以通过名字打开它。
2. 互斥量(Mutex),信号灯(Semaphore),事件(Event)都可以被跨越进程使用来进行同步数据操作,而其他的对象与数据同步操作无关,但对于进程和线程来讲,如果进程和线程在运行状态则为无信号状态,在退出后为有信号状态。所以可以使用WaitForSingleObject来等待进程和线程退出。
3. 通过互斥量可以指定资源被独占的方式使用,但如果有下面一种情况通过互斥量就无法处理,比如现在一位用户购买了一份三个并发访问许可的数据库系统,可以根据用户购买的访问许可数量来决定有多少个线程/进程能同时进行数据库操作,这时候如果利用互斥量就没有办法完成这个要求,信号灯对象可以说是一种资源计数器。
HTTP请求:第一行 请求行 request Line 用来说明请求类型
第二行:request header 首部 用来说明服务器使用的附加信息
第三行:空行
第四行:任意的其他数据称之为主体body
GET/POST 区别:(1) GET提交吧请求数据翻入URL中以?分割URL和数据传输,多个参数之间用&,BASE64加密。。
POST提交吧数据放入Http的包体中,get数据会在地址栏显示,post提交地址栏不会改变。
(2)传输数据的大小:首先声明http现已没有对数据大小限制也没对url有限制。实际过程中浏览器对URL的长度有限制,get提交时传输的数据收到url长度限制。POST由于不是URL传值,理论上数据不受限,但实际上也有一定限制。
JAVA的事件机制包括三个部分,分别为事件、事件监听器、事件源。
1、事件本身、 一般继承与java.util.EventObject类,创建一个事件类CusEvent 继承与EventObject。用于封装与事件源相关的一些参数。(本体)
/**
* 事件类,用于封装事件源及一些与事件相关的参数.
* @author Eric
*/
public class CusEvent extends EventObject {
private static final long serialVersionUID = 1L;
private Object source;//事件源
public CusEvent(Object source){
super(source);
this.source = source;
}
public Object getSource() {
return source;
}
public void setSource(Object source) {
this.source = source;
}
}
2、事件监听器,实现java.util.EventListener接口,注册事件源上当事件源的属性或者状态改变的时候,监听器就调用内部的回调方法。(事件源 EventSourceObject)(本体的监听者)
public class CusEventListener implements EventListener {
//事件发生后的回调方法
public void fireCusEvent(CusEvent e){
EventSourceObjecteObject = (EventSourceObject)e.getSource();
System.out.println("My name has been changed!");
System.out.println("I got a new name,named \""+eObject.getName()+"\""); }
}
3、事件源:事件发生的地方,由于事件源的某项属性或者状态发生了变化,比如button被单机导致某一项事件的发生,换句话说就是生成相应的事件对象。因为事件监听器要注册到事件源身上,所以事件源中应该要有装着监听器的容器LIst或者set(本体的发生地)
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* 事件源.
* @author Eric
*/
public class EventSourceObject {
private String name;
//监听器容器
private Set listener;
public EventSourceObject(){
this.listener = new HashSet();
this.name = "defaultname";
}
//给事件源注册监听器
public void addCusListener(CusEventListener cel){
this.listener.add(cel);
}
//当事件发生时,通知注册在该事件源上的所有监听器做出相应的反应(调用回调方法)
protected void notifies(){
CusEventListener cel = null;
Iterator iterator = this.listener.iterator();
while(iterator.hasNext()){
cel = iterator.next();
cel.fireCusEvent(new CusEvent(this));
}
}
public String getName() {
return name;
}
//模拟事件触发器,当成员变量name的值发生变化时,触发事件。
public void setName(String name) {
if(!this.name.equals(name)){
this.name = name;
notifies();
}
}
}
public class MainTest {
/**
* @param args 测试方法
*/
public static void main(String[] args) {
EventSourceObject object = new EventSourceObject();
//注册监听器
object.addCusListener(new CusEventListener(){
@Override
public void fireCusEvent(CusEvent e) {
super.fireCusEvent(e);
}
});
//触发事件
object.setName("eric");
}
}
模块间相互调用分为三种模式(1)、同步调用:同步调用是最简单的一种调用方式,类A的方法a调用类B的方法b,一直等待b方法执行完毕,a方法继续往下走。这种调用方式适用于方法b执行时间不长的情况。(2)异步调用,为了解决同步调用可能出现阻塞,类A的方法a通过新线程的方式调用类B的方法b,代码接着往下执行。可以通过类似Callable+FutureTask的方式。
public static class CallableThread implements Callable{
public String call() throws Exception{
System.out.println("开始睡觉")
Thread.sleep1(10000);
return "123";
}
public static void main(String[] args) throws Exception{
ExectorService ex = Executors.newCachedThreadPool();
CallableThread ct = new CallableThread();
FutureTask f = new FutureTask(ct);
es.submit(f);
es.shutdown();
Thread.sleep(5000);
System.out.println("主线程等待5s");
String str = f.get();
System.out.println("Future 已拿到数据 str="+str);
}
}
(3)回调:类A的a()方法调用类B的b方法,类B的b()方法执行完毕主动调用a的callback,这种调用的方式组成的双向的调用。
//回调接口
public interface Callback{
public void tellAnswer(int answer);
}
//定义老师对象实现Callback接口
public class Teacher implements Callback{
private Student student;
public Teacher(Student student){
this.student = student;
}
//学生问问题
public void askQuestion(){
student.resolveQuestion(this);
}
//这是回调接口是学生回答完毕问题后老师知道的事情
@Override
public void tellAnswer(int answer){
System.out.println("知道了答案")
}
}
//学生接口
public interface Student{
public void resolveQuestion(Callback callback){
@Override
public void resolveQuestion(Callback callback){
try{
Thread.sleep(3000);
}catch(InterruptedException e){
}
//回调 告诉老师解决了多久
callback.tellAnswer(3);
}
}
}
//测试
public class CallbackTest {
@Test
public void testCallback() {
Student student = new Ricky();
Teacher teacher = new Teacher(student);
teacher.askQuestion();
}
}
所谓回调就是,Teacher有一个子类student作为引用,由构造函数传入*(传值)。然后老师调用学生接口的方法resolveQuestion向学生提问,老师解决完毕问题之后调用学生的回调方法tellAnswer。实际上就是对老师进行抽象,对于学生来说就是不需要关心哪一位老师询问我,我只需要将询问的问题提出,得到答案,然后告诉提问老师就可以了。还有学生的抽象,就是对于老师来说学生也是非常灵活,以为老师未必对一个学生进行提问,可能同时对三个学生进行提问,这样就可以将成员变量Student改为List
java.util.concurrent,Exector接口用于创建线程池 执行器
使用线程池的原因:多线程技术主要解决处理器单元内多个线程执行的问题,他可以显著的减少处理器单元的闲置时间,增加处理器单元的吞吐能力。 举例:例如一个服务器完成一项任务所需时间为,T1创建线程的时间,T2在线程中执行任务的时间、T3销毁线程的时间,T1+T3远大于T2则可以采用线程池,可以提高服务器性能。 作用:线程池的技术正是关注如何缩短和调整T1与T3的时间,从而提高服务器程序的性能。他把T1与T3分别安排在服务器程序的启动和结束的时间段或者一些空闲的时间段,这样服务器在处理客户端请求时就不会有T1与T3的开销。
1、减少了创建与销毁线程的次数,每个先吃都可以被重复利用,可以执行多个任务。2、可以根据系统的承受能力,调整线程池中的工作线程的数目,防止因为消耗过多的内存。
线程池的组成:1、线程池管理器(ThreadPool)用于创建并管理线程池,包括创建线程池,销毁线程池、添加新任务。2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务。2、工作线程(poolWorker)线程池中线程,在没有任务时处于等待状态,可以循环的执行任务。3、任务接口(Task)每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完的收尾工作,任务的执行状态。4、任务队列(taskQueue)用于存放没有处理的任务,提供缓冲机制。
常见线程池:1、newSingleThreadExector 单个线程的线程池,即线程池中每次只有线程工作,单线程串行任务。2、newFixedThreadExector 固定数量的线程池,没提交一个任务就是一个线程,直到达到线程池的最大数量,然后后面进入等待队列直到前面的任务才继续执行(线程执行异常而结束后,会补充一个线程)。 3、newCacheThreadExector 可缓存线程池,当线程池大小超过了处理任务所需的线程,就会回收一部分线程。(大小依赖JVM)4、newScheduleThreadExector 大小无限制的线程池,支持定时和周期性的执行线程。
无队列,或者 有界队列、无解队列。 有界队列:1、初始的poolSize 提交runable任务,会直接作为new一个Thread的参数,当任务提交数目超过了corePoolSize会将后序的任务放入一个block queue中,如果有界队列也满了,那么就会紧急创建Thread救急,如果3也无法处理,那么走到第四步执行reject操作。 无界队列:与有界队列相比,除非系统资源耗尽,否则无界的任务队列不存在任务入队失败的如果任务创建速度和处理速度差异比较大,无界队列会保持快速增长,直到耗尽系统的内存。
corePoolSize 核心池的大小,当任务来了,就会创建一个线程去执行任务,当线程池中的线程数据达到这个值,就会把后续任务放入缓存队列中。
maxmumPoolSize 线程池最大线程数目,他表示线程池中最多创建多少个线程;KeepAliveTime表示线程没有任务执行时最多保持多久时间会终止。
keepAliveTime 表示线程没有任务执行时保持多久就会终止
unit 参数keepAliveTime的时间单位,天时分秒等
workQueue 阻塞队列,用于存储等待执行的任务。有三种如下
ArrayBlockingQueue有界的缓存等待,这是基于数组的阻塞队列,内部维持一个定长的数据缓存队列。生产者和消费对数据处理公用一个锁对象,因此两者无法真正的并行运行,这点尤其不同于下者,优点是读写获取的时候非常轻巧,前者在插入与删除元素不会产生或销毁任何额外的对象实例,后者会生成一个额外的Node对象。这对GC的影响还是有的。
LinkedBlockingQueue 基于链表的阻塞队列,内部维持着一个数据缓冲队列,当生产者往队列中放入一个数据时,队列会从生产者手中获取数据,并缓存在队列内部,而生产者立即返回;只有当队列缓冲区达到最大容量就会阻塞生产者队列,直到消费者消费掉一部分数据,生产者会被继续唤醒。LinkedBlockingQueue之所以能够高效处理并发数据,还因为就是生产者和消费者分别采用了独立的锁来控制数据同步,也就意味着高并发情况下生产者和消费者可以并行操作数据,以此来提高整个队列的并发性能。
SynchronousQueue是无缓冲无界,必须某次元素添加后被取走才能继续添加,他有一个缓存值一般为1。