文章目录
- 1、多态
- 2、final参数
- 3、finally
- 4、hashCode()相同,equals() 也一定为 true吗?
- 5、integer类型的比较推荐用equals
- 6、Runnable 和 Callable 有什么区别
- 7、线程池中 submit() 和 execute()方法有什么区别?
- 8、包装类型与基本类型比较会自动拆箱
- 10、java锁之公平和非公平锁
- 11、java锁之自旋锁理论知识
- 12、java锁之读写锁理论知识
- 13、CountDownLatch
- 14、CyclicBarrierDemo
- 15、Synchronized和Lock有什么区别
- 16、锁绑定多个条件Condition
- 17、线程池使用及优势
- 18、线程池7大参数深入介绍
- 19、线程池配置合理线程数
- 20、jconsole jvisualvm javaGUI图像化监测工具
- 21、新增一条数据返回主键 useGeneratedKeys="true"
- 22、ThreadLocal的基本使用
- 23索引失效的情况有哪些?
- 24redis默认持久化
- 25、RDB优缺点
- 26、 AOF优缺点
- 27、redis五种类型的应用场景
1、多态
2、final参数
3、finally
finally中如果有return语句 则会返回finally中的 return b
try{
return a;
}catch(){
...
}finally{
return b;
}
4、hashCode()相同,equals() 也一定为 true吗?
hashCode() 返回该对象的哈希码值;equals() 返回两个对象是否相等。
hashcode相等,equals()不一定相等。
equals()相同,hashcode一定相等(前提:没有重写的情况下)
5、integer类型的比较推荐用equals
integer类型会缓存-128~127,在这个范围内地址一样,== 对于引用类型 比较的是地址 所以会返回true
6、Runnable 和 Callable 有什么区别
主要区别
Runnable 接口 run 方法无返回值;Callable 接口 call 方法有返回值,支持泛型
Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;Callable 接口 call 方法允许抛出异常,可以获取异常信息
7、线程池中 submit() 和 execute()方法有什么区别?
execute() 参数 Runnable ;submit() 参数 (Runnable) 或 (Runnable 和 结果 T) 或 (Callable)
execute() 没有返回值;而 submit() 有返回值
submit() 的返回值 Future 调用get方法时,可以捕获处理异常
8、包装类型与基本类型比较会自动拆箱
intValue():拆箱
valueOf():装箱
## 9、CAS是什么
Compare And Set
示例程序
public class CASDemo{
public static void main(string[] args){
AtomicInteger atomicInteger = new AtomicInteger(5);// mian do thing. . . . ..
System.out.println(atomicInteger.compareAndSet(5, 2019)+"\t current data: "+atomicInteger.get());
System.out.println(atomicInteger.compareAndset(5, 1024)+"\t current data: "+atomicInteger.get());
}
}
输出结果为
true 2019
false 2019
10、java锁之公平和非公平锁
公平锁―是指多个线程按照申请锁的顺序来获取锁,类似排队打饭,先来后到。
非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后中请的线程比先中请的线程优先获取锁。
在高并发的情况下,有可能会造成优先级反转或者饥饿现象
并发包中ReentrantLock的创建可以指定构造函数的boolean类型来得到公平锁或非公平锁,默认是非公平锁。
Java ReentrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。
非公平锁的优点在于吞吐量比公平锁大。
对于Synchronized而言,也是一种非公平锁
11、java锁之自旋锁理论知识
自旋锁(Spin Lock)
是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU
12、java锁之读写锁理论知识
独占锁:指该锁一次只能被一个线程所持有。对ReentrantLock和Synchronized而言都是独占锁
共享锁:指该锁可被多个线程所持有。
多个线程同时读一个资源类没有任何问题,所以为了满足并发量,读取共享资源应该可以同时进行。但是,如果有一个线程想去写共享资源来,就不应该再有其它线程可以对该资源进行读或写。
对ReentrantReadWriteLock其读锁是共享锁,其写锁是独占锁。
读锁的共享锁可保证并发读是非常高效的,读写,写读,写写的过程是互斥的。
13、CountDownLatch
让一线程阻塞直到另一些线程完成一系列操作才被唤醒。
CountDownLatch主要有两个方法(await(),countDown())。
当一个或多个线程调用await()时,调用线程会被阻塞。其它线程调用countDown()会将计数器减1(调用countDown方法的线程不会阻塞),当计数器的值变为零时,因调用await方法被阻塞的线程会被唤醒,继续执行。
14、CyclicBarrierDemo
CyclicBarrier的字面意思就是可循环(Cyclic)使用的屏障(Barrier)。它要求做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活,线程进入屏障通过CyclicBarrier的await方法。
CyclicBarrier与CountDownLatch的区别:CyclicBarrier可重复多次,而CountDownLatch只能是一次。
程序演示集齐7个龙珠,召唤神龙
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class SummonTheDragonDemo {
public static void main(String[] args) {
/**
* 定义一个循环屏障,参数1:需要累加的值,参数2 需要执行的方法
*/
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println("召唤神龙");
});
for (int i = 1; i <= 7; i++) {
final Integer tempInt = i;
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t 收集到 第" + tempInt + "颗龙珠");
try {
// 先到的被阻塞,等全部线程完成后,才能执行方法
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();
}
}
}
15、Synchronized和Lock有什么区别
16、锁绑定多个条件Condition
实现场景
多线程之间按顺序调用,实现 A-> B -> C 三个线程启动,要求如下:
AA打印5次,BB打印10次,CC打印15次
紧接着
AA打印5次,BB打印10次,CC打印15次
…
来10轮
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class ShareResource {
// A 1 B 2 c 3
private int number = 1;
// 创建一个重入锁
private Lock lock = new ReentrantLock();
// 这三个相当于备用钥匙
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
public void print5() {
lock.lock();
try {
// 判断
while(number != 1) {
// 不等于1,需要等待
condition1.await();
}
// 干活
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "\t " + number + "\t" + i);
}
// 唤醒 (干完活后,需要通知B线程执行)
number = 2;
// 通知2号去干活了
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print10() {
lock.lock();
try {
// 判断
while(number != 2) {
// 不等于1,需要等待
condition2.await();
}
// 干活
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "\t " + number + "\t" + i);
}
// 唤醒 (干完活后,需要通知C线程执行)
number = 3;
// 通知2号去干活了
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print15() {
lock.lock();
try {
// 判断
while(number != 3) {
// 不等于1,需要等待
condition3.await();
}
// 干活
for (int i = 0; i < 15; i++) {
System.out.println(Thread.currentThread().getName() + "\t " + number + "\t" + i);
}
// 唤醒 (干完活后,需要通知C线程执行)
number = 1;
// 通知1号去干活了
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class SynchronizedAndReentrantLockDemo {
public static void main(String[] args) {
ShareResource shareResource = new ShareResource();
int num = 10;
new Thread(() -> {
for (int i = 0; i < num; i++) {
shareResource.print5();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < num; i++) {
shareResource.print10();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < num; i++) {
shareResource.print15();
}
}, "C").start();
}
}
...
A 1 0
A 1 1
A 1 2
A 1 3
A 1 4
B 2 0
B 2 1
B 2 2
B 2 3
B 2 4
B 2 5
B 2 6
B 2 7
B 2 8
B 2 9
C 3 0
C 3 1
C 3 2
C 3 3
C 3 4
C 3 5
C 3 6
C 3 7
C 3 8
C 3 9
C 3 10
C 3 11
C 3 12
C 3 13
C 3 14
A 1 0
A 1 1
A 1 2
A 1 3
A 1 4
B 2 0
B 2 1
B 2 2
B 2 3
B 2 4
B 2 5
B 2 6
B 2 7
B 2 8
B 2 9
C 3 0
C 3 1
C 3 2
C 3 3
C 3 4
C 3 5
C 3 6
C 3 7
C 3 8
C 3 9
C 3 10
C 3 11
C 3 12
C 3 13
C 3 14
17、线程池使用及优势
线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量超出数量的线程排队等候,等其它线程执行完毕,再从队列中取出任务来执行。
它的主要特点为:线程复用,控制最大并发数,管理线程。
优点:
降低资源消耗。通过重复利用己创建的线程降低线程创建和销毁造成的消耗。
提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
18、线程池7大参数深入介绍
corePoolSize:线程池中的常驻核心线程数
在创建了线程池后,当有请求任务来之后,就会安排池中的线程去执行请求任务,近似理解为今日当值线程。
当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中。
maximumPoolSize:线程池能够容纳同时执行的最大线程数,此值必须大于等于1
keepAliveTime:多余的空闲线程的存活时间。
当前线程池数量超过corePoolSize时,当空闲时间达到keepAliveTime值时,多余空闲线程会被销毁直到只剩下corePoolSize个线程为止
unit:keepAliveTime的单位。
workQueue:任务队列,被提交但尚未被执行的任务。
threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程一般用默认的即可。
handler:拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数( maximumPoolSize)。
19、线程池配置合理线程数
合理配置线程池你是如何考虑的?
CPU密集型
CPU密集的意思是该任务需要大量的运算,而没有阻塞,CPU一直全速运行。
CPU密集任务只有在真正的多核CPU上才可能得到加速(通过多线程),
而在单核CPU上,无论你开几个模拟的多线程该任务都不可能得到加速,因为CPU总的运算能力就那些。
CPU密集型任务配置尽可能少的线程数量:
一般公式:(CPU核数+1)个线程的线程池
lO密集型
由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如CPU核数 * 2。
IO密集型,即该任务需要大量的IO,即大量的阻塞。
在单线程上运行IO密集型的任务会导致浪费大量的CPU运算能力浪费在等待。
所以在IO密集型任务中使用多线程可以大大的加速程序运行,即使在单核CPU上,这种加速主要就是利用了被浪费掉的阻塞时间。
IO密集型时,大部分线程都阻塞,故需要多配置线程数:
参考公式:CPU核数/ (1-阻塞系数)
阻塞系数在0.8~0.9之间
比如8核CPU:8/(1-0.9)=80个线程数
20、jconsole jvisualvm javaGUI图像化监测工具
cmd 输入jvisualvm (推荐)
21、新增一条数据返回主键 useGeneratedKeys=“true”
useGeneratedKeys=“true” keyProperty=“id”
useGeneratedKeys:必须设置为true,否则无法获取到主键id。
keyProperty:设置为POJO对象的主键id属性名称。
22、ThreadLocal的基本使用
ThreadLocal:提供线程内部的局部变量,不同的线程之间不可以互相干扰,这种变量只在线程的声明周期中起作用
总结:线程并发 传递数据 线程隔离
public class MyThreadLocal {
ThreadLocal<String> threadLocal = new ThreadLocal();
private String name;
public String getName() {
// return name;
return threadLocal.get();
}
public void setName(String name) {
// this.name = name;
threadLocal.set(name);
}
public static void main(String[] args) {
MyThreadLocal local = new MyThreadLocal();
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(() -> {
local.setName(Thread.currentThread().getName() + "的数据");
System.out.println("---------------");
System.out.println(Thread.currentThread().getName() + "-------->" + local.getName());
});
thread.setName("线程"+i);
thread.start();
}
}
}
23索引失效的情况有哪些?
1、like 以%开头索引无效,当 like 以&结尾,索引有效。
2、or 语句前后没有同事使用索引,当且仅当 or 语句查询条件的前后列均为索引时,索引 生效。
3、组合索引,使用的不是第一列索引时候,索引失效,即最左匹配规则。
4、数据类型出现隐式转换,如 varchar 不加单引号的时候可能会自动转换为 int 类型,这 个时候索引失效。
5、在索引列上使用 IS NULL 或者 IS NOT NULL 时候,索引失效,因为索引是不索引空值 得。
6、在索引字段上使用,NOT、 <>、!= 、时候是不会使用索引的,对于这样的处理只会进 行全表扫描。
7、对索引字段进行计算操作,函数操作时不会使用索引。
8、当全表扫描速度比索引速度快的时候不会使用索引。
24redis默认持久化
rdb(默认)
rdb是redis的一种数据持久化策略,redis将某一时间点的数据全部打包生成一个.rdb的文件,保存在磁盘中,当我们重启redis服务的时候,将会读取该rdb文件恢复数据库中的数据
aof
aof同样是redis的持久化策略,采用该策略的时候,redis会将被执行的写命令添加到aof文件的末尾,该文件被保留在磁盘中。当重启redis服务的时候会优先(相对于rdb文件而言)读取aof文件,完成对redis数据的恢复
25、RDB优缺点
定时备份,Redis效率高,但是容易造成数据丢失,丢失的多少和备份策略有关,例如:5分钟备份一次,但是第8分时宕机了,那么就丢失了后面的3分钟数据
26、 AOF优缺点
AOF基本可以保证数据不丢失,但是AOF持久化文件会变的越来越大。例如我们调用incr test命令100次,文件中必须保存全部的100条命令,其实有99条都是多余的。
27、redis五种类型的应用场景
1、string
此类型和memcache相似,作为常规的key-value缓存应用。
例如微博数、粉丝数等
注:一个键最大能存储512MB
2、hash
redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象(应为对象可能会包含很多属性)
常用命令:hget hset hgetall
主要用来存储对象信息
3、list
list列表是简单的字符串列表,按照插入顺序排序(内部实现为LinkedList),可以选择将一个链表插入到头部或尾部
常用命令 :lpush(添加左边元素),rpush,lpop(移除左边第一个元素),rpop,lrange(获取列表片段,LRANGE key start stop)等。
应用场景:Redis list的应用场景非常多,也是Redis最重要的数据结构之一,比如twitter的关注列表,粉丝列表等都可以用Redis的list结构来实现。
4、set
案例:在微博中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis还为集合提供了求交集、并集、差集等操作,可以非常方便的实现如共同关注、共同喜好、二度好友等功能,对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存集到一个新的集合中。
5、zset
常用命令:zadd,zrange
实现方式:Redis sorted set的内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序,HashMap里放的是成员到score的映射,跳跃表按score从小到大保存所有集合元素。使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。时间复杂度与红黑树相同,增加、删除的操作较为简单。
输入方式
应用场景:排行榜