参考链接
基础知识
创建线程方式
- 继承 Thread ;重写 run 方法;
- 实现 Runnable 接口;重写 run 方法;
- 实现 Callable 接口;重写call()方法
Thread 、Runnable 、Callable 区别
-
Runnable接口:
避免lava单继承特性而带来的局限;增强程序的健壮性,代码能够被多个线程共享,代码与数据是独立的;- 适合多个相同程序代码的线程区处理同一资源的情况。
- start()启动线程后,线程被JVM放到就绪队列中,如果有处理机可用,则执行run方法。
-
Callable接口
- 重写call方法,线程执行完毕后会
有返回值,其他方法无返回值。
- 重写call方法,线程执行完毕后会
sleep()、wait() 区别
| sleep() | wait() |
|---|---|
| Thread类中的静态方法 | Object类中的方法 |
| 可在任何地方调用 | 只能在同步代码块或同步方法中使用(即使用synchronized关键字修饰的) |
不会释放对象锁 ,暂停执行指定时间,到时后会自动恢复 | 会**放弃对象锁,进入等锁池,只有notify / notifyAll**能唤醒。 |
submit()、execute() 区别?
-
接收参数:submit()可以执行 Runnable 和 Callable 类型的任务。execute()只能执行 Runnable 类型的任务。
-
返回值:submit()方法可以返回持有计算结果的 Future 对象,而execute()没有
-
异常处理:submit()方便Exception处理
线程 B 怎么知道线程 A 修改了变量
- volatile 修饰变量
- synchronized 修饰修改变量的方法
- wait/notify
- while 轮询
synchronized、volatile 的区别
| volatile | synchronized |
|---|---|
只能修饰变量 | 可修饰类、方法、代码段 |
防止指令重排 | |
仅能实现变量的修改可见性 | 可见性 + 原子性 |
不会造成线程的阻塞 | 可能会阻塞 |
synchronized 、Lock 的区别
| 区别 | synchronized | Lock |
|---|---|---|
| 存在层面 | 关键字,JVM托管 | 接口 |
| 修饰对象 | 类、方法、代码段 | 代码块 |
| 【获取 / 释放】锁 | 不需要手动获取锁、释放锁,不会造成死锁 | 需要自己加锁和释放锁,如果释放锁就会造成死锁 |
| 锁状态 | 无法获知 | 可获知 |
| 获取锁的方式 | 抢占式 | 自旋获取锁直到获取 |
atomic 原理
主要利用 CAS + volatile + native 方法来保证原子操作,从而避免 synchronized 的高开销
volatile
volatile作用
- 保证可见性
- 禁止指令重排
- 和 CAS 结合,保证了原子性
volatile保证可见性原理
缓存一致性协议:MESI
-
Modify:当缓存行中的数据被修改时,该缓存行置为M状态
-
Exclusive(独占):数据只有一个缓存行使用时,置为E状态
-
Shared:当其他CPU中也读取某数据到缓存行时,所有持有该数据的缓存行置为S状态
-
Invalid(无效):当
某个缓存行数据修改时,其他持有该数据的缓存行置为I状态 -
核心的思想:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。
总线嗅探机制
volatile 数组
- 可以创建 volatile 类型数组,但只是一个
指向数组的引用 - 若多个线程同时改变数组的元素,volatile不起作用
volatile 变量、atomic 变量的区别
volatile 变量可确保先行关系,即写操作会发生在后续的读操作之前,不能保证原子性;atomic 方法可以让这种操作具有原子性
volatile原子操作
- 如果使用volatile修饰
long和double,则读写都是原子操作 对于64位的引用地址的读写,都是原子操作- 在实现JVM时,可以自由选择是否把读写long和double作为原子操作
- 推荐JVM实现为原子操作
synchronized
synchronized 底层实现
- 同步方法通过
ACC_SYNCHRONIZED调用monitorenter / monitorexit实现 - 同步代码块通过
monitorenter / monitorexit / monitorexit实现;第二个monitorexit可防止因为异常退出而未释放锁的情况
synchronized 使用场景
-
非静态方法,锁对象实例(this)public synchronized void method() {} -
静态方法,锁类对象public static synchronized void method() {} -
作用于
Lock.class,锁住的是 Lock 的Class 对象,也是全局只有一个。synchronized (Lock.class) {} -
作用于
this,锁住的是对象实例synchronized (this) {} -
作用于
静态成员变量,锁住的是该静态成员变量对象public static Object monitor = new Object(); synchronized (monitor) {}
synchronize 维护了几个队列?
3个双向链表- 获取锁失败,
_cxq 阻塞链表 _EntryList 链表等待链表_WaitSet
- 获取锁失败,
synchronized 非公平锁体现在哪些地方?
- 当持有锁的线程释放锁时
- 锁的持有者 owner 属性赋值为 null
- 唤醒等待链表中的一个线程(假定继承者)
- 当获取锁失败进入阻塞时,放入链表的顺序,和最终被唤醒的顺序是不一致的
JVM 做了哪些锁优化?
偏向锁、轻量级锁、自旋锁、自适应自旋、锁消除、锁粗化·
synchronized 锁升级的原理
- 在锁对象的对象头里面有一个
threadid 字段, - 在
第一次访问时 threadid 为空,jvm 让其持有偏向锁,并使threadid = 线程 id, 再次进入,先判断threadid == 线程 id ?可以直接使用此对象:升级偏向锁为轻量级锁- 通过
自旋循环一定次数来获取锁 - 执行一定次数之后,如果还没有正常获取到要使用的对象,此时就会把锁从
轻量级升级为重量级锁,此过程就构成了 synchronized 锁的升级。
锁的升级的目的:锁升级是为了减低了锁带来的性能消耗。在 Java 6 之后优化 synchronized 的实现方式,使用了偏向锁升级为轻量级锁再升级到重量级锁的方式,从而减低了锁带来的性能消耗。
Lock体系
ReentrantLock、AQS
CLH:虚拟的双向队列
- 虚拟的双向队列:
不存在队列实例,仅存在结点之间的关联关系 - 将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。
共享变量state
-
通过protected类型的getState,setState,compareAndSetState进行操作
private volatile int state; // volatile修饰保证线程可见性//返回同步状态的当前值 protected final int getState() { return state; } // 设置同步状态的值 protected final void setState(int newState) { state = newState; } //原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值) protected final boolean compareAndSetState(int expect, int update) { return unsafe.compareAndSwapInt(this, stateOffset, expect, update); }
获取锁资源
- 每次
tryAcquire,传+1 - 每次
tryRelease,传-1 - 新增的线程
获取不到锁,则加入CLH队尾,如果前驱为头结点则尝试获取锁,否则获取不到锁之后park进入等待状态 - 占锁任务结束后
unpark唤醒等待队列中最前边的那个未放弃线程
tryAcquire(int)//独占方式。尝试获取资源,成功则返回true,失败则返回false。
tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。
tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回false。
ReadWriteLock:未完成
- 继承
Sync类实现公平锁、非公平锁
并发容器
ConcurrentHashMap:未完成
CopyOnWriteArrayList:未完成
ThreadLocal
ThreadLocal 实现原理
- 为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
ThreadLocal 哪些使用场景?
- 数据库连接
- session 管理等
本文详细解读Java中线程的创建方式(Thread, Runnable, Callable),sleep()与wait()的区别,submit()与execute()的应用,以及线程间通信、volatile与synchronized的对比,涵盖了AQS和ReentrantLock等内容,适合并发编程面试准备。
2247

被折叠的 条评论
为什么被折叠?



