多线程

1. 线程的五种状态

2. Thread 中 run() 与 start() 的区别

3. sleep()和wait()有什么区别

4. Thread的yiele方法有什么作用?

5. synchronized锁的是什么?

6. Java对象头的存储结构

7. 锁的升降级规则

8. 乐观锁和悲观锁

9. CountDownLatch与CyclicBarrier区别

10. CAS?CAS 有什么缺陷,如何解决

11. 介绍下Thredlocal

12. 介绍下volatile

13. synchronized和volatile异同

14. 单例模式的实现都有哪几种

15. ReentrantLock和aqs

16 synchronized 的底层实现

17 synchronized 和 ReentrantLock/lock 的区别

18 解释下锁膨胀、锁粗化、锁消除

19 Monitor数据结构


  1. 创建、就绪、运行、阻塞、等待、等待超时

  2. start是开启了一个线程,并等待cpu时间片分配,分配到之后就开始执行
    run方法是直接执行,但是作为主线程的普通方法执行,而不是新起的线程

  • 类不同:sleep()是Thread下的静态方法,wait()是Object类下的方法
  • 是否释放锁:sleep()不释放锁,wait()释放锁
  • 用处不同:wait()常用于线程间的通信,sleep()常用于暂停执行。
  • 后果不同:wait()用完后,线程不会自动执行,必须调用notify()或notifyAll()方法才能执行,sleep()方法调用后,线程经过过一定时间会自动苏醒,wait(参数)也可以传参数使其苏醒。它们苏醒后还有所区别,因为wait()会释放锁,所以苏醒后没有获取到锁就进入堵塞状态,获取到锁就进入就绪状态,而sleep苏醒后之间进入就绪状态,但是如果cpu不空闲,则进入的是就绪状态的堵塞队列中。
  1. 让出cpu时间片,使当前线程从运行状态进入就绪状态,等待CPU的下次调度。但可能会很快又获取到

  2. 普通同步方法 —————> 锁的是当前实力对象。
    静态同步方法—————> 锁的是当前类的Class对象。
    同步方法快块—————> 锁的是synchonized括号里配置的对象。

  3. 32位JVM的Mark Word 默认存储结构


    image.png

    Mark Word 存储的数据会随着锁标志为的变化而变化。


    image.png

    64位虚拟机下,Mark Word是64bit大小的
    image.png

7.Java SE 1.6 为了提高锁的性能。引入了“偏向锁”和轻量级锁“。

Java SE 1.6 中锁有4种状态。级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。

锁只能升级不能降级

    • CountDownLatch:一个或者多个线程,等待其他多个线程完成某件事情之后才能执行;
  • CyclicBarrier:多个线程互相等待,直到到达同一个同步点,再继续一起执行
image.png
  • 引入版本的概念
  • 设定循环次数
  • 将多个变量封装为对象
  1. ThreadLocal是Jvm的堆内存开辟一小块空间,每个线程独有的空间。从.ThreadLocal的get和set方法中可以看出,每次进行操作之前,先根据线程id取到对应的threadLocal,在进行存取。使用场景是会话管理和数据库连接池

  2. volatile的三个特点保证可见性、不保证原子性、禁止指令重排

  • 保证可见性:主内存和工作空间的数据是一致的,没有volatile之前是加锁后从主内存读到工作空间,解锁前把工作空间的值刷新到主内存
  • 不保证原子性:比如i++操作,解决方法:加锁或者使用原子类
  • 禁止指令重排:指令重排是编译器和执行器对我们写的代码进行优化,改了代码的执行顺序,volatile禁止了这种优化,防止乱序执行带来的线程安全问题
  • volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;
  • synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
  • volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的
  • volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性
  • volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
  • volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化
  1. 懒汉式、饿汉式、双重检查、匿名静态类、枚举

  2. ReentrantLock是重入锁,也是公平锁和非公平锁(取决于构造参数的bool值),内部有个aqs()对象,有两个属性分别记录是否加锁,以及加锁的线程是哪个;
    公平与非公平原理是构造的对象不一样

  3. synchronized 修饰代码块时,编译后会生成 monitorenter 和 monitorexit 指令,分别对应进入同步块和退出同步块。可以看到有两个 monitorexit,这是因为编译时 JVM 为代码块添加了隐式的 try-finally,在 finally 中进行了锁释放,这也是为什么 synchronized 不需要手动释放锁的原因。

synchronized 修饰方法时,编译后会生成 ACC_SYNCHRONIZED 标记,当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了则会先尝试获得锁。

两种实现其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。

17.1)底层实现:synchronized 是 Java 中的关键字,是 JVM 层面的锁;ReentrantLock 是 JDK 层次的锁实现。

2)是否需要手动释放:synchronized 不需要手动获取锁和释放锁,在发生异常时,会自动释放锁,因此不会导致死锁现象发生;ReentrantLock 在发生异常时,如果没有主动通过 unLock() 去释放锁,很可能会造成死锁现象,因此使用 ReentrantLock 时需要在 finally 块中释放锁。

3)锁的公平性:synchronized 是非公平锁;ReentrantLock 默认是非公平锁,但是可以通过参数选择公平锁。

4)是否可中断:synchronized 是不可被中断的;ReentrantLock 则可以被中断。

5)灵活性:使用 synchronized 时,等待的线程会一直等待下去,直到获取到锁;ReentrantLock 的使用更加灵活,有立即返回是否成功的,有响应中断、有超时时间等。

6)性能上:随着近些年 synchronized 的不断优化,ReentrantLock 和 synchronized 在性能上已经没有很明显的差距了,所以性能不应该成为我们选择两者的主要原因。官方推荐尽量使用 synchronized,除非 synchronized 无法满足需求时,则可以使用 Lock。

  • 锁粗化:多个将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁。
  • 锁膨胀就是锁升级,锁逐渐从无锁到偏向锁、轻量锁和重量锁
  • 锁消除,是jvm优化的一种,如果代码加了锁但是jvm发现并没有多线程竞争,jvm会将该锁消除
  • 锁自旋,不断循环尝试获取锁
  1. image.png

_owner: 初始时为NULL。当有线程占有该monitor时,owner标记为该线程的唯一标识。当线程释放monitor时,owner又恢复为NULL。owner是一个临界资源,JVM是通过CAS操作来保证其线程安全。
_cxq: 竞争队列,所有请求锁的线程首先会被放在这个队列中(单向列表)。cxq是一个临界资源,JVM通过CAS原子指令来修改cxq队列。修改前cxq的旧值填入了node的next字段,cxq指向新值(新线程)。因此cxq是一个后进先出的stack(栈)。
_EntryList:cxq队列中有资格成为候选资源的线程会被移动到该队列中。
_WaitSet:因为调用wait方法而被阻塞的线程会被放在该队列中。

你可能感兴趣的:(多线程)