MyBatis中默认定义了两级缓存,分别是一级缓存和二级缓存。
(1)默认情况下,只有一级缓存(SqlSession级别的缓存,也称为本地缓存)开启。
(2)二级缓存需要手动开启和配置,二级缓存是基于namespace级别的缓存。
(3)为了提高扩展性,MyBatis定义了缓存接口Cache,我们可以通过实现Cache接口来自定义二级缓存。
- 一级缓存(local cache),即本地缓存,作用域默认为SqlSession。当Session flush 或close 后,该Session中所有Cache将被清空。
- 本地缓存不能被关闭,但可以调用clearCache()来清空本地缓存,或者改变缓存的作用域。
- mybatis3.1之后,可以配置本地缓存的作用域,在mybatis.xml中配置。
- 不同的SqlSession对应不同的一级缓存。
- 同一个SqlSession但是查询条件不同。
- 同一个SqlSession的两次查询期间执行了增删改操作。
- 同一个SqlSession的两次查询期间手动清空了缓存。
- 二级缓存(second level cache),是全局作用域缓存。
- 二级缓存默认不开启,需要手动配置。
- MyBatis提供二级缓存的接口以及实现,实现二级缓存要求被查询的JavaBean实现Serializable接口。
- 二级缓存在SqlSession关闭或提交之后才会生效。
- 在mybatis全局配置文件中开启二级缓存 < setting name=“cacheEnabled” value=“true”/>。
- 在需要使用二级缓存的mapper映射文件中使用< cache />标签配置缓存。
- 被查询的JavaBean需要实现Serializable接口。
缺点:
- 长度开始必须指定,而且一旦指定,无法修改;
- 保存的必须为同一类型的元素;
- 使用数组进行增删操作比较麻烦;
优点:读取可以使用下标直接进行。
2:集合好处:
- 可以动态保存多个对象,使用方便;
- 提供了一系列方便的操作对象的方法,如add、remove、set、get等;
- 使用集合增删较方便。
分类:单列集合(Collection接口) 双列集合(Map接口)
- Collection实现子类可以存放多个元素,每个元素可以是Object
- 有些Collection的实现类,可以存放重复的元素,有些不可以
- 有些Collection的实现类,可以是有序的(List), 有些也是无序的(Set)
- Collection接口没有直接的实现子类,是通过他的子接口Set和List来实现的
Iterator对象称为迭代器,主要用于遍历Collection集合中的元素;所有实现了Collection接口的集合类都有一个iterator()方法,用于返回一个实现了Iterator接口的对象,即可以返回一个迭代;Iterator仅用于遍历集合,Iterator本身不存放对象。
5:List接口的特点- List集合类中元素有序(添加顺序和取出顺序一致),且可重复
- List集合中的每个元素都有其对应的顺序索引,即支持索引
- List容器中的元素都对应一个整数型的序号记载其在容器中的位置,额可以根据序号存取容器中的元素
- JDK API中List接口的实现类非常多,常用的有ArrayList、LinkedList、Vector
特点:
- 允许存储所有元素,包括null,ArrayList可以加入一个或者多个null
- ArrayList是由数组来实现数据存储的
- ArrayList基本等同于Vector,除了ArrayList是线程不安全的(源码中没有synchronized关键字,执行效率高)。
底层操作机制源码分析:
- 创建ArrayList对象的时候,如果使用的是无参构造器,则初始elementData容量为0,第一次添加,则扩容elementData为10,如需再次扩容,
则扩容elementData1.5倍 - 如果使用的是指定大小的构造器,则初始化elementData容量为指定大小,如果需要扩容,则直接扩容elementData为1.5倍
构造分析:
- LinkedList底层实现了双向链表和双端队列的特点
- 可以添加任意元素(可以重复), 包括null
- 线程不安全,没有实现同步
特点:
- HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。
- HashMap 实现了 Map 接口,根据键的 HashCode 值存储数据,具有很快的访问速度,最多允许一条记录的键为null,不支持线程同步。
- HashMap 是无序的,即不会记录插入的顺序。
取元素:
设置元素:
1、区别
- a. lock是一个接口,而synchronized是java的一个关键字。
- b. synchronized在发生异常时会自动释放占有的锁,因此不会出现死锁;而lock发生异常时,不会主动释放占有的锁,必须手动来释放锁,可能引起死锁的发生
2、 synchronized的实现
Java中每一个对象都可以作为锁,synchronized实现同步的基础:
普通同步方法,锁是当前实例对象
静态同步方法,锁是当前类的class对象
同步方法块,锁是括号里面的对象
同步代码块是使用monitorenter和monitorexit指令实现的,同步方法(在这看不出来需要看JVM底层实现)依靠的是方法修饰符上的ACC_SYNCHRONIZED实现
b:同步代码块:monitorenter指令是在编译后插入到同步代码块的开始位置,monitorexit指令插入到同步代码块的结束位置,JVM需要保证每一个monitorenter都有一个monitorexit与之相对应。任何对象都有一个monitor与之相关联,当且一个monitor被持有之后,他将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor所有权,即尝试获取对象的锁;
c:同步方法:synchronized方法则会被翻译成普通的方法调用和返回指令如:invokevirtual、areturn指令,在VM字节码层面并没有任何特别的指令来实现被synchronized修饰的方法,而是在Class文件的方法表中将该方法的access_flags字段中的synchronized标志位置1,表示该方法是同步方法并使用调用该方法的对象或该方法所属的Class在JVM的内部对象表示Klass做为锁对象
d:区别
用法区别:
- synchronized:在需要同步的对象中加入此控制,synchronized可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象。
- lock:一般使用ReentrantLock类做为锁。在加锁和解锁处需要通过lock()和unlock()显示指出。所以一般会在finally块中写unlock()以防死锁。
性能区别:
synchronized是托管给JVM执行的,
而lock是java写的控制锁的代码。
在Java1.5中,synchronize是性能低效的。因为这是一个重量级操作,需要调用操作接口,导致有可能加锁消耗的系统时间比加锁以外的操作还多。相比之下使用Java提供的Lock对象,性能更高一些。
但是到了Java1.6,发生了变化。synchronize在语义上很清晰,可以进行很多优化,有适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致在Java1.6上synchronize的性能并不比Lock差。官方也表示,他们也更支持synchronize,在未来的版本中还有优化余地。
两种机制区别:
synchronized原始采用的是CPU悲观锁机制,即线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。而在CPU转换线程阻塞时会引起线程上下文切换,当有很多线程竞争锁的时候,会引起CPU频繁的上下文切换导致效率很低。
而Lock用的是乐观锁方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁实现的机制就是CAS操作(Compare and Swap)。我们可以进一步研究ReentrantLock的源代码,会发现其中比较重要的获得锁的一个方法是compareAndSetState。这里其实就是调用的CPU提供的特殊指令。
现代的CPU提供了指令,可以自动更新共享数据,而且能够检测到其他线程的干扰,而 compareAndSet() 就用这些代替了锁定。这个算法称作非阻塞算法,意思是一个线程的失败或者挂起不应该影响其他线程的失败或挂起的算法。
用途区别:
synchronized原语和ReentrantLock在一般情况下没有什么区别,但是在非常复杂的同步应用中,请考虑使用ReentrantLock,特别是遇到下面2种需求的时候。
1.某个线程在等待一个锁的控制权的这段时间需要中断
2.需要分开处理一些wait-notify,ReentrantLock里面的Condition应用,能够控制notify哪个线程
3.具有公平锁功能,每个到来的线程都将排队等候
- (1)wait():一旦执行此方法当前线程进入阻塞状态,并释放同步监视器
- (2)notify():一旦执行此方法就会唤醒被wait的另一个线程。如果存在多个被wait的线程,唤醒优先级高的线程。
- (3)notifyAll():一旦执行此方法就会唤醒所有被wait的线程
注意:
-
(1)wait()、notify()、notifyAll()三个方法必须在同步代码块或同步方法里面使用
-
(2)wait()、notify()、notifyAll()三个方法的调用者必须是同步代码块或同步方法里面的同步监视器
如下面的,obj对象作为同步监视器的话其他地方调用这个三个方法也需要使用obj对象 -
(3)因为任何一个对于多线程来说是唯一的对象都可以充当同步监视器,所以上面三个方法是定义在Object对象里面的
- (1)两个方法声明的位置不同,sleep()方法声明在Thread类里面,wait()方法声明在Object类里面
- (2)调用要求不同,sleep()方法可以在任何需要的场景下面使用,wait()方法必须在同步代码块或同步方法里面使用
- (3)关于释放同步监视器的问题:如果sleep()和wait()方法都在步代码块或同步方法里面使用时, sleep()方法不会释放同步监视器,而wait()方法会释放同步监视器
a、什么是线程池
用一句话来概述就是:线程池是指在初始化一个多线程应用程序过程中创建一个线程集合,然后再需要执行新的任务时重用这些线程而不是新建线程。
b、为什么使用线程池
使用线程池最大的原因就是可以根据系统的需求和硬件环境灵活的控制线程的数量,且可以对所有线程进行统一的管理和控制,从而提高系统的运行效率,降低系统的运行压力。
c、线程池有那些优势
- 降低资源消耗:线程和任务分离,提高线程重用性 控制线程并发数量,降低服务器压力,统一管理所有线程
- 提高系统响应速度。假如创建线程用的时间为T1,执行任务的时间为T2,销毁线程的时间为T3,那么使用线程池就免去了T1和T3的时间。
d、线程池使用
Java内置线程池:ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
RejectedExecutionHandler handler)
corePoolSize:线程池核心线程数量
maximumPoolSize:线程池最大线程数量
keepAliverTime:当活跃线程数大于核心线程数时,空闲的多余线程最大存活时间
unit:存活时间的单位
workQueue:存放任务的队列
handler:超出线程范围和队列容量的任务的处理程序
e、创建顺序
核心线程 -> 队列 -> 最大线程 -> 拒绝策略
f、workQueue
ArrayBlockingQueue 基于数组的有界阻塞队列
LinkedBlockingQuene 基于链表的无界阻塞队列
SynchronousQuene 一个不缓存任务的阻塞队列
PriorityBlockingQueue 具有优先级的无界阻塞队列
g、
通过Executor工厂类获取线程池有三种方式,这三种方式都是通过Executors类中的静态方法来获取的:
- 通过newCachedThreadPool获取线程池对象 特点是:创建一个默认的线程池对象,里面的线程可重用,且在第一次使用时才创建
- 通过newFixedThreadPool获取线程池对象 特点是:可指定创建线程数,并且可以重复用
- 通过newSingleThreadExecutor获取线程池对象 特点是:只会创建一个线程
第一种:newCachedThreadPool:线程的数据是不做限制的,每次有任务来的时候都会以任务优先,性能最大化(也就是服务器压力比较大)
第二种:newFixedThreadPool:可以让压力不那么大,并且可以规定线程的数量,当线程的数量达到指定数量的时候,这个时候就不会再有新的线程了
第三种:newSingleThreadExecutor:绝对的安全,不考虑性能,因为是单线程,永远只有一个线程来执行任务



