Java Collections框架中包含了大量集合接口以及这些接口的实现类和操作它们的算法(排序、查找、反转、替换、复制、取最小元素、取最大元素、线程安全化等),主要提供List、Queue、Set、Stack、Map等数据结构
Collection是这个集合框架的基础,里面存储一组对象,表示不同类型的Collection是,作用只是提供维护一组对象的基本接口
Set:表示数学意义上的集合概念,主要特点集合中的元素不能重复,因此存入Set中的每个元素都必须定义equals()方法来确保对象的唯一性,该接口实现2个类:HashSet和TreeSet
List:有序Collection,按对象进入的顺序保存对象,能够对列表中的每个元素的插入和删除位置进行精确的控制,同时可以保存重复的对象,LinkedList、ArrayList、Vector都实现了这个接口
Map:提供一个从键映射到值的数据结构,用于保存键值对,值可以重复,但键必须是唯一的,键不能重复,HashMap(基于散列)、TreeMap(基于红黑树)、LinkedHashMap(基于链表)、WeakHashMap、IdentityHashMap都实现了这个接口,但是执行效率不完全相同
迭代器是一个对象,它的工作是遍历并选择序列中的独享,它提供了一种访问一个容器对象中的个元素,而又不暴露该对象内部细节的方法,迭代器创建的代价小,因此迭代器通常被称为轻量级的容器
迭代器使用注意事项:
ListIterator只存在于List中,支持在迭代期间想List中添加或删除元素,并且可以在List中双向滚动
Tips:在使用iterator()方法时,经常遇到ConcurrentModificationException异常:
解决方案:
ArrayList、Vector、LinkedList类都在java.util包中,均未可伸缩数组(可以动态改变长度的数组)
名称 | 描述 | 随机访问 | 插入删除元素 | 扩容 | 是否线程安全 |
---|---|---|---|---|---|
ArrayList | 基于存储元素的Object[] array来实现的,在内存中开辟连续的空间来存储 | 快 | 慢 | 1.5倍 | 否 |
Vector | 基于存储元素的Object[] array来实现的,在内存中开辟连续的空间来存储 | 快 | 慢 | 2倍 | 是 |
LinkedList | 双向链表实现,对数据的索引需要从列表头开始遍历 | 慢 | 快 | 动态增长 | 否 |
Map迎来存储键值对,在数组中通过下标来对内容索引,在Map中,通过对象来进行索引,用来索引的对象叫key,其对应的对象叫value
名称 | 描述 | 访问速度 | 是否线程安全 | 特点 | 迭代 | 扩容方式 |
---|---|---|---|---|---|---|
HashMap | 最常用的Map,根据键的HashCode值存储数据 | 快 | 否 | 允许null的key(最多能用一个null的key),不允许多条记录的值为null | Iterator | 默认大小16,2 的指数倍扩容 |
Hashtable | 根据键的HashCode值存储数据 | 快 | 是 | 不允许null的key和value | Enumeration | 默认11,old*2+1的方式扩容 |
TreeMap | 实现了SortMap接口,能够把保存的记录根据键排序,取出来的顺序是排序后的 | 否 | Iterator | |||
LinkedHashMap | 输入输出顺序相同 | 否 | Iterator | |||
WeakHashMap | 与HashMap类似,但是采用弱引用,key不在被外部引用就可以被垃圾回收器回收 | 否 | Iterator |
对线程不安全的Map实现线程安全:
Map map = Collections.synchronizedMAP(New HashMap());
用自定义的类作为HashMap或hashtable的key的注意事项:
HashMap添加元素的过程:
当新添加的key 的hash值已经存在,就会产生冲突,处理冲突的方法,开放地址法,在hash法,链地址法(HashMap使用这个)
线程是指在执行过程中,能够执行程序代码的一个执行单元,线程的四种状态(运行、就行、挂起、结束)
进程是指一段正在执行的程序,程序执行的最小单位,一个进程可以拥有多个线程,各个线程之间共享程序的内存空间(代码段、数据段、堆空间)以及一些进程级资源(文件),各个线程拥有自己的栈空间;在操作系统级别上程序的运行都是进程为单位的
使用多线程的优势:
同步和异步的区别:
当多个线程需要访问同一个资源时,需要以某种顺序来确保资源在某一时刻只能被一个线程使用,要实现同步操作,必须要获得每一个线程对象的锁,获得了锁保证在同一时刻只有一个线程能够进入临界区(访问互斥的代码块),并且在这个锁释放之前,其他线程就不能再进入这个临界区;实现同步的方式:同步代码块或者同步方法
异步与非阻塞类似,由于每个线程都包含了运行时自身需要的数据或方法,因此,在进行输入输出处理时,不必关系其他线程的状态或行为,也不必等到输入输出处理完毕才返回
JVM允许应用程序并发地运行多个线程,在Java中多线程实现一般有以下三种方式:
启动线程的唯一方法是通过Thread类的start()方法,调用start()方法后并不是立即执行多线程代码,而是使得该线程变为可运行状态
Callable与Runnable功能类似,但是前者更强大:
run()和start()方法的区别:
系统通过调用线程类的start()方法来启动一个线程,此时该线程处于就绪状态,而非运行状态,意味着这个线程可以被JVM调度执行,在调度过程中,JVM通过调用线程类的run()方法来完成时间的操作,run()结束后,线程就终止
如果直接调用线程类的run()方法,会被当作一个普通的函数调用,也就是说start()能够异步的调用run(),直接调用run()是同步的
当使用多线程访问同一个资源时,非常容易出现线程安全问题,因此需要采用同步机制来解决这个问题,Java主要有3中同步机制:
1、synchronized关键字
每个对象都有一个对象锁与之关联,该锁表明对象在任何时候只允许被一个线程拥有,当一个线程调用对象的synchronized代码时,需要先获得这个锁,然后才能执行相应的代码,执行结束后释放
synchronized方法,在方法声明前加入synchronized关键字,当一个方法的方法体规模很大,把该方法声明为synchronized会大大影响程序的执行效率
synchronized代码块,即可以把任意代码段声明为synchronized,也可以指定上锁的对象,非常灵活
2、 wait()方法和notify()方法
在synchronized代码被执行期间,线程可以调用对象的wait()方法,释放对象锁,进入等待状态,并可以调用notify()/notifyAll()方法通知整在等待的其他线程,唤醒等的线程去获得锁,让它们去竞争
3、Lock
JDK5新增了Lock接口,以及它的实现类ReentranLock(重入锁),Lock可以用来实现多线程同步,过程如下:
synchronized与Lock
名称 | 描述 | 用法不同 | 运行方式 | 性能不同 | 锁机制不同 |
---|---|---|---|---|---|
synchronized | 使用Object对象本身的wait、nitif、notifyAll调度机制 | 方法、代码块(括号中表示需要锁的对象) | 托管给JVM | 资源竞争不激烈的情况下性能好 | 锁和释放都在块结构中,获取多个锁,以相反顺序释放,自动解锁,不会因为异常而不释放锁造成死锁 |
Lock | 使用Condition进行线程之间的调度 | 显式指定起始位置和终止位置 | 代码实现,更精确 | 资源竞争激烈的情况下性能好 | 手动释放,必须在finally块中释放,否则会引起死锁 |
不能同时使用这2中同步机制
sleep()和wait()
sleep()是线程暂停执行一段时间的方法,wait()也是线程暂停执行的方式
原理不同:
名称 | 区别 | 苏醒方式 |
---|---|---|
sleep | Thread的静态方法,线程用来控制自身流程的 | 计时结束线程自动苏醒 |
wait | Object的方法,用于线程间通信,使当前拥有对象锁的线程等待 | 直到其他线程调用notify、notifyAll方法才醒来 |
对锁的处理机制不同:
名称 | 区别 |
---|---|
sleep | 不释放锁 |
wait | 释放锁,使得下线程所在对象中的其他synchronized数据可以被其他线程使用 |
使用区域不同:
名称 | 区别 | 是否捕获异常 |
---|---|---|
sleep | 任何地方 | 必须捕获异常 |
wait | 在同步控制方法或同步语句块中使用 | 不需要捕获异常 |
sleep()与yield()
名称 | 优先级 | 状态 | 异常 |
---|---|---|---|
sleep | 给其他线程运行机会时,不考虑优先级 | 进入阻塞状态 | 抛出InterruptedException |
yield | 给相同优先级或更高优先级的线程 | 重新进入可执行状态 | 没有声明任何异常 |
sleep指定的时间是线程不会运行的最小时间
join()方法的作用,让调用该方法的线程在执行完run()方法后,在执行join方法后面的代码,将2个线程合并用于实现同步功能
名称 | 描述 | 后果 |
---|---|---|
Thread.stop() | 终止线程时会释放已经锁定的所以监视资源 | 导致程序执行的不确定性 |
Thread.suspend() | 不会释放锁,容易导致死锁 | |
让线程自行结束进入Dead状态 | 提供某种方式让线程自动结束run()方法 | 进入Dead状态 |
让线程自行结束进入Dead状态 | 使用interrupt()方法,抛出InterruptedException | 在run方法中捕获来让线程安全退出 |
线程因为IO而停止,需要抛出一个IO相关的异常使线程离开run方法
死锁:两个或以上的进行在执行过程中,因为争夺资源而造成的一种相互等待现象
Java提供2种线程,守护线程和用户线程
守护线程:又被称为服务线程、后台线程,在程序运行是在后台提供一种通用服务的线程,并不属于程序中不可或缺的部分
只要有任何非守护线程在运行,程序就不会终止
守护线程的优先级低,并非只有JVM内部提供,用户在编写程序时也可以设置守护线程(在调用线程的start()方法之前,调用对象的setDaemon(true)方法)
在守护线程中产生的子线程还是守护线程,守护线程的一个典型例子就是垃圾回收器,只要JVM启动,它始终在运行, 实时监控和管理系统中可以被回收的资源