1.锁

synchronized:它与lock 都是可重入锁

alt

synchronized与ReentrantLock :

  • ReentrantLock可以完成synchronized所实现的所有功能:synchronized是Java中的一个关键字,ReentrantLock是一个类,主要不同点:synchronized会自动释放锁,而ReentrantLock一定要手动释放,并且最好在finally块中释放;synchronized的锁状态无法判断,而ReentrantLock的锁状态可以通过tryLock()方法进行判断。 alt

悲观锁与乐观锁

alt

alt

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,锁了总线
  • 轻量级锁不一定比重量级锁效率更高alt

双重校验锁实现对象单例(线程安全)

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;
    }
}

alt

Reentrantlock & AQS

alt

synchronized锁的底层实现

alt

JKD1.6优化之后synchronized锁的分类

alt

  • 偏向锁适合一个线程对一个锁的多次获取的情况; 轻量级锁适合锁执行体比较简单(即减少锁粒度或时间), 自旋一会儿就可以成功获取锁的情况
  • 这种策略是为了提高获得锁和释放锁的效率

alt

2.创建线程的多种方式

alt

3.线程池

  • 下面三种线程池的创建方式
    1. 一池多线程(指定int)数目
    2. 一池一线程
    3. 自适应线程数目,(比如10个任务时候,自动创建5个线程;20个任务时候,自动创建13个线程。)
    • 三者的实现,均在底层new 了ThreadPoolExecutor对象。

alt

线程池的七个参数

  • alt

线程池的执行流程

  • 执行execute()方法时才会新建线程
  • 具体过程见下图所示1,2,...,9;

alt

线程池的拒绝策略

alt

实际上,不会使用上述方式建立线程池,而是自定义线程池:

  • 原因: alt
  • 自定义线程池实例: alt

4.产生死锁的条件?

  1. 互斥使用(资源独占):一个资源每次只能给一个进程使用
  2. 占有且等待(请求和保持,部分分配):**进程在申请新的资源的同时保有对原有资源的占有; **
  3. 不可抢占(不可剥夺):资源申请者不能强行从资源占有者手中夺取资源,资源只能由占有者资源释放。
  4. 循环等待:存在一个进程等待队列{P1, P2, ..., Pn},其中P1等待P2占有的资源,P2等待P3占有的资源,..., Pn等待P1占有的资源,形成一个进程等待循环
  • 当死锁产生的时候一定会有这四个条件,有一个条件不成立都不会造成死锁。

追问1:如何避免死锁的发生?

  • 产生死锁的条件有;互斥条件、不可剥夺条件、持有并等待条件、环路等待条件
  • 那么避免死锁问题只需要破坏其中一个条件即可,最常用的并且可行的就是使用资源有序分配法,来破坏环路等待条件

5.ThreadLocal

  • 使每个线程有自己独立的数据备份,这份数据不可以被别的线程访问到。
  • 弱引用解决ThreadLocal的OOM问题:
    • 补充:强引用:堆区内存不足时候,宁愿报错也不会回收;
    • 软引用:堆区内存不足时候,才会回收软引用;
    • 弱引用:被GC发现即刻回收;
    • 虚引用:基本上等于无引用。 alt

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 线程下的普通方法去执行并不会在某个线程中执行它,所以这并不是多线程工作。