1.集合
2.线程
- 并行和并发有什么区别?
• 并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。
• 并行是在不同实体上的多个事件,并发是在同一实体上的多个事件。
• 在一台处理器上“同时”处理多个任务,在多台处理器上同时处理多个任务。如 hadoop 分布式集
群。
所以并发编程的目标是充分的利用处理器的每一个核,以达到最高的处理性能。 - 线程和进程的区别?
简而言之,进程是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线
程。进程在执行过程中拥有独立的内存单元,而多个线程共享内存资源,减少切换次数,从而效率更高。
线程是进程的一个实体,是 cpu 调度和分派的基本单位,是比程序更小的能独立运行的基本单位。同一
进程中的多个线程之间可以并发执行。 - 守护线程是什么?
守护线程(即 daemon thread),是个服务线程,准确地来说就是服务其他的线程。 - 创建线程有哪几种方式?
①. 继承 Thread 类创建线程类
• 定义 Thread 类的子类,并重写该类的 run 方法,该 run 方法的方法体就代表了线程要完成的任
务。因此把 run()方法称为执行体。
• 创建 Thread 子类的实例,即创建了线程对象。
• 调用线程对象的 start()方法来启动该线程。
②. 通过 Runnable 接口创建线程类
• 定义 runnable 接口的实现类,并重写该接口的 run()方法,该 run()方法的方法体同样是该线程的
线程执行体。
• 创建 Runnable 实现类的实例,并依此实例作为 Thread 的 target 来创建 Thread 对象,该
Thread 对象才是真正的线程对象。
• 调用线程对象的 start()方法来启动该线程。
③. 通过 Callable 和 Future 创建线程
• 创建 Callable 接口的实现类,并实现 call()方法,该 call()方法将作为线程执行体,并且有返回值。
• 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装
了该 Callable 对象的 call()方法的返回值。
• 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
• 调用 FutureTask 对象的 get()方法来获得子线程执行结束后的返回值。 - 说一下 runnable 和 callable 有什么区别?
有点深的问题了,也看出一个 Java 程序员学习知识的广度。
• Runnable 接口中的 run()方法的返回值是 void,它做的事情只是纯粹地去执行 run()方法中的代码
而已;
• Callable 接口中的 call()方法是有返回值的,是一个泛型,和 Future、FutureTask 配合可以用来获
取异步执行的结果。 - 线程有哪些状态?
线程通常都有五种状态,创建、就绪、运行、阻塞和死亡。
• 创建状态。在生成线程对象,并没有调用该对象的 start 方法,这是线程处于创建状态。
• 就绪状态。当调用了线程对象的 start 方法之后,该线程就进入了就绪状态,但是此时线程调度程
序还没有把该线程设置为当前线程,此时处于就绪状态。在线程运行之后,从等待或者睡眠中回来
之后,也会处于就绪状态。
• 运行状态。线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开
始运行 run 函数当中的代码。
• 阻塞状态。线程正在运行的时候,被暂停,通常是为了等待某个时间的发生(比如说某项资源就绪)
之后再继续运行。sleep,suspend,wait 等方法都可以导致线程阻塞。
• 死亡状态。如果一个线程的 run 方法执行结束或者调用 stop 方法后,该线程就会死亡。对于已经
死亡的线程,无法再使用 start 方法令其进入就绪 - sleep() 和 wait() 有什么区别?
sleep():方法是线程类(Thread)的静态方法,让调用线程进入睡眠状态,让出执行机会给其他线程,
等到休眠时间结束后,线程进入就绪状态和其他线程一起竞争 cpu 的执行时间。因为 sleep() 是 static
静态的方法,他不能改变对象的机锁,当一个 synchronized 块中调用了 sleep() 方法,线程虽然进入休
眠,但是对象的机锁没有被释放,其他线程依然无法访问这个对象。
wait():wait()是 Object 类的方法,当一个线程执行到 wait 方法时,它就进入到一个和该对象相关的等
待池,同时释放对象的机锁,使得其他线程能够访问,可以通过 notify,notifyAll 方法来唤醒等待的线
程。 - notify()和 notifyAll()有什么区别?
• 如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去
竞争该对象的锁。
• 当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个
wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就
是说,调用了 notify 后只要一个线程会由等待池进入锁池,而 notifyAll 会将该对象等待池内的所
有线程移动到锁池中,等待锁竞争。
• 优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯
有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,
直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象
锁。 - 线程的 run()和 start()有什么区别?
每个线程都是通过某个特定 Thread 对象所对应的方法 run()来完成其操作的,方法 run()称为线程体。通
过调用 Thread 类的 start()方法来启动一个线程。
start()方法来启动一个线程,真正实现了多线程运行。这时无需等待 run 方法体代码执行完毕,可以直接
继续执行下面的代码; 这时此线程是处于就绪状态, 并没有运行。 然后通过此 Thread 类调用方法
run()来完成其运行状态, 这里方法 run()称为线程体,它包含了要执行的这个线程的内容, Run 方法运
行结束, 此线程终止。然后 CPU 再调度其它线程。
run()方法是在本线程里的,只是线程里的一个函数,而不是多线程的。 如果直接调用 run(),其实就相当于
是调用了一个普通函数而已,直接待用 run()方法必须等待 run()方法执行完毕才能执行下面的代码,所以
执行路径还是只有一条,根本就没有线程的特征,所以在多线程执行时要使用 start()方法而不是 run()方
法。 - 创建线程池有哪几种方式?
①. newFixedThreadPool(int nThreads)
创建一个固定长度的线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数量,这时线程
规模将不再变化,当线程发生未预期的错误而结束时,线程池会补充一个新的线程。
②. newCachedThreadPool()
创建一个可缓存的线程池,如果线程池的规模超过了处理需求,将自动回收空闲线程,而当需求增加时,
则可以自动添加新线程,线程池的规模不存在任何限制。
③. newSingleThreadExecutor()
这是一个单线程的 Executor,它创建单个工作线程来执行任务,如果这个线程异常结束,会创建一个新
的来替代它;它的特点是能确保依照任务在队列中的顺序来串行执行。
④. newScheduledThreadPool(int corePoolSize)
创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于 Timer。 - 线程池都有哪些状态?
线程池有 5 种状态:Running、ShutDown、Stop、Tidying、Terminated。
线程池各个状态切换框架图: - 线程池中 submit()和 execute()方法有什么区别?
• 接收的参数不一样
• submit 有返回值,而 execute 没有
• submit 方便 Exception 处理 - 在 java 程序中怎么保证多线程的运行安全?
线程安全在三个方面体现:
• 原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作,(atomic,synchronized);
• 可见性:一个线程对主内存的修改可以及时地被其他线程看到,(synchronized,volatile);
• 有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱无序,
(happens-before 原则)。 - 多线程锁的升级原理是什么?
在 Java 中,锁共有 4 种状态,级别从低到高依次为:无状态锁,偏向锁,轻量级锁和重量级锁状态,这
几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级。
锁升级的图示过程: - 什么是死锁?
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现
象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互
相等待的进程称为死锁进程。是操作系统层面的一个错误,是进程死锁的简称,最早在 1965 年由
Dijkstra 在研究银行家算法时提出的,它是计算机操作系统乃至整个并发程序设计领域最难处理的问题之
一。 - 怎么防止死锁?
死锁的四个必要条件:
• 互斥条件:进程对所分配到的资源不允许其他进程进行访问,若其他进程访问该资源,只能等待,
直至占有该资源的进程使用完成后释放该资源
• 请求和保持条件:进程获得一定的资源之后,又对其他资源发出请求,但是该资源可能被其他进程
占有,此事请求阻塞,但又对自己获得的资源保持不放
• 不可剥夺条件:是指进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用完后自己释
放
• 环路等待条件:是指进程发生死锁后,若干进程之间形成一种头尾相接的循环等待资源关系
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之 一不满足,
就不会发生死锁。
理解了死锁的原因,尤其是产生死锁的四个必要条件,就可以最大可能地避免、预防和 解除死锁。
所以,在系统设计、进程调度等方面注意如何不让这四个必要条件成立,如何确 定资源的合理分配算
法,避免进程永久占据系统资源。
此外,也要防止进程在处于等待状态的情况下占用资源。因此,对资源的分配要给予合理的规划。 - ThreadLocal 是什么?有哪些使用场景?
线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共享。Java 提供
ThreadLocal 类来支持线程局部变量,是一种实现线程安全的方式。但是在管理环境下(如 web 服务
器)使用线程局部变量的时候要特别小心,在这种情况下,工作线程的生命周期比任何应用变量的生命周
期都要长。任何线程局部变量一旦在工作完成后没有释放,Java 应用就存在内存泄露的风险。
52.说一下 synchronized 底层实现原理?
synchronized 可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还
可以保证共享变量的内存可见性。
Java 中每一个对象都可以作为锁,这是 synchronized 实现同步的基础:
• 普通同步方法,锁是当前实例对象
• 静态同步方法,锁是当前类的 class 对象
• 同步方法块,锁是括号里面的对象 - synchronized 和 volatile 的区别是什么?
• volatile 本质是在告诉 jvm 当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读
取; synchronized 则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
• volatile 仅能使用在变量级别;synchronized 则可以使用在变量、方法、和类级别的。
• volatile 仅能实现变量的修改可见性,不能保证原子性;而 synchronized 则可以保证变量的修改
可见性和原子性。
• volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。
• volatile 标记的变量不会被编译器优化;synchronized 标记的变量可以被编译器优化。 - synchronized 和 Lock 有什么区别?
• 首先 synchronized 是 java 内置关键字,在 jvm 层面,Lock 是个 java 类;
• synchronized 无法判断是否获取锁的状态,Lock 可以判断是否获取到锁;
• synchronized 会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放
锁),Lock 需在 finally 中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
• 用 synchronized 关键字的两个线程 1 和线程 2,如果当前线程 1 获得锁,线程 2 线程等待。如果
线程 1 阻塞,线程 2 则会一直等待下去,而 Lock 锁就不一定会等待下去,如果尝试获取不到锁,
线程可以不用一直等待就结束了;
• synchronized 的锁可重入、不可中断、非公平,而 Lock 锁可重入、可判断、可公平(两者皆
可);
• Lock 锁适合大量同步的代码的同步问题,synchronized 锁适合代码少量的同步问题。 - synchronized 和 ReentrantLock 区别是什么?
synchronized 是和 if、else、for、while 一样的关键字,ReentrantLock 是类,这是二者的本质区别。
既然 ReentrantLock 是类,那么它就提供了比 synchronized 更多更灵活的特性,可以被继承、可以有
方法、可以有各种各样的类变量,ReentrantLock 比 synchronized 的扩展性体现在几点上:
• ReentrantLock 可以对获取锁的等待时间进行设置,这样就避免了死锁
• ReentrantLock 可以获取各种锁的信息
• ReentrantLock 可以灵活地实现多路通知
另外,二者的锁机制其实也是不一样的:ReentrantLock 底层调用的是 Unsafe 的 park 方法加锁,
synchronized 操作的应该是对象头中 mark word。 - 说一下 atomic 的原理?
Atomic 包中的类基本的特性就是在多线程环境下,当有多个线程同时对单个(包括基本类型及引用类
型)变量进行操作时,具有排他性,即当多个线程同时对该变量的值进行更新时,仅有一个线程能成功,
而未成功的线程可以向自旋锁一样,继续尝试,一直等到执行成功。
Atomic 系列的类中的核心方法都会调用 unsafe 类中的几个本地方法。我们需要先知道一个东西就是
Unsafe 类,全名为:sun.misc.Unsafe,这个类包含了大量的对 C 代码的操作,包括很多直接内存分配
以及原子操作的调用,而它之所以标记为非安全的,是告诉你这个里面大量的方法调用都会存在安全隐
患,需要小心使用,否则会导致严重的后果,例如在通过 unsafe 分配内存的时候,如果自己指定某些区
域可能会导致一些类似 C++一样的指针越界到其他进程的问题。
3.mysql
索引失效情况
1.对于创建的多列索引(复合索引),不是使用的第一部分就不会使用索引
alter table student add index my_index(name, age) // name左边的列, age 右边的列
select * from student where name = ‘aaa’ // 会用到索引
select * from student where age = 18 // 不会使用索引
2.对于使用 like 查询, 查询如果是 ‘%aaa’ 不会使用索引,而 ‘aaa%’ 会使用到索引。
select * from student where name like ‘aaa%’ // 会用到索引
select * from student where name like ‘%aaa’ 或者 ‘_aaa’ // 不会使用索引
3.如果条件中有 or, 有条件没有使用索引,即使其中有条件带索引也不会使用,换言之, 就是要求使用的所有字段,都必须单独使用时能使用索引。
4.如果列类型是字符串,那么一定要在条件中使用引号引用起来,否则不使用索引。
5.如果mysql认为全表扫面要比使用索引快,则不使用索引。
如:表里只有一条数据。
索引是不是越多越好
1.首先数据量小的表不需要建立索引,因为小的表即使建立索引也不会有大的用处,还会增加额外的索引开销
2 不经常引用的列不要建立索引,因为不常用,即使建立了索引也没有多大意义
3 经常频繁更新的列不要建立索引,因为肯定会影响插入或更新的效率
4 索引并不是一劳永逸的,用的时间长了需要进行整理或者重建
mysql优化
4.SSM
Spring通过DI(依赖注入)实现IOC(控制反转),常用的注入方式主要有三种:构造方法注入,setter注入,基于注解的注入。