1.锁
synchronized:它与lock 都是可重入锁
synchronized与ReentrantLock :
- ReentrantLock可以完成synchronized所实现的所有功能:synchronized是Java中的一个关键字,ReentrantLock是一个类,主要不同点:synchronized会自动释放锁,而ReentrantLock一定要手动释放,并且最好在finally块中释放;synchronized的锁状态无法判断,而ReentrantLock的锁状态可以通过tryLock()方法进行判断。
悲观锁与乐观锁
java中验证是否有死锁
- 1.jps 工具,类似linux ps -ef,能查看到当前正在运行的进程相关内容;
- 2.jstack : jvm 自带的堆栈跟踪工具;
synchronized 与CAS 的区别?
-
synchronized在JDK1.2之前的实现均为重量级锁->(JVM所管理的线程和锁都交给操作系统来管理。)
-
轻量级锁:不需要OS参与,JVM内部空间自己来管理的锁。AtomicInteger类,轻量级锁/自旋锁/无锁。
-
CAS:(compareAndSwap)乐观锁,比较数据是否是期望的值(没有改动之前的那个值),不是的话,则取出现数,重复CAS过程(死循环while/自旋)。
如何解决CAS中的ABA问题?
- 给数据(对象)加版本号,version/时间戳。
CAS本身是否具备原子性?
- 具备原子性。
- 底层是一句汇编语言保证了原子性,多核cpu的情况需要lock,锁了总线。
- 轻量级锁不一定比重量级锁效率更高。
双重校验锁实现对象单例(线程安全)
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
//先判断对象是否已经实例过,没有实例化过才进入加锁代码
if (uniqueInstance == null) {
//类对象加锁
synchronized(Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
Reentrantlock & AQS
synchronized锁的底层实现
JKD1.6优化之后synchronized锁的分类
- 偏向锁适合一个线程对一个锁的多次获取的情况; 轻量级锁适合锁执行体比较简单(即减少锁粒度或时间), 自旋一会儿就可以成功获取锁的情况
- 这种策略是为了提高获得锁和释放锁的效率。
2.创建线程的多种方式
3.线程池
- 下面三种线程池的创建方式:
- 一池多线程(指定int)数目;
- 一池一线程;
- 自适应线程数目,(比如10个任务时候,自动创建5个线程;20个任务时候,自动创建13个线程。)
- 三者的实现,均在底层new 了ThreadPoolExecutor对象。
线程池的七个参数
线程池的执行流程
- 执行execute()方法时才会新建线程;
- 具体过程见下图所示1,2,...,9;
线程池的拒绝策略
实际上,不会使用上述方式建立线程池,而是自定义线程池:
- 原因:
- 自定义线程池实例:
4.产生死锁的条件?
- 互斥使用(资源独占):一个资源每次只能给一个进程使用;
- 占有且等待(请求和保持,部分分配):**进程在申请新的资源的同时保有对原有资源的占有; **
- 不可抢占(不可剥夺):资源申请者不能强行从资源占有者手中夺取资源,资源只能由占有者资源释放。
- 循环等待:存在一个进程等待队列{P1, P2, ..., Pn},其中P1等待P2占有的资源,P2等待P3占有的资源,..., Pn等待P1占有的资源,形成一个进程等待循环。
- 当死锁产生的时候一定会有这四个条件,有一个条件不成立都不会造成死锁。
追问1:如何避免死锁的发生?
- 产生死锁的条件有;互斥条件、不可剥夺条件、持有并等待条件、环路等待条件。
- 那么避免死锁问题只需要破坏其中一个条件即可,最常用的并且可行的就是使用资源有序分配法,来破坏环路等待条件。
5.ThreadLocal
- 使每个线程有自己独立的数据备份,这份数据不可以被别的线程访问到。
- 弱引用解决ThreadLocal的OOM问题:
- 补充:强引用:堆区内存不足时候,宁愿报错也不会回收;
- 软引用:堆区内存不足时候,才会回收软引用;
- 弱引用:被GC发现即刻回收;
- 虚引用:基本上等于无引用。
6.sleep()和wait()的区别
- sleep()是Thread类的静态方法;wait()是Object类的成员本地方法;
- sleep()可以在任何地方使用;wait()方法则只能在同步方法或者同步代码块中使用(用于线程间通信),否则抛出异常Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
- sleep() 会休眠当前线程指定时间,释放 CPU 资源,不释放对象锁,休眠时间到自动苏醒继续执行;wait() 方法放弃持有的对象锁,进入等待队列,当该对象被调用 notify() / notifyAll() 方法后才有机会竞争获取对象锁,进入运行状态
- JDK1.8 sleep() wait() 均需要捕获 InterruptedException 异常
- 回到yield ()方法上来,与wait ()和sleep ()方法有一些区别,它仅仅释放线程所占有的CPU资源,从而让其他线程有机会运行,但是并不能保证某个特定的线程能够获得CPU资源。. 谁能获得CPU完全取决于调度器,在有些情况下调用yield方法的线程甚至会再次得到CPU资源。. 所以,依赖于yield方法是不可靠的,它只能尽力而为。
7.为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?
- new 一个 Thread,线程进入了新建状态。调用 start()方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。 但是,直接执行 run() 方法,会把 run() 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。