Java之web项目问答:Java基础

Java软件开发工程师的项目经验集锦:

1Java基础

1.1 CollectionMap

(1)掌握CollectionMap的继承体系。

(2)掌握ArrayList(数组)LinkedList(链表)Vector(线程同步)Stack(继承Vector先进后出的栈)PriorityQueue(优先级堆的极大优先级队列)HashSetLinkedHashSet(链表+hash)TreeSet(Comparator比较器)HashMapLinkedHashMap(链表)TreeMap(Comparator比较器)WeakHashMap(弱键à即没用了就自动被GC回收)EnumMap(key不能为空,内部以数组实现,性能更好)HashTable(线程同步)的特点和实现原理。

补充☆(3)掌握CopyOnWriteArrayListCopyOnWriteArraySetConcurrentHashMap的实现原理和适用场景。

CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

CopyOnWrite容器有很多优点,但是同时也存在两个问题,即内存占用问题和数据一致性问题。所以在开发的时候需要注意一下。

  内存占用问题。因为CopyOnWrite的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象(注意:在复制的时候只是复制容器里的引用,只是在写的时候会创建新对象添加到新容器里,而旧容器的对象还在使用,所以有两份对象内存)。如果这些对象占用的内存比较大,比如说200M左右,那么再写入100M数据进去,内存就会占用300M,那么这个时候很有可能造成频繁的Yong GCFull GC。之前我们系统中使用了一个服务由于每晚使用CopyOnWrite机制更新大对象,造成了每晚15秒的Full GC,应用响应时间也随之变长。

  针对内存占用问题,可以通过压缩容器中的元素的方法来减少大对象的内存消耗,比如,如果元素全是10进制的数字,可以考虑把它压缩成36进制或64进制。或者不使用CopyOnWrite容器,而使用其他的并发容器,如ConcurrentHashMap

  数据一致性问题CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。

 

1.2 IO

掌握InputStreamOutputStreamReaderWriter的继承体系。

 

IO流中的设计模式

1.IO中用到的适配器模式

IO中,如将字符串数据转变成字节数据保存到文件中,将字节数据转变成流数据等都用到了适配器模式,下面以InputStreamReaderOutputStreamWriter类为例介绍适配器模式。

 InputStreamReaderOutputStreamWriter类分别继承了ReaderWriter接口,但要创建它们必须在构造函数中传入一个InputStreamOutputStream的实例,InputStreamReaderOutputStreamWriter的作用也就是将InputStreamOutputStream适配到ReaderWriter

  InputStreamReader实现了Reader接口,并且持有了InputStream的引用,这是通过StreamDecoder类间接持有的,因为bytechar要经过编码。

       这里,适配器就是InputStreamReader类,而源角色就是InputStream代表的实例对象,目标接口就是Reader类,OutputStreamWriter类也是类似的方式。

       IO中类似的还有,如StringReader将一个String类适配到Reader接口,ByteArrayInputStream适配器将byte数组适配到InputStream流处理接口。

2.IO中用到的装饰模式

        装饰模式就是对一个类进行装饰,增强其方法行为,在装饰模式中,作为原来的这个类使用者还不应该感受到装饰前与装饰后有什么不同,否则就破坏了原有类的结构了,所以装饰器模式要做到对被装饰类的使用者透明,这是对装饰器模式的一个要求。总之装饰器设计模式就是对于原有功能的扩展

IO中有许多不同的功能组合情况,这些不同的功能组合都是使用装饰器模式实现的,下面以FilterInputStream为例介绍装饰器模式的使用。

InputStream类就是以抽象组件存在的,而FileInputStream就是具体组件,它实现了抽象组件的所有接口,FilterInputStream类就是装饰角色,它实现了InputStream类的所有接口,并持有InputStream的对象实例的引用,BufferedInputStream是具体的装饰器实现者,这个装饰器类的作用就是使得InputStream读取的数据保存在内存中,而提高读取的性能。类似的还有LineNumberInputStream类,它的作用是提高按行读取数据的功能。

 

总结

    这两种设计模式看起来都是起到包装一个类或对象的作用,但是使用它 们的目的却不尽相同。适配器模式主要在于将一个接口转变成另一个接口它的目的是通过改变接口来达到重复使用的目的;而装饰器模式不是要改变被装饰对象的接口,而是保持原有的接口但是增强原有对象的功能,或改变原有对象的方法而提高性能

补充☆(3) 高性能的IO体系

首先得明白什么是同步,异步,阻塞,非阻塞.

1,同步和异步是针对应用程序和内核的交互而言的

2,阻塞和非阻塞是针对于进程在访问数据的时候,根据IO操作的就绪状态来采取的不同方式

总结一句简短的话,同步和异步是目的,阻塞和非阻塞是实现方式。

名词解释 

同步 指的是用户进程触发IO操作并等待或者轮询的去查看IO操作是否就绪 自己上街买衣服,自己亲自干这件事,别的事干不了。 
2) 异步 异步是指用户进程触发IO操作以后便开始做自己的事情,而当IO操作已经完成的时候会得到IO完成的通知(异步的特点就是通知) 告诉朋友自己合适衣服的尺寸,大小,颜色,让朋友委托去卖,然后自己可以去干别的事。(使用异步IO时,JavaIO读写委托给OS处理,需要将数据缓冲区地址和大小传给OS 
3) 阻塞 所谓阻塞方式的意思是指, 当试图对该文件描述符进行读写时, 如果当时没有东西可读,或者暂时不可写, 程序就进入等待 状态, 直到有东西可读或者可写为止 去公交站充值,发现这个时候,充值员不在(可能上厕所去了),然后我们就在这里等待,一直等到充值员回来为止。(当然现实社会,可不是这样,但是在计算机里确实如此。) 
4) 非阻塞 非阻塞状态下, 如果没有东西可读, 或者不可写, 读写函数马上返回, 而不会等待 银行里取款办业务时,领取一张小票,领取完后我们自己可以玩玩手机,或者与别人聊聊天,当轮我们时,银行的喇叭会通知,这时候我们就可以去了。

同步阻塞IOJAVA BIO): 
    同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。 
同步非阻塞IO(Java NIO)  同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。用户进程也需要时不时的询问IO操作是否就绪,这就要求用户进程不停的去询问。 
异步阻塞IOJava NIO):  
   此种方式下是指应用发起一个IO操作以后,不等待内核IO操作的完成,等内核完成IO操作以后会通知应用程序,这其实就是同步和异步最关键的区别,同步必须等待或者主动的去询问IO是否完成,那么为什么说是阻塞的呢?因为此时是通过select系统调用来完成的,而select函数本身的实现方式是阻塞的,而采用select函数有个好处就是它可以同时监听多个文件句柄(如果从UNP的角度看,select属于同步操作。因为select之后,进程还需要读写数据),从而提高系统的并发性!  
Java AIO(NIO.2))异步非阻塞IO:  
   在此种模式下,用户进程只需要发起一个IO操作然后立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的IO读写操作,因为真正的IO读取或者写入操作已经由内核完成了。

 

1.3 异常

掌握Throwable继承体系。

Java异常体系:

Throwable(异常和错误的顶层父类)

  |----- Error:错误类。表示的是程序在运行过程中出现的错误。错误的发生都属于系统级别。(JVM是运行操作系统上,JVM操作内存是需要借助操作系统,如果内在发生错误,会由操作系统反馈给JVM)

             通常在程序中发生错误的原因是因为程序在书写时存在问题,而JVM运行有问题的代码就会引发内存出错。解决错误的方案:修改源代码

  |----- Exception:异常类。程序在运行过程中,出现了异常现象:数组越界、类型转换异常等。通常在程序中如果发生了异常,是有专门针对异常处理的方案(处理方案是由开发人员自己制定)

                 程序中异常的发生通常是因为程序在操作数据时引发的,解决异常的方案是:声明、捕获

                 (学习以Exception为主,开发也主要以Exception为主)

            提示:声明最终还需要使用捕获来处理异常

异常类的分类:

运行时异常:RuntimeException 

 

编译时异常:Exception

异常的处理

声明:其实就是程序中遇到异常时,自己不处理,交给其它程序处理

关键字:throws

捕获:其实就是在程序中遇到异常时,不会交给其它程序处理,自己处理

关键字:try 、catch、finally

注意:1,在使用throw抛出异常代码的后面,不能书写任意代码。

      2,如果使用try...catch...finally结构的,catch中抛出异常后面如果有其他语句,执行时先执行finally语句再去执行catch中的其他语句

      3try..catch..catch结构中必须按照子类到父类的顺序写

 

 

1.4 多线程

(1) 掌握Executors可以创建的三种线程池的特点及适用范围。

1.继承Thread类,重写父类run()方法

2.实现runnable接口

3.使用ExecutorServiceCallableFuture实现有返回结果的多线程(JDK5.0以后)

(2) 多线程同步机制。

在需要同步的方法的方法签名中加入synchronized关键字。

使用synchronized块对需要进行同步的代码段进行同步。

使用JDK 5中提供的java.util.concurrent.lock包中的Lock对象。

一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,在 java里边就是拿到某个同步对象的锁(一个对象只有一把锁); 如果这个时候同步对象的锁被其他线程拿走了,他(这个线程)就只能等了(线程阻塞在锁池 等待队列中)。 取到锁后,他就开始执行同步代码(被synchronized修饰的代码);线程执行完同步代码后马上就把锁还给同步对象,其他在锁池中 等待的某个线程就可以拿到锁执行同步代码了。这样就保证了同步代码在统一时刻只有一个线程在执行。

(3)线程的几种可用状态。

线程在执行过程中,可以处于下面几种状态:

就绪(Runnable):线程准备运行,不一定立马就能开始执行

运行中(Running)进程正在执行线程的代码。

等待中(Waiting):线程处于阻塞的状态,等待外部的处理结束。

睡眠中(Sleeping):线程被强制睡眠

I/O阻塞(Blocked on I/O)等待I/O操作完成

同步阻塞(Blocked on Synchronization)等待获取锁

死亡(Dead)线程完成了执行

(4)什么是死锁(deadlock)

两个进程都在等待对方执行完毕才能继续往下执行的时候就发生了死锁。结果就是两个进程都陷入了无限的等待中

(5)如何确保N个线程可以访问N个资源同时又不导致死锁?

使用多线程的时候,一种非常简单的避免死锁的方式就是:指定获取锁的顺序,并强制线程按照指定的顺序获取锁。因此,如果所有的线程都是以同样的顺序加锁和释放锁,就不会出现死锁了。

 

猜你喜欢

转载自blog.csdn.net/qq_43532342/article/details/83653839