电子商务软件开发编程语言的知识整理
树图思维导图提供 软件开发语言知识梳理 在线思维导图免费制作,点击“编辑”按钮,可对 软件开发语言知识梳理 进行在线思维导图编辑,本思维导图属于思维导图模板主题,文件编号是:38d110b2645f89e5f7a89aeebaf3d172
知识梳理思维导图模板大纲
java 堆是jvm主要的内存管理区域,里面存放了大量是对象实例以及数组
垃圾回收算法
标记回收对象
引用计算法:
给对象添加一个引用计数器,有地方引用就+1,引用失效就-1,计数器为0 就不在引用
缺点:很难解决对象之间的相互引用
可达性性分析:
从一系列称为“GC ROOT”的对象作为起点,从这些节点开始向下搜索,如果从“GC ROOT” 到一个对象不可达,则证明 这个对象不可用
可以做“GC ROOT” 的有:
虚拟机栈中的引用对象
本地方法栈中的引用对象(Native)
方法区中类静态常量引用的对象
方法区中常量引用的对象
对于java程序而言,对象基本都位于堆内存中,简单来说GC ROOT 就是有被堆外区域引用的对象
四种引用
强引用
只有强引用对象不使用或者超过生命周期,GC 就可以回收这个对象
软引用
内存充足的时候不会回收,不足的时候尝试回收
弱引用
只要垃圾回收器线程扫到 只具有弱引用对象,就会回收
虚引用
在任何时候都可能被垃圾回收器回收
垃圾收集算法:
标记--清除
效率不高,容易灿盛大量不连续的内存碎片
复制算法:
标记--整理算法
分代收集算法:降java堆分为新生代或者老年代,根据其各自的特点采用最适当的手机算法
程序计数器:
此内存区域是唯一一个在 Java 虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域。
JVM就是通过读取程序计数器的值来决定下一条需要执行的字节码指令,进而进行选择语句、循环、异常处理等
java虚拟机栈
本地方法栈
java堆
方法区
属于共享内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
对象的创建:
遇到new指令,首先检查这个指令的参数是否能正常在常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已经被加载、解析和初始化。如果没有就执行相应的类加载
类加载检查通过后,为新的对象分配内存(内存大小在类的加载完成后便可以确认),在堆的空闲内存中划分一块区域
内存空间分配完成后会初始化0(不包过对象头)接下来就是填充对象头,把对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息存入对象头。
执行 new 指令后执行 init 方法后才算一份真正可用的对象创建完成。
虚拟机类的加载机制
虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验、装换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型。
在 Java 语言中,类型的加载、连接和初始化过程都是在程序运行期间完成的。
类的加载时机:
加载->验证->准备->解析(其中加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的。解析阶段可以在初始化之后再开始(运行时绑定或动态绑定或晚期绑定)。)->初始化->使用->卸载
验证:是连接的第一步,确保 Class 文件的字节流中包含的信息符合当前虚拟机要求。
准备:这个阶段正式为类分配内存并设置类变量初始值,内存在方法去中分配(含 static 修饰的变量不含实例变量)。
解析:这个阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
初始化:前面过程都是以虚拟机主导,而初始化阶段开始执行类中的 Java 代码。
类加载器:通过一个类的全限定名来获取描述此类的二进制字节流。
数组类的特殊性:数组类的本身不通过类的加载器创建,它是由java虚拟机直接创建的。但数组类与类加载器仍然有很密切的关系,因为数组类的元素类型最终是要靠类加载器去创建的
Iterator 和 ListIterator 有什么区别?
Iterator 可以遍历 Set 和 List 集合,而 ListIterator 只能遍历 List。
Iterator 只能单向遍历,而 ListIterator 可以双向遍历(向前/后遍历)。
ListIterator 实现 Iterator 接口,然后添加了一些额外的功能,比如添加一个元素、替换一个元素、获取前面或后面元素的索引位置。
ArrayList
ArrayList 不是线程安全的,如果遇到多线程场景,可以通过 Collections 的 synchronizedList 方法将其转换成线程安全的容器后再使用
Set和List对比
Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。
List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变
在 Queue 中 poll()和 remove()有什么区别?
相同点:都是返回第一个元素,并在队列中删除返回的对象。
不同点:如果没有元素 poll()会返回 null,而 remove()会直接抛出 NoSuchElementException 异常。
HashMap
jdk1.8之后:
相比于之前的版本,jdk1.8在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。
引入红黑树,是因为避免单条链表过程而影响查询效率
什么是hash?
简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。
为什么HashMap中String、Integer这样的包装类适合作为K?
String、Integer等包装类的特性能够保证Hash值的不可更改性和计算准确性,能够有效的减少Hash碰撞的几率
如果使用Object作为HashMap的Key,应该怎么办呢?
重写hashCode()和equals()方法
重写hashCode()是因为需要计算存储数据的存储位置,需要注意不要试图从散列码计算中排除掉一个对象的关键部分来提高性能,这样虽然能更快但可能会导致更多的Hash碰撞;
重写equals()方法,需要遵守自反性、对称性、传递性、一致性以及对于任何非null的引用值x,x.equals(null)必须返回false的这几个特性,目的是为了保证key在哈希表中的唯一性;
ConcurrentHashMap
1.7之前是分段锁,1.8是synchronized 和 CAS 来操作
1.7:
首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。
在JDK1.7中,ConcurrentHashMap采用Segment + HashEntry的方式进行实现
一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个HashEntry数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment的锁。
该类包含两个静态内部类 HashEntry 和 Segment ;前者用来封装映射表的键值对,后者用来充当锁的角色;
Segment 是一种可重入的锁 ReentrantLock,每个 Segment 守护一个HashEntry 数组里得元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment 锁。
可重入锁:重入锁已线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象的锁,而其他线程不可以。ReentrantLock和synchronized都是可重入锁
comparable 和 comparator的区别?
comparable接口实际上是出自java.lang包,它有一个 compareTo(Object obj)方法用来排序
comparator接口实际上是出自 java.util 包,它有一个compare(Object obj1, Object obj2)方法用来排序
TreeMap 和 TreeSet 在排序时如何比较元素?Collections 工具类中的 sort()方法如何比较元素?
TreeSet 要求存放的对象所属的类必须实现 Comparable 接口,该接口提供了比较元素的 compareTo()方法,当插入元素时会回调该方法比较元素的大小。TreeMap 要求存放的键值对映射的键必须实现 Comparable 接口从而根据键对元素进 行排 序。
Collections工具类的sort方法有两种重载的形式,第一种要求传入的待排序容器中存放的对象必须实现Comparable接口以实现元素的比较;第二种不强制性的要求容器中的元素必须可比较,但是要求传入第二个参数,参数是Comparator接口的子类型(需要重写compare方法实现元素的比较),相当于一个临时定义的排序规则,其实就是通过接口注入比较元素大小的算法,也是对回调模式的应用(Java中对函数式编程的支持)
JDK Atomic开头的原子类、synchronized、LOCK,可以解决原子性问题
synchronized、volatile、LOCK,可以解决可见性问题
Happens-Before 规则可以解决有序性问题
线程的创建四种方式:
继承Thread类
定义一个Thread类的子类,重写run方法,将相关逻辑实现,run方法就是线程要执行的业务逻辑代码
创建自定义的线程子类对象
调用子类的实例方法start,启动线程
实现Runnable接口
定义Runnable接口实现类MyRunnable,并重写run()方法
创建MyRunnable实例myRunnable,以myRunnable作为target创建Thead对象,该Thread对象才是真正的线程对象
调用线程对象的start()方法
实现callable 接口
创建实现Callable接口的类myCallable
以myCallable为参数创建FutureTask对象
将FutureTask作为参数创建Thread对象
调用线程对象的start()方法
使用Executors工具类创建线程池
Executors提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService接口。(ExecutorService executorService = Executors.newSingleThreadExecutor();)
四种线程池:
newFixedThreadPool
newCachedThreadPool
newSingleThreadExecutor
newScheduledThreadPool
FutureTask
当FutureTask处于未启动或者已启动的状态时,调用FutureTask对象的get方法会将导致调用线程阻塞。当FutureTask处于已完成的状态时,调用FutureTask的get方法会立即放回调用结果或者抛出异常。
FutureTask使用
可以把FutureTask交给Executor执行
也可以通ExecutorService.submit(…)方法返回一个FutureTask,然后执行FutureTask.get()方法或FutureTask.cancel(…)方法
除此以外,还可以单独使用FutureTask。
要想对线程额外操作,可以使用Callable Futrue中的一些方法
runnable 和 callable 有什么区别?
相同点:
都是接口
都可以编写多线程程序
都采用Thread.start()启动线程
主要区别:
Runnable 接口 run 方法无返回值;Callable 接口 call 方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果
Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;Callable 接口 call 方法允许抛出异常,可以获取异常信息
注:Callalbe接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。
线程的 run()和 start()有什么区别?
每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,run()方法称为线程体.通过调用Thread类的start()方法来启动一个线程。
start() 方法用于启动线程,run() 方法用于执行线程的运行时代码。run() 可以重复调用,而 start() 只能调用一次。
start()方法来启动一个线程,真正实现了多线程运行。调用start()方法无需等待run方法体代码执行完毕,可以直接继续执行其他的代码; 此时线程是处于就绪状态,并没有运行。 然后通过此Thread类调用方法run()来完成其运行状态, run()方法运行结束, 此线程终止。然后CPU再调度其它线程。
run()方法是在本线程里的,只是线程里的一个函数,而不是多线程的。 如果直接调用run(),其实就相当于是调用了一个普通函数而已,直接待用run()方法必须等待run()方法执行完毕才能执行下面的代码,所以执行路径还是只有一条,根本就没有线程的特征,所以在多线程执行时要使用start()方法而不是run()方法。
new 一个 Thread,线程进入了新建状态。调用 start() 方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。
而直接执行 run() 方法,会把 run 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。
总结: 调用 start 方法方可启动线程并使线程进入就绪状态,而 run 方法只是 thread 的一个普通方法调用,还是在主线程里执行。
什么是Callable和Fture?
Callable 接口类似于 Runnable,从名字就可以看出来了,但是 Runnable 不会返回结果,并且无法抛出返回结果的异常,而 Callable 功能更强大一些,被线程执行后,可以返回值,这个返回值可以被 Future 拿到,也就是说,Future 可以拿到异步执行任务的返回值。
Future 接口表示异步任务,是一个可能还没有完成的异步任务的结果。所以说Callable用于产生结果,Future用于获取结果
什么是 FutureTask?
FutureTask表示一个异步的运算任务。FutureTask里面可以传一个Callable的具体实现类,可以对这个异步运算的任务的结果进行等待、判断是否已经完成、取消任务等操作。只有当运算完成的时候结果才能取回,如果运算尚未完成get方法将会阻塞。一个FutureTask对象可以对调用了Callable和Runnable的对象进行包装,由于FutureTask也是Runnable接口的实现类,所以FutureTask也可以放入线程池
通过JDK反汇编指令可以看出 在执行同步代码块的之前之后都有一个monitor的字样 其中前面是monitorenter 后面的是离开的monitorexit 一个线程在执行同步代码块 首先要获取锁 而获取锁的过程就是monitorenter 执行完代码之后 要释放锁 释放锁就是执行monitorexit
为什么会有两个monitorexit呢?
这个主要是防止在同步代码块中线程因异常退出,而锁没有得到释放,这必然会造成死锁(等待的线程永远获取不到锁)。因此最后一个monitorexit是保证在异常情况下,锁也可以得到释放,避免死锁。
synchronized可重入的原理:
重入锁是指一个线程获取到该锁之后,该线程可以继续获得该锁。底层原理维护一个计数器,当线程获取该锁时,计数器加一,再次获得该锁时继续加一,释放锁时,计数器减一,当计数器值为0时,表明该锁未被任何线程所持有,其它线程可以竞争获取锁。
synchronized关键字最主要的三种使用方式:
修饰实例方法:
作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁
修饰静态方法:
也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份)。所以如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。
修饰代码块:
指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
总结: synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。synchronized 关键字加到实例方法上是给对象实例上锁。尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具有缓存功能!
synchronized、volatile、CAS 比较
(1)synchronized 是悲观锁,属于抢占式,会引起其他线程阻塞。
(2)volatile 提供多线程共享变量可见性和禁止指令重排序优化。
(3)CAS 是基于冲突检测的乐观锁(非阻塞)
synchronized 和 Lock 有什么区别?
首先synchronized是Java内置关键字,在JVM层面,Lock是个Java类;
synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。
synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁;而 lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁。
通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。
Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:
普通同步方法,锁是当前实例对象
静态同步方法,锁是当前类的class对象
同步方法块,锁是括号里面的对象
synchronized 和 volatile 的区别是什么?
synchronized 表示只有一个线程可以获取作用对象的锁,执行代码,阻塞其他线程。
volatile 表示变量在 CPU 的寄存器中是不确定的,必须从主存中读取。保证多线程环境下变量的可见性;禁止指令重排序。
CAS 是 compare and swap 的缩写,即我们所说的比较交换。
cas 是一种基于锁的操作,而且是乐观锁。在 java 中锁分为乐观锁和悲观锁。悲观锁是将资源锁住,等一个之前获得锁的线程释放锁之后,下一个线程才可以访问。而乐观锁采取了一种宽泛的态度,通过某种方式不加锁来处理资源,比如通过给记录加 version 来获取数据,性能较悲观锁有很大的提高。
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存地址里面的值和 A 的值是一样的,那么就将内存里面的值更新成 B。CAS是通过无限循环来获取数据的,若果在第一轮循环中,a 线程获取地址里面的值被b 线程修改了,那么 a 线程需要自旋,到下次循环才有可能机会执行。
CopyOnWriteArrayList
ThreadLocal
ThreadLocal 是一个本地线程副本变量工具类,在每个线程中都创建了一个 ThreadLocalMap 对象
ThreadLocal 就是一种以空间换时间的做法,每个线程可以访问自己内部 ThreadLocalMap 对象内的 value。通过这种方式,避免资源在多线程间共享。
原理:线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共享。
ThreadLocal内存泄漏解决方案?
每次使用完ThreadLocal,都调用它的remove()方法,清除数据。
在使用线程池的情况下,没有及时清理ThreadLocal,不仅是内存泄漏的问题,更严重的是可能导致业务逻辑出现问题。所以,使用ThreadLocal就跟加锁完要解锁一样,用完就清理。
BlockingQueue
阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。
这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。
阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。
阻塞队列使用最经典的场景就是 socket 客户端数据的读取和解析,读取数据的线程不断将数据放入队列,然后解析线程不断从队列取数据解析。
线程池都有哪些状态?
RUNNING:这是最正常的状态,接受新的任务,处理等待队列中的任务。
SHUTDOWN:不接受新的任务提交,但是会继续处理等待队列中的任务。
STOP:不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程。
TIDYING:所有的任务都销毁了,workCount 为 0,线程池的状态在转换为 TIDYING 状态时,会执行钩子方法 terminated()。
TERMINATED:terminated()方法结束后,线程池的状态就会变成这个。
并发工具
CountDownLatch
调用CountDownLatch的countDown方法后,当前线程并不会阻塞,会继续往下执行
CountDownLatch方法比较少,操作比较简单
CountDownLatch是不能复用的
CyclicBarrier
调用CyclicBarrier的await方法,会阻塞当前线程,直到CyclicBarrier指定的线程全部都到达了指定点的时候,才能继续往下执行
CyclicBarrier提供的方法更多,比如能够通过getNumberWaiting(),isBroken()这些方法获取当前多个线程的状态,并且CyclicBarrier的构造方法可以传入barrierAction,指定当所有线程都到达时执行的业务功能;
而CyclicLatch是可以复用的。
Semaphore
Semaphore(信号量)-允许多个线程同时访问: synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,Semaphore(信号量)可以指定多个线程同时访问某个资源。
树图思维导图提供 软考软件设计师 在线思维导图免费制作,点击“编辑”按钮,可对 软考软件设计师 进行在线思维导图编辑,本思维导图属于思维导图模板主题,文件编号是:6a80d6e336e8d531c07d47352540a4df
树图思维导图提供 双重循环--C++第11课 在线思维导图免费制作,点击“编辑”按钮,可对 双重循环--C++第11课 进行在线思维导图编辑,本思维导图属于思维导图模板主题,文件编号是:cf46a6cf29101c316c75f05a5a6602f8