[TOC]
1. java集合
1.1 常用集合的导图
1.2 Collection和Map
java集合类分为collection 和 map两类
Collection
List(有序、可重复)
- ArrayList :线程不安全,底层用数组实现,按插入先后排序,随机访问快,插入与删除慢。
- LinkedList :线程不安全的,底层基于链表的数据结构实现,访问慢但插入元素与删除元素比较快。LinkedList类实现了Queue接口,可当做队列使用
- Vector :线程安全的, 实现了Cloneable接口,即实现clone()函数。它能被克隆。Vector 实现Serializable接口,支持序列化。
Set(无序、不可重复)
- HashSet :线程不安全的,值可以为null,层使用数据结构的hash算法实现的,因此具有很好的存取,查找的性能。在add对一个对象的添加要重写equals和hashcode,保证对象不重复性。
- TreeSet :线程不安全的, 底层使用数据结构红黑树算法进行维护的。可通过实现compareTo(Object obj) 来进行来进行排序
- LinkedHashSet :线程不安全的, 底层使用链表来进行维护的。里面的顺序是值插入的顺序,插入的时候性能不如hashSet而查询的时候性能会比hashSet更好。
Queue(队列,先进先出)
- PriorityQueue :保存队列元素的顺序并不是按加入队列的顺序,而是按队列元素的大小进行重新排序
常用队列操作方法:
1
2
3
4
5
6void add(Object e);//将指定元素加入此队列的尾部。
Object element();//获取队列头部的元素,但是不删除该元素。
boolean offer(Object e);//将指定元素加入此队列的尾部。当使用有容量限制的队列时,此方法通常比add(Object e)方法更好。
Object peek(); //获取队列头部的元素,但是不删除该元素,如果此队列为空,则返回null。
Object poll();//获取队列头部的元素,并删除该元素,如果此队列为空,则返回null。
Object remove();//获取队列头部的元素,并删除该元素。
Map
- HashMap :线程不安全的,基于哈希表实现。Key值和value值都可以为null
- HashTable :线程安全的,基于哈希表实现。Key值和value值不能为null
- TreeMap :线程不安全的,保存元素key-value不能为null,允许key-value重复;遍历元素时随机排列
2. java并发
- 并行:表示两个线程同时做事情。
- 并发:表示一会做这个事情,一会做另一个事情,存在着调度。单核 CPU 不可能存在并行(微观上)。
java实现多线程的两种方式:继承 Thread 类和实现 Runnable 接口
2.1 java多线程中名词的概念
多个进程或线程同时(或着说在同一段时间内)访问同一资源会产生并发问题。
具体详细内容可点击这里查看
- 临界区
临界区用来表示一种公共资源或者说是共享数据,可以被多个线程使用。但是每一次,只能有一个线程使用它,一旦临界区资源被占用,其他线程要想使用这个资源,就必须等待。
- 阻塞与非阻塞
阻塞和非阻塞通常用来形容多线程间的相互影响。比如一个线程占用了临界区资源,那么其它所有需要这个资源的线程就必须在这个临界区中进行等待,等待会导致线程挂起。这种情况就是阻塞。
此时,如果占用资源的线程一直不愿意释放资源,那么其它所有阻塞在这个临界区上的线程都不能工作。阻塞是指线程在操作系统层面被挂起。阻塞一般性能不好,需大约8万个时钟周期来做调度。
非阻塞则允许多个线程同时进入临界区。
- 死锁
死锁是进程死锁的简称,是指多个进程循环等待他方占有的资源而无限的僵持下去的局面。
- 活锁
假设有两个线程1、2,它们都需要资源 A/B,假设1号线程占有了 A 资源,2号线程占有了 B 资源;由于两个线程都需要同时拥有这两个资源才可以工作,为了避免死锁,1号线程释放了 A 资源占有锁,2号线程释放了 B 资源占有锁;此时 AB 空闲,两个线程又同时抢锁,再次出现上述情况,此时发生了活锁。
简单类比,电梯遇到人,一个进的一个出的,对面占路,两个人同时往一个方向让路,来回重复,还是堵着路。
如果线上应用遇到了活锁问题,恭喜你中奖了,这类问题比较难排查。
- 饥饿
饥饿是指某一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行。
2.2 java实现并发的方式
一般都是通过加锁进行实现。
2.2.1 synchronize关键字
- 用法1
1 | public class Test{ |
- 用法2
1 | public class Test{ |
- 用法3
1 | public class Test{ |
- 静态方法的锁
1 | public class Test{ |
2.2.2 java.util.Lock的使用
Lock能实现synchronized完成的所以功能,不同点是:Lock有比synchronize更精确的线程语义和更好的性能,synchronize会自动释放锁,而Lock一定要求程序员手工释放,并且必须在finally从句中释放。Lock还有更强大的功能,例如,它的tryLock方法可以非阻塞方式去拿锁。
1 | final Lock lock = new ReentrantLock(); |
2.2.3 使用线程池
通过上面的介绍,完全可以开发一个多线程的程序,为什么还要引入线程池呢。主要是因为上述单线程方式存在以下几个问题:
- 线程的工作周期:线程创建所需时间为 T1,线程执行任务所需时间为 T2,线程销毁所需时间为 T3,往往是 T1+T3 大于 T2,所有如果频繁创建线程会损耗过多额外的时间;
- 如果有任务来了,再去创建线程的话效率比较低,如果从一个池子中可以直接获取可用的线程,那效率会有所提高。所以线程池省去了任务过来,要先创建线程再去执行的过程,节省了时间,提升了效率;
- 线程池可以管理和控制线程,因为线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控;
- 线程池提供队列,存放缓冲等待执行的任务。
四种线程池的使用
newCachedThreadPool :创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
1
2
3
4
5ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
cachedThreadPool.execute(new Runnable() {
public void run() {
System.out.println(index);
}newFixedThreadPool :创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
1 | ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3); |
newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行
1
2
3
4
5
6ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool.schedule(new Runnable() {
public void run() {
System.out.println("delay 3 seconds");
}
}, 3, TimeUnit.SECONDS); //延迟3秒执行newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
1
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
2.2.4 wait 和 sleep 理解
对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object
类中的。 sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态
依然保持者,当指定的时间到了又会自动恢复运行状态。
在调用sleep()方法的过程中,线程不会释放对象锁。
而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对
此对象调用notify()方法或notifyAll()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
3. java异常
3.1 异常类导图
3.2 异常的使用
一般都是通过try···catch···finally 进行处理
3.2.1 try、catch、finally
1 | try{ |
需要注意的是:
- try块中的局部变量和catch块中的局部变量(包括异常变量),以及finally中的局部变量,他们之间不可共享使用。
- 每一个catch块用于处理一个异常。异常匹配是按照catch块的顺序从上往下寻找的,只有第一个匹配的catch会得到执行。
- try{}里有一个return语句,那么紧跟在这个try后的finally{}里代码会在return之前执行,更准确的说,是在return中间执行,看下面例子:
1
2
3
4
5
6
7
8
9
10
11public int test(){
int x = 1;
try{
return x;
}finally{
System.out.print("执行了");
++x;
}
}
//执行结果是 :执行了
//但是 x = 1
3.2.2 抛出异常
throws 抛到上一层(用在方法上)
1
2
3
4
5public void foo() throws SQLException, IOException,ClassNotFloundException
{
//foo内部可以抛出 SQLException, IOException,ClassNotFloundException 类的异常
//或者他们的子类的异常对象。
}throw 抛出异常 (用在方法内部)
1
2
3
4public void save(User user){
if(user == null)
throw new IllegalArgumentException("User对象为空");
}
4. java反射
4.1 反射的概述
反射是框架设计的灵魂
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.
以上的总结就是什么是反射
反射就是把java类中的各种成分映射成一个个的Java对象
例如:一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把个个组成部分映射成一个个对象。
(其实:一个类中这些成员方法、构造方法、在加入类中都有一个类来描述)
如图是类的正常加载过程:反射的原理在与class对象。
熟悉一下加载的时候:Class对象的由来是将class文件读入内存,并为之创建一个Class对象。
4.2 反射中常用的方法
4.2.1 获得Class对象
Object ——> getClass();
1
2Student stu1 = new Student();//产生一个Student对象,一个Class对象。
Class stuClass1 = stu1.getClass();//获得Class对象任何数据类型(包括基本数据类型)都有一个“静态”的class属性
1 | Class stuClass2 = Student.class; |
- 通过Class类的静态方法:forName(String className)(常用)
1
Class stuClass3 = Class.forName("fanshe.Student");
4.2.2 获得构造方法
1 | //1.加载Class对象 |
4.2.3 获得成员属性并设值
1 | //1.获取Class对象 |
4.2.4 获得成员方法并调用
1 | //获取Class对象 |
4.2.5 获得main 方法
1 | //1.获取Student对象的字节码 |
5. java常用的设计模式
5.1 单例模式
单例模式可以保证系统中一个类只有一个实例。即一个类只有一个对象实例
- 饿汉模式
1
2
3
4
5
6
7public class Singleton{
private static Singleton instance = new Singleton(){}
private Singleton(){}//将构造私有化
public static Singleton getInstance(){
return instance;
}
}
对于饿汉模式来说,这种写法已经很‘perfect’了,
唯一的缺点就是,由于instance的初始化是在类加载时进行的,
类加载是由ClassLoader来实现的,如果初始化太早,就会造成资源浪费
- 懒汉模式
1
2
3
4
5
6
7
8
9
10public class Singleton{
private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){
if(instance==null){
return new Singleton();
}
return instance;
}
}
这种写法在单线程的时候是没问题的。
但是,当有多个线程一起工作的时候,如果有两个线程同时运行到 if (instance == null),
都判断为null(第一个线程判断为空之后,并没有继续向下执行,当第二个线程判断的时候instance依然为空)
最终两个线程就各自会创建一个实例出来。这样就破环了单例模式 实例的唯一性,
双重检查模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class Singleton{
//volatile关键字的一个作用是禁止指令重排,
//把instance声明为volatile之后,对它的写操作就会有一个内存屏障
//这样,在它的赋值完成之前,就不用会调用读操作。
private static volatile Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){
if(instance==null){
sysnchronized(Singleton.class){
if(instance==null){
return new Singleton();
}
}
}
return instance;
}
}静态内部类实现
1
2
3
4
5
6
7
8
9public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
它利用了ClassLoader来保证了同步,同时又能让开发者控制类加载的时机。从内部看是一个饿汉式的单例,但是从外部看来,又的确是懒汉式的实现
5.2 工厂模式
5.3 代理模式
5.4 适配器模式
5.5 状态模式
5.6 观察者模式
具体内容请移步: