本文仅仅是私人MEMO,仅仅起到私人记录大纲之用
AbstractQueuedSynchronizer,简称AQS,是Java并发包里一系列同步工具的基础实现,原理是根据状态位来控制线程的入队阻塞、出队唤醒来处理同步。
AQS内部维护一个CLH队列来管理锁。
线程会首先尝试获取锁,如果失败,则将当前线程以及等待状态等信息包成一个Node节点加到同步队列里。
接着会不断循环尝试获取锁(条件是当前节点为head的直接后继才会尝试),如果失败则会阻塞自己,直至被唤醒;
而当持有锁的线程释放锁时,会唤醒队列中的后继线程。
自定义基于AQS的同步工具时,可以选择覆盖以下几个方法实现同步状态的管理:
1、boolean tryAcquire(int)//尝试获取独占锁
2、boolean tryRelease(int)//尝试释放独占锁
3、int tryAcquireShared(int)//尝试获取共享锁
4、boolean tryReleaseShared(int)//尝试释放共享锁
5、boolean isHelodExclusively()//当前线程是否获得了独占锁
这些方法的实现应当是无阻塞的。
CountDownLatch的countDown()释放操作实际上是sync.releaseShared(1) 其中Sync是AQS的一个子类实例
await()操作实际上是sync.acquireSharedInterruptibly(1)
CyclicBarrier实现主要基于ReentrantLock
Semaphore也是基于AQS,有一个内部类Sync,分别有公平Sync和非公平Sync
各个锁的优缺点
内核锁:基于内核对象构造的锁机制,就是通常说的内核构造模式。
优点:cpu利用最大化。它发现资源被锁住,请求就排队等候。线程切换到别处干活,直到接受到可用信号,线程再切回来继续处理请求。
缺点:托管代码->用户模式代码->内核代码损耗、线程上下文切换损耗。
在锁的时间比较短时,系统频繁忙于休眠、切换,是个很大的性能损耗。
自旋锁:原子操作+自循环。通常说的用户构造模式。 线程不休眠,一直循环尝试对资源访问,直到可用。
优点:完美解决内核锁的缺点。
缺点:长时间一直循环会导致cpu的白白浪费,高并发竞争下、CPU的消耗特别严重。
基于CAS操作,需要硬件配合。需要保证各个CPU缓存一致性。没办法保证公平性。
混合锁:内核锁+自旋锁。 混合锁是先自旋锁一段时间或自旋多少次,再转成内核锁。
优点:内核锁和自旋锁的折中方案,利用前二者优点,避免出现极端情况(自旋时间过长,内核锁时间过短)。
缺点: 自旋多少时间、自旋多少次,这些策略很难把控。
Java没有提供自旋锁的API,但是默认使用了自旋锁优化
自旋锁(spin lock)与互斥量(mutex)的比较
自旋锁是一种非阻塞锁,也就是说,如果某线程需要获取自旋锁,但该锁已经被其他线程占用时,该线程不会被挂起,而是在不断的消耗CPU的时间,不停的试图获取自旋锁。
互斥量是阻塞锁,当某线程无法获取互斥量时,该线程会被直接挂起,该线程不再消耗CPU时间,当其他线程释放互斥量后,操作系统会激活那个被挂起的线程,让其投入运行。
如果加锁的代码经常被调用,但竞争情况很少发生时,应该优先考虑使用自旋锁,自旋锁的开销比较小,互斥量的开销较大。
如果是单核处理器,一般建议不要使用自旋锁。
Ticket Lock
解决了普通自旋锁的公平性问题,锁有一个服务号,表示正在服务的线程。每个线程自己有一个排队号。线程不断询问服务号是不是自己的排队号,不是就自旋轮询,否则推出自旋。
但是这个模式使得各个线程的缓存需要同步(访问同一个服务号),导致数据在缓存间同步开销很大,
MCS锁
申请线程只在本地变量上自旋,直接前驱负责通知其结束自旋,减少了不必要的处理器缓存同步。
CLH锁
申请线程只在本地变量上自旋,不断轮询前去的状态,发现前驱释放了锁就结束自旋。
AQS中的CLH机制的等待机制从自旋变成了阻塞唤醒。但是JVM默认对锁进行自旋优化(混合锁)。
num&=(num-1)可以去掉num最低位的1,如果存在的话
ArithmeticException
LinkedHashMap 保证插入数据顺序的HashMap
BigDecimal的高精度特点 multiply和divide
public class LinkedList
extends AbstractSequentialList
implements List, Deque, Cloneable, java.io.Serializable
public class ArrayList extends AbstractList
implements List, RandomAccess, Cloneable, java.io.Serializable
IllegalThreadStateException
System.out.println()线程安全 但是如果在参数列表进行数据计算会出现线程安全问题
currentThread() isAlive()可以判断是否存活
Thread.sleep() 让this.currentThread()返回的线程沉睡
java中有以下3中方法可以终止正在运行的线程:
1、线程正常退出(run()方法结束)
2、使用stop()方法强行停止线程,但是stop、suspend、resume都过期了
3、interrupt方法中断线程
调用interrupt()方法不是马上中断,而是打了一个标记
静态方法Thread.interrupted()作用是测试当前线程是否发生中断 清除中断位
interrupt()作用是打断指定线程 置中断状态
isInterrupted()方法作用是判断是否是中断状态
利用中断位加return进行线程停止是很好的方法
stop()方法与java.lang.ThreadDeath异常
stop()方法会抛出java.lang.ThreadDeath异常
suspend()可以暂停线程和resume()恢复线程执行 但是被废弃了
suspend和resume方法很容易导致锁的独占、不同步
yield()方法作用是放弃当前CPU资源 让给其他线程去执行(当然也可能刚刚放弃时间片就又获取到了)
Java中线程优先级分为1-10这1-个等级 小于1大于10都会抛出IllegalArgumentException
线程的优先级具有继承特性 A启动B则B和A一样的 可以通过setPriority(int)和getPriority()设置和获取优先级 幽默
高优先级的线程大部分先执行完 但是不代表高优先级的线程全部先执行完。
也就是线程的优先级具有随机性
守护线程
Java线程中有两种线程,用户线程和守护线程
当进程没有非守护线程,守护线程就自动销毁,比如GC setDaemon()设置守护线程
Java中的锁都是可重入锁
可重入锁的概念:自己可以再次获取自己的内部锁。当一条线程已经获取了某个对象锁却还没有释放,需要获取这个锁的时候可以直接获取 。这种情况不是可重入的话就会造成死锁。
出现异常 锁自动释放 同步不具有继承性 子类方法需要手动添加synchronized关键字
volatile和synchronized的区别:
1、volatile是线程同步的轻量级实现 性能比synchronized要好 volatile只能修饰变量,synchronized可以修饰方法和代码块 synchronized随着jdk版本越来越高 效率也会提高很多
2、多线程访问volatile不会阻塞 synchronized会
3、volatile保证可见性 不保证原子性 synchronized保证原子性 也可以间接保证可见性 同步私有内存和公共内存中的数据
4、一句话volatile解决多个线程的可见性 synchronized关键字解决多个线程的同步性
5、volatile可以禁止重排序 synchronized不能
PipedReader PipedWriter
PipedReader(PipedWriter src,int pipeSize)//使用buf大小和传入的pw构造pr
void connect(PipedWriter src)//reader的方法,绑定writer
void write(char cbuf[],int off,int len)//写多个字符到Reader缓冲区
SimpleDateFormat非线程安全
父类委派机制的优点:
1、避免类的重复加载,当父类加载了该类的时候,就没必要各个子ClassLoader再加载一次
2、从安全角度考虑 如果不适用这种委托模式 用户就可以编写新的类代替Java核心API中的类,存在很大的安全隐患。
如果使用双亲加载机制就会使得Java核心API已经被加载了,而无法再使用自定义的ClassLoader加载核心API
ClassLoader重要方法:
1、Class loadClass(String name, boolean resolve);//name是全限定名 第二个参数表示是否需要解析类
在准备执行类之前,应该考虑类解析。并不总需要解析,如果JVM只需要知道该类是否存在或者找出该超类 就不需要解析
2、defineClass方法
defineClass方法接受由原始字节组成的数组 转化为Class对象。实现了许多复杂层面--字节码分析成运行时数据结构、校验有效性
这个类不能被覆盖
3、findSystemClass方法
从本地文件系统装入文件。对于系统提供的ClassLoader,只有尝试其他方法装入类之后,才使用findSystemClass。
4、resolveClass方法
我们可以不解析地装入类,也可以解析地装入类。当编写我们的loadClass时,可以调用resolveClass(取决于loadClass的resolve参数)
5、findLoadedClass方法
充当一个缓存,当loadClass装入类时,调用该方法判断ClassLoader是否已经装入该类 避免重复装入类
6、findClass方法
loadClass默认是先调用这个方法 一般覆盖这个方法重写类加载器
7、getSystemClassLoader方法
目的是运行ClassLoader获取到它的父类ClassLoader 当定制的ClassLoader找不到类时 可以使用这种方法
8、forName方法
Class的静态方法。和loadClass一样都是加载类,但是作用有区别。
Class.forName("some") 等同于 Class.forName("some",true,CALLCLASS.class.getClassLoader());
JVM的编码时unicode,奖一个字符分为两部分:JVM内部和OS的文件系统
JVM内部统一使用Unicode表示,当这个字符从JVM内部移到外部,就完成了编码转化。所有的编码转化只发生再边界的地方,JVM和OS交界处,也就是各种输入/输出流起作用的地方
I/O基本上分为两大阵营:面向字符的I/O和面向字节的I/O
面向字节就是保证系统中的文件二进制内容和读入JVM内部二进制内容一致,不做0 1顺序变换。这种方式很适合读入视频文件或者音频文件,或者任何不需要做变换的文件内容。
面向字符就是希望系统中文件的字符和读入内存中的字符要一致。比如一个字符'国',我们只关心'国'这个字,只希望它都出来仍然是'永',不关心这个变量具体的二进制是多少。
Reader/Writer是面向字符的顶层接口 InputStream和OutputStream是面向字节的顶层接口 InputStreamReader和OutputStreamWriter是字节流和字符流之间的适配器类
在I/O结构中,RandomAccessFile是比较不寻常的类,直接继承于Object,不属于Streams结构中的一部分。RandomAccessFile raf = new RandomAccessFile("myfile.txt","rw");
数据类型转化问题
(byte,short,char这三个相加都是转化为int,char转化为ASCII码)-int-long-float-double
Integer.valueOf(int)从int到Integer包装 i.intValue()从Integer到int转化
三目运算符是右结合的 如true?false:true == true?false:true等价于true?false:((true==true)?false:true)
三目运算符也会进行数据类型转换 比如a<5?10.9:9结果是9.0 不是 9
当后两个表达式有一个是常量表达式,另一个是类型T时,如果常量表达式可以被T类型表示时,输出类型也是T。如果false?10:'x';输出结果是10对应char的值 也就是120
移位操作符右边的参数要对32求模 比如num>>32等于num>>0 num>>33等于num>>1
反射
反射主要是指程序可以访问、检测和修改它本身状态或者行为的一种能力。
java中的反射是一种强大的工具,能创建灵活的代码,使得这些代码可以在运行时装配,无需在组建之间连接。在编写与执行的时候,程序代码能够动态装载类的内部消息。
序列化
想要一个类的对象可以被序列化只需要实现Serializable接口就行了。序列化过程如下:
创建一个OutputStream,嵌入ObjectOutputStream,使用writeObject()方法就可以写入OutputStream
都的时候把InputStream嵌到ObjectInputStream中,调用readObject()方法(当然返回的只是一个reference)
for循环可以不用{} 但是仅限于执行语句 不包含变量声明语句
Queue的poll()方法在队列为空会返回null,而remove则会抛出异常
Iterator迭代器需要泛型参数 如果不加泛型参数,i.next()返回Object类型 否则返回泛型参数类型
Spring最核心的两个类
1、DefaultListableBeanFactory XmlBeanFactory继承自DefaultListableBeanFactory,被继承这个类是bean加载的核心部分
2、XmlBeanDefinitionReader XML配置文件的读取是Spring中重要的功能 XmlBeanDefinitionReader包含许多资源文件读取、解析、注册的大致脉络
配置文件封装 Spring的配置文件读取通过ClassPathResource进行封装的。在Java中,不同资源抽象为URL,通过注册不同的handler来处理不同来源的资源都去逻辑,这些handler使用不同的前缀如"file:"、"http:"、"jar:"等
Spring对其内部使用到的资源使用Resource接口来封装底层资源。
Spring的Bean加载的大致处理流程:
1、封装资源文件 进入XmlBeanDefinitionReader后首先对参数Resource进行封装
2、获取输入流 从Resource中获取对应InputStream并且构造InputSource。
3、通过构造的InputSource实例和Resouce实例继续调用函数doLoadBeanDefinitions。 (验证XML、加载XML并且得到document、返回Document注册Bean信息)
常见的XML的验证模式:DTD、XSD
DTD即文档类型定义(Document Type Definition),是一种XML约束模式语言,是XML文件验证机制,属于XML文件一部分
DTD是一种保证XML文档格式正确的有效方法。通过比较XML和DTD判断XML是否规范 DTD包括元素的定义规则、元素间关系的定义规则、元素可使用属性、可使用实体和符合规则。
DTD验证模式的使用需要在XML文件头部声明
XSD(XML Schemas Definition)描述了XML文档的结构 可以用一个指定的XML Schema来验证某个XML文档,以检查该XML文档是否符合其要求
获取Document
委托给DefaultDocumentLoader
1,什么是代理模式?
代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问。
2,代理模式有什么好处?
在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
3、动态代理的作用:
Proxy类的代码量被固定下来,不会因为业务的逐渐庞大而庞大;
可以实现AOP编程,实际上静态代理也可以实现,总的来说,AOP可以算作是代理模式的一个典型应用;
解耦,通过参数就可以判断真实类,不需要事先实例化,更加灵活多变。
JVM、JRE、JDK、JIT的区别:
JVM:Java虚拟机,是整个Java实现跨平台的核心部分,所有Java程序会首先被编译为.class类文件,这种文件可以在JVM上执行。
也就是说class不直接与OS对应,而是通过JVM和OS交互,由JVM将程序解释给本地系统执行。
只有JVM还不能让class执行,解释class的时候JVM需要调用解释所需要的类库lib,而jre包含lib类库。
JVM屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。
JRE:Java运行环境,运行基于Java语言编写的程序必不可少的运行环境。加热包括JVM、运行时类库和Java应用启动器。与JDK不同,JRE是Java运行环境,不是开发环境,没有如编译器、调试器这样的开发工具。
JDK:Java开发工具包。包含jre目录,jre目录包含bin和lib,bin可以被认为是JVM,lib是JVM工作所需类库,JVM+lib合起来被称为JRE。
包含JRE、Java工具、Java基础类库(比如rt.jar).
JIT:即使编译器 当开启JIT时,JVM会分析Java应用程序函数调用并且编译字节码为本地更高效的机器码。JIT通常会为总繁忙的函数调用提供更高的优先级。
JVM执行Java应用程序流程:
1、Java源码通过编译器转为平台无关的字节码(bytecode)或Java class文件。
2、在启动Java应用程序后,JVM会在运行时加载编译后的类并通过Java解释器执行适当的 语义计算。
3、当开启JIT时,JVM会分析Java应用程序的函数调用并且(达到内部一些阀值后)编译字节码为本地更高效的机器码。JIT流程通常为最繁忙的函数调用提供更高的优先级。
4、一旦函数调用被转为机器码,JVM会直接执行而不是“解释执行”。
5、上述过程会在程序运行过程中不断提高性能。
RMI和RPC的区别:
RPC(Remote Procedure Call Protocol)远程过程调用协议,通过网络从远程计算机上请求调用某种服务。
RMI:远程方法调用(Remote Method Invocation)。能够让在客户端Java虚拟机上的对象像调用本地对象一样调用服务端java 虚拟机中的对象上的方法。
1:方法调用方式不同:
RMI中是通过在客户端的Stub对象作为远程接口进行远程方法的调用。每个远程方法都具有方法签名。如果一个方法在服务器上执行,但是没有相匹配的签名被添加到这个远程接口(stub)上,那么这个新方法就不能被RMI客户方所调用。
RPC中是通过网络服务协议向远程主机发送请求,请求包含了一个参数集和一个文本值,通常形成“classname.methodname(参数集)”的形式。RPC远程主机就去搜索与之相匹配的类和方法,找到后就执行方法并把结果编码,通过网络协议发回。
2:适用语言范围不同:
RMI只用于Java;
RPC是网络服务协议,与操作系统和语言无关。
3:调用结果的返回形式不同:
Java是面向对象的,所以RMI的调用结果可以是对象类型或者基本数据类型;
RMI的结果统一由外部数据表示 (External Data Representation, XDR) 语言表示,这种语言抽象了字节序类和数据类型结构之间的差异。
JAXP、JAXM、WSDL、SOAP、UDDI
Web Service是基于网络的、分布式的模块化组件,它执行特定的任务,遵守具体的技术规范,这些规范使得Web Service能与其他兼容的组件进行互操作。
JAXP(Java API for XML Parsing) 定义了在Java中使用DOM, SAX, XSLT的通用的接口。这样在你的程序中你只要使用这些通用的接口,当你需要改变具体的实现时候也不需要修改代码。
JAXM(Java API for XML Messaging) 是为SOAP通信提供访问方法和传输机制的API。
WSDL是一种 XML 格式,用于将网络服务描述为一组端点,这些端点对包含面向文档信息或面向过程信息的消息进行操作。这种格式首先对操作和消息进行抽象描述,然后将其绑定到具体的网络协议和消息格式上以定义端点。相关的具体端点即组合成为抽象端点(服务)。
SOAP即简单对象访问协议(Simple Object Access Protocol),它是用于交换XML编码信息的轻量级协议。
UDDI 的目的是为电子商务建立标准;UDDI是一套基于Web的、分布式的、为Web Service提供的、信息注册中心的实现标准规范,同时也包含一组使企业能将自身提供的Web Service注册,以使别的企业能够发现的访问协议的实现标准。
可以创建volatile数组吗?
可以,但是是保证数组引用的可见性和不重排优化
sizeof不是Java的关键字和保留字 const是Java的保留字
在switch(expression)中,expression只能是一个整数表达式或者枚举常量,整数表达式可以是int类型或Integer包装类型。由于,byte,short,char都可以隐式转换为int类型,所以,这些类型也可以用作表达式。
另外jdk7以后,switch表达式也可以为String类型。(Only convertible int values, strings or enum variables are permitted)
ConcurrentLinkedQueue、LinkedBlockingQueue的区别和联系
都是线程安全,前者是并发队列,后者是阻塞队列,采用CAS算法。
WeakHashMap
继承自AbstractMap,线程不安全,当某个键不再使用会从这个数据结构中移除。
EnumSet
EnumSet是Java枚举类型的容器 速度很快
从Add方法的源码可以看出add方法实际只是对长整型数据element做了一个操作,也就是说EnumSet实际上将枚举值保存在一个长整型数据上。没个枚举值占用一bit。每次添加做的主要操作是1、类型检查 2、 添加枚举值 3、判断枚举值是否已经添加过了。
Collections.sort() 传入一个List 以及可选参数Comparator 底层是归并排序
Java 中 LinkedHashMap 和 PriorityQueue 的区别是什么?
PriorityQueue 保证最高或者最低优先级的的元素总是在队列头部,但是 LinkedHashMap 维持的顺序是元素插入的顺序。当遍历一个 PriorityQueue 时,没有任何顺序保证,但是 LinkedHashMap 课保证遍历顺序是元素插入的顺序。
LinkedHashMap底层存在一个双向链表,用这个双向链表记录Entry的存储顺序,对LinkedHashMap遍历实际上是对这个双向链表。
PriorityQueue底层是最小堆 但是是用数组实现的
B树
二叉搜索树 所有非叶子节点之多拥有两个儿子 所有节点存储一个关键字 非叶子结点左指针指向小于关键字的子树 右指针指向大于其关键字的子树
B-树
多路搜索树
B-树的特性:
1.关键字集合分布在整颗树中;
2.任何一个关键字出现且只出现在一个结点中;
3.搜索有可能在非叶子结点结束;
4.其搜索性能等价于在关键字全集内做一次二分查找;
5.自动层次控制;
B-树限制了除根节点以外的非叶子结点 至少含有M/2个儿子 确保了结点的至少利用率
B+树
B+的搜索与B-树也基本相同,区别是B+树只有达到叶子结点才命中(B-树可以在非叶子结点命中),其性能也等价于在关键字全集做一次二分查找;
B+的特性:
1.所有关键字都出现在叶子结点的链表中(稠密索引),且链表中的关键字恰好是有序的;
2.不可能在非叶子结点命中;
3.非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储(关键字)数据的数据层;
4.更适合文件索引系统;
原因:相对于B树,(1)B+树空间利用率更高,因为B+树的内部节点只是作为索引使用,而不像B-树那样每个节点都需要存储硬盘指针。
(2)增删文件(节点)时,效率更高,因为B+树的叶子节点包含所有关键字,并以有序的链表结构存储,这样可很好提高增删效率。
B*树
是B+树的变体,在B+树的非根和非叶子结点再增加指向兄弟的指针;
小结:
B树:二叉树,每个结点只存储一个关键字,等于则命中,小于走左结点,大于走右结点;
B-树:多路搜索树,每个结点存储M/2到M个关键字,非叶子结点存储指向关键字范围的子结点;
所有关键字在整颗树中出现,且只出现一次,非叶子结点可以命中;
B+树:在B-树基础上,为叶子结点增加链表指针,所有关键字都在叶子结点中出现,非叶子结点作为叶子结点的索引;B+树总是到叶子结点才命中;
它更适合文件索引系统;
原因:相对于B树,(1)B+树空间利用率更高,因为B+树的内部节点只是作为索引使用,而不像B-树那样每个节点都需要存储硬盘指针。
(2)增删文件(节点)时,效率更高,因为B+树的叶子节点包含所有关键字,并以有序的链表结构存储,这样可很好提高增删效率。
B*树:在B+树基础上,为非叶子结点也增加链表指针,将结点的最低利用率从1/2提高到2/3;
Comparable和Comparator的作用和区别:
都是用来自定义class的比较大小的
区别:
1、Comparable定义在内部。比如class Person implements Comparable就说明Person是一个可比较的对象
2、Comparator是定义在外部 class PersonComparator implements Comparator 说明PersonComparator是一个比较器
main方法是否可以重载?
Java可重载main方法,可以有任意个数main()方法
为了运行java类,类的main()方法应该有例如“public static void main(String[] args)”的声明。如果你对此声明做任何修改,编译也是可以成功的。但是,运行不了Java程序。
进程的通信机制主要有:管道、有名管道、消息队列、信号量、共享空间、信号、套接字。
线程通信:内存共享、消息通信
java中基本类型的长度都是固定的,不随平台改变而改变
多线程中的性能杀手--伪共享
伪共享的非标准定义为:缓存系统中是以缓存行(cache line)为单位存储的,当多线程修改互相独立的变量时,如果这些变量共享同一个缓存行,就会无意中影响彼此的性能,这就是伪共享。
线程组:
线程组表示一个线程的集合。此外,线程组也可以包含其他线程组。线程组构成一棵树,在树中,除了初始线程组外,每个线程组都有一个父线程组。
允许线程访问有关自己的线程组的信息,但是不允许它访问有关其线程组的父线程组或其他任何线程组的信息。
线程组的作用是:可以批量管理线程或线程组对象,有效地对线程或线程组对象进行组织。
线程池和线程组的区别:
线程组:
线程组存在的意义,首要原因是安全。
java默认创建的线程都是属于系统线程组,而同一个线程组的线程是可以相互修改对方的数据的。
但如果在不同的线程组中,那么就不能“跨线程组”修改数据,可以从一定程度上保证数据安全.
线程池:
线程池存在的意义,首要作用是效率。
线程的创建和结束都需要耗费一定的系统时间(特别是创建),不停创建和删除线程会浪费大量的时间。所以,在创建出一条线程并使其在执行完任务后不结束,而是使其进入休眠状态,在需要用时再唤醒,那么 就可以节省一定的时间。
如果这样的线程比较多,那么就可以使用线程池来进行管理。保证效率。
线程组被弃用的原因:
1.线程组ThreadGroup对象中比较有用的方法是stop、resume、suspend等方法,由于这几个方法会导致线程的安全问题(主要是死锁问题),已经被官方废弃掉了,所以线程组本身的应用价值就大打折扣了。
2.线程组ThreadGroup不是线程安全的,这在使用过程中获取的信息并不全是及时有效的,这就降低了它的统计使用价值。
JVM的线程调度器实现了抢占式调度,每条线程执行的时间由它分配管理,它将按照线程优先级的建议对线程执行的时间进行分配,优先级越高,可能得到CPU的时间则越长。
Java并发包中Lock接口比起synchronized的优势
优势有:
1、可以使锁更公平;
2、可以使线程在等待锁的时候响应中断;
3、可以让线程尝试获取锁,并在无法获取锁的时候立即返回或者等待一段时间;
4、可以在不同的范围,以不同的顺序获取和释放锁。
偏,轻,重锁,分别解决三个问题,只有一个线程进入临界区,多个线程交替进入临界区,多线程同时进入临界区。
偏向锁,轻量级锁都是乐观锁,重量级锁是悲观锁。
偏向锁
当一个线程访问同步快并获取锁时,会在对象头和栈帧中的锁记录存储偏向的线程id,以后此线程在进入和退出同步快时不需要花费CAS操作来加锁解锁,只需要测试一下对象头里是否存储着指向当前线程的偏向锁。
轻量级锁
轻量级锁认为竞争存在,但是竞争的程度很轻,一般两个线程对于同一个锁的操作都会错开,或者说稍微等待一下(自旋),另一个线程就会释放锁。
但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁膨胀为重量级锁,重量级锁使除了拥有锁的线程以外的线程都阻塞,防止CPU空转。
死锁、活锁、饥饿
死锁:是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
死锁的条件:
1、线程对资源访问互斥
2、线程访问资源后保持
3、线程获得资源不能被其他线程剥夺
4、环路等待条件 {p0,p1,p2,...pn},进程p0(或线程)等待p1占用的资源,p1等待p2占用的资源,pn等待p0占用的资源。
任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败,尝试,失败。活锁和死锁的区别在于,处于活锁的实体是在不断的改变状态,所谓的“活”, 而处于死锁的实体表现为等待;活锁有可能自行解开,死锁则不能。
饥饿:是指如果线程T1占用了资源R,线程T2又请求封锁R,于是T2等待。T3也请求资源R,当T1释放了R上的封锁后,系统首先批准了T3的请求,T2仍然等待。然后T4又请求封锁R,
当T3释放了R上的封锁之后,系统又批准了T4的请求......,T2可能永远等待。
如何避免死锁:
加锁顺序(线程按照一定的顺序加锁)
加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
死锁检测
解决协同活锁的一种方案是调整重试机制。比如引入一些随机性。例如如果检测到冲突,那么就暂停随机的一定时间进行重试。这回大大减少碰撞的可能性。
活锁和死锁的区别在于,处于活锁的实体是在不断的改变状态,所谓的“活”, 而处于死锁的实体表现为等待;活锁有可能自行解开,死锁则不能。
Executors 类提供工厂方法用来创建不同类型的线程池。比如: newSingleThreadExecutor() 创建一个只有一个线程的线程池,newFixedThreadPool(int numOfThreads)来创建固定线程数的线程池,newCachedThreadPool()可以根据需要创建新的线程,但如果已有线程是空闲的会重用已有线程。
Executor则是一个接口 提供execute()方法用来提交任务 实现了这个接口的ExecutorService提供了submit()和execute()
UnsupportedOperationException
使用了Java中未实现的方法抛出的异常
异常链
异常链”是Java中非常流行的异常处理概念,是指在进行一个异常处理时抛出了另外一个异常,由此产生了一个异常链条。
JDBC过程
1、Class.forName("com.mysql.jdbc.Driver");//加载数据库驱动
2、Connection conn = DriverManager.getConnection(url,username,password);//获取数据库连接
3、PreparedStatement ps = conn.preparedStatemenet(sql);//预编译定义的sql语句
4、ResultSet rs = ps.executeUpdate();//执行操作 获取结果
5、while(rs.next()){rs.getInt(字段)}//访问
6、rs.close();//关闭 会抛出SQLException
PreparedStatement的好处
1、性能 2、可读性和可维护性 3、防止sql注入
Externalizable继承自Serializable
Serializable和Externalizable的区别
实现serializable,这个确确实实就是一个可以序列化的标示,对serializable进行implements之后,我们不用实现任何的功能,但是如果选择用externalizable,则需要进行实现它其中的接口,那么这其中的工作都将是程序员的.
但是serializable是虚拟机内建的,那么必然是占用大多的资源,导致速度减慢.而externalizable则是由程序员决定,存储什么,那么性能就要比serializable好多了.
Externalizable 接口定义了两个方法,writerExternal方法在序列化时被调用,可以再该方法中控制序列化内容,readExternal方法在反序列时被调用,可以在该方法中控制反序列的内容
继承Serializable想要自定义序列化就可以定义自己的readObject()和writeObject() 不是重写 只是定义
OSI七层模型
应用层 http ftp dns
表示层 ASCLL PICT TIFF JPEG MIDI MPEG
会话层 RPC SQL NFS ASP
传输层 TCP UDP SPX
网络层 IP ICMP
数据链路层 802.2 HDLC、FRAME RELAY
物理层
ByteBuffer
java.nio.ByteBuffer不能通过构造函数实例化,但是可以通过静态工厂方法获取
1、allocate(int) 从堆空间分配对应容量的byte数组作为缓冲区的byte数据存储器
2、allocateDirect(int) 不使用JVM对战 而是使用OS创建内存块作缓冲区 速度更快 但是分配过程开销大 只有缓冲区长期存在、经常重用且较大才使用
3、wrap(byte[]) 这个缓冲区的数据会存放到byte数组中,byte数组或者缓冲区任何一方数据修改都会影响到另一方。
对于ByteBuffer buffer = ByteBuffer.allocate(1024); String str = "hello world";
buffer.put(str.getBytes());将str的byte数组放入buffer
buffer.flip();改变读写状态
byte[] bbyte = new byte[buffer.limit()]; buffer.get(bbyte);//将buffer读入bbyte字节数组
类不能多继承的原因:
第一,如果一个类继承多个父类,如果父类中的方法名如果相同,那么就会产生歧义。
第二,如果父类中的方法同名,子类中没有覆盖,同样会产生上面的错误。
接口多继承不存在这两个问题
Date、Calendar、SimpleDateFormat都不是线程安全的。
Calendar.getInstance()获取日历类实例
set()方法:使用下面三个方法把日历定到任何一个时间:
set(int year ,int month,int date)
set(int year ,int month,int date,int hour,int minute)
set(int year ,int month,int date,int hour,int minute,int second)
before(Object) after(Object)判断时间是否在Object之前、之后
将字符串转化为Date对象
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date = sdf.parse("2008-08-08");
DOM和SAX解析XML的区别:
1、DOM将所有文件读取到内存中形成DOM树,文件量过大时无法使用。
SAX顺序读入所需要的文件内容,不会一次性读取全部,所以不受文件大小限制。
2、DOM解析中DOM树在内存中生成,可以随意修改文件树的任何部分,SAX解析由于是部分读取,只能从头到尾读取XML文件内容,不能修改。
3、DOM开发较为简单
综上DOM适合修改,SAX适合读取大型文件 两者结合可以使用JDOM