多线程
1.创建线程的方式
- 继承Thread类,重写run方法,通过new对象来调用start启动
- 实现Runnable接口,重写run方法,通过new Thread(该类对象).start调用
以上两种方式的区别
相同点:Thread类也实现了Runnable接口,重写了run方法。
Thread类有形参为Runnable的构造器,所以本质上以上两种方式都是创建Thread对象。
不同点:
实现Runnable接口避免了单继承的局限性,适合多个相同代码的线程去处理同一资源。
继承Thread类需要new多个对象,而实现Runnable接口只需要new一个对象,放到new Thread(对象).start()调用即可。
优先选择实现Runnable接口的方式。
- 实现Callable接口,重写call方法,创建这个实现类的对象,将该对象作为参数用FutureTask的构造器创建FutureTask对象,再创建new Thread(FutureTask对象).start()启动线程。
FutureTask是Future接口的唯一实现类,它实现了Future接口和Runnable接口。
Callable和Runnable的区别?
1.Runnable接口没有返回值,Callable接口有返回值,可以由FutureTask对象调用get方法得到返回值
2.call方法可以抛出异常,因为Callable接口里的call方法也抛了异常,run方法不能抛异常,只能在自己的方法体里解决。
3.Callable支持泛型,FutureTask也支持泛型,泛型用于改变返回值类型
- 使用线程池,Executors工具类
线程池种类:
Executors.newCachedThreadPool()
创建一个可缓存线程池,若长度超过需要则回收空闲线程,若不可回收则创建新线程
Executors.newFixedThreadPool(n)
创建一个固定线程数的线程池
Executors.newSingleThreadPool()
创建一个单线程的线程池,保证所有任务按优先级来执行
Executors.newScheduledThreadPool(n)
创建一个定长线程池,支持定时和周期任务的执行
线程池对象的execute方法和submit方法提交任务的区别?
1.execute只能传入Runnable的实现类对象,由于Runnable没有返回值所以返回值为null,出现异常会立即产生
2. submit既能传入Runnable的实现类对象,也能传入Callable的实现类对象,返回值为call方法的返回值,异常会在调get方法的时候才出现,因为throws向上抛了异常。
2.线程的生命周期
新建态:创建了具体线程执行体的Thread对象就进入新建态
就绪态:调用start方法进入就绪态,获得除了CPU执行权的所有资源
运行态:获得CPU执行权,执行run方法。调用yeild方法让步交出CPU执行权回到就绪态,若优先级高很可能短暂的又回到运行态。
阻塞态:调用sleep(),wait(),join(),等待同步锁等情况进入阻塞态,情况处理完进入就绪态
死亡态(终止态):run方法执行完成,调用stop方法,出现异常未处理进入死亡态
3.线程安全
方式一:同步代码块
synchronized(同步监视器){
//需要同步的代码
}
同步监视器俗称锁,可以由任何对象充当
当这种方式用于继承Thread的类时需将锁定义为static的,因为会new很多个不同的对象,需确保锁唯一。
方式二:同步方法
1.在实现Runnable的类中,同步方法只需要在方法前加上synchronized,默认的锁对象为this即当前对象
2.在继承Thread的类中,同步方法需加上static synchronized ,不加static默认的this会由于多对象的创建而不同,加了static后默认的this代表的是当前的类xxx.class,是唯一的。
方式三:Lock锁
在线程类里定义一个属性
private ReetrantLock lock= ne w ReetrrantLock()
同样的如果是继承方式这个属性需要定义为static的
在代码前用lock.lock()加锁,最后用lock.unlock()解锁来保证线程安全
synchronized和lock锁的区别?
synchronized自动释放,而lock手动设置以及手动释放。
4.一些问题
什么是线程死锁?
两个线程互相占用对方所需的资源,都不释放。
sleep方法和wait方法的区别?
同:都会使线程进入阻塞态
异:1.wait方法会释放锁,sleep方法不会释放锁
wait方法用于线程间通信,sleep用于在执行时暂停
wait方法需要notify()或者notifyall()唤醒一个或全部线程,一个则唤醒优先级高的,优先级 相同则随机唤醒。
2.wait(),notify(),notifyall()只能使用在同步方法或者同步代码块中,wait()只能由锁调用,并且这三个方法是Object类中的方法。
sleep()是Thread类的静态方法
start和run方法的区别?
调用start会让线程进入就绪态,获取除CPU执行权以外的其他所需资源,并将run方法作为线程执行体
直接调用run方法和调用普通方法没区别
5.同优先级先进先出,高优先级抢占式
集合
Collection
List
-ArrayList
-LinkedList
-Vector
Set
-HashSet
-TreeSet
-LinkedHashSet
Map
-HashMap
-LinkedHashMap
-HashTable
-properties
-TreeMap
1.ArrayList和Vector区别?
同:都实现了List接口,都是有序可重复的,底层都用Object[]存储,这种存储方式查询速度快但是增删速度慢。
异:Vector线程安全,ArrayList线程不安全但是效率高。
在jdk1.7中两者底层都默认创建长度为10的数组,但是ArrayList在长度超过时扩容为原来的1.5倍,Vector扩容为原来的两倍。
在jdk1.8中,ArrayList在new的时候并没有创建长度为10的数组,只是声明了一个数组类型的变量,在调用add方法的 时候才创建长度为10的数组。类似于单例模式的饿汉和懒汉
2.LinkedList:
底层使用双向链表存储,插入删除操作效率高,线程不安全。
3.HashMap和HashTable的区别?
同:都继承了Map接口,以key-value的形式存储数据
异:HashTable线程安全效率低,HashMap线程不安全效率高。
HashTable不能存储key或者value为null,HashMap允许key和value为null
4.List和Map的区别?
List存储单列数据,Map存储键值对。
List是有序可重复的集合,Map的key是无序不可重复的,用set存储。
Map的value是无序可重复的,用Collection存储
key和value构成entry对象在Map中存储,也是用set无序不可重复
5.对于无序不可重复的理解
1.无序并不是随机,而是根据数据的哈希值在数组中存储
2.不可重复性,如果类未重写HashCode和equals方法,那么两个属性值相同的对象用equals判断为不相同,因为用的是Object类的equals方法,实际上是==,比较地址。
如果重写了这两个方法,那么首先哈希值通过某种算法转换为数组下标,添加元素看该位置是否有值,没有则插入,有则比较哈希值是否相同,若不同则插入,相同则通过equals比较,相同则插入失败。
在jdk1.7中新加入的元素指向旧元素,
而在jdk1.8中旧元素指向新元素
6.HashMap实现原理
jdk7里(数组+链表):
new HashMap默认创建长度为16的Entry[]数组,添加元素根据key所在类的HashCode方法得到哈希值对应的数组位置,若此位置为空插入,若不为空比较哈希值,若哈希值相同则equals比较。默认扩容为原来的两倍。
jdk8中(数组+链表+红黑树):
底层是Node[],首次调用put时,底层创建长度为16的数组(懒加载),当索引上元素以链表形式存在个数大于8个且当前数组长度大于64时,此索引上的所有元素改为红黑树存储(有序)
默认的加载因子 是0.75,扩容临界值=容量*0.75,也就是超过12就扩容 为两倍。
HashSet底层也是HashMap ,实现原理是一样的。
7.ConcurrentHashMap
jdk1.7将整个桶数组进行分段(segment),每把锁锁住一部分数据,多线程可以同时访问不同数据段的数据,提高效率。
jdk1.8用数组加链表加红黑树存储,用synchronized和CAS来加锁,类似于优化且线程安全的HashMap
get操作不需要加锁是因为Node的成员val是用volatile修饰的,保证数组扩容的可见性。
8.LinkedHashMapHashMap,TreeMap
LinkedHashMap是HashMap的直接子类,使用双向链表将所有的entry对象按照插入顺序连接,保证迭代顺序和插入顺序相同。
9.LinkedHashMap和TreeMap
两者都是有序的,TreeMap按照比较器Comparator排序(默认key升序)
LinkedHashMap基于链表实现有序。
10.Comparable和Comparator区别?
自然排序Comparable,实现Comparable接口,重写CompareTo方法,自定义排序规则,例如String,包装类等都实现了这个接口
定制排序Comparator,当代码未实现Comparable且不能修改代码,用这个接口的匿名子类的方式Arrays.sort(arr,new Comparator(){
//重写Compare方法
})
对对象数组排序用Arrays.sort()
对对象列表排序用Collections.sort()
Collections内部实际上将列表转换为数组再按数组排序。
11.如何遍历集合?
方式一:迭代器
Collection接口实现了Iterable接口,所有在Collection下的所有集合类都有iterator()方法,用于创建迭代器对象。
1.Iterable iterator= 对象.iterator()
2.while(iterator.hasNext()){
iterator.next();
}
默认游标在第一个元素之前
方式二:foreach
底层也是用迭代器实现的,
for(元素类型 局部变量:集合对象){
}
局部变量的改变并不影响原有的集合
IO流
字节输入流InputStream
字节输出流OutputStream
字符输入流Reader
字符输出流Writer
字节流和字符流的区别?
字节流的操作不会经过缓存区(内存)直接操作文件,而字符流的操作会经过缓存区再操作文件。
什么是java序列化?
序列化指将一个Java对象写入IO流中,对象的反序列化机制则指从IO流中恢复该Java对象.
对象需要实现Serializable接口,标记为可序列化的对象。同时加上一个全局常量,若不加,java会自动根据内容生成一个UID,但是当内容改变很可能UID也会改变,所有需要自己显示的声明,java虚拟机会将字节流传来的UID和本地UID比较,相同才能反序列化。
声明为static和transient的属性不能序列化。