日常开发中我们常常需要使用到多线程来充分利用cpu实现高效执行,这其中需要我们多线程的执行和线程安全有充分的了解,本文主要围绕相关问题展开。
线程的状态集
Java线程有五种状态:
- 创建(new): 当刚创建线程对象并没有启动线程时
- 等待(waitting):此时线程处于对象的等待队列中,线程释放CPU,等待被唤醒,此时线程不会被分配CPU,常见于Object.wait(), Thread.join()方法调用后。
- 阻塞(blocked):此时线程因为没有获得对象同步锁而进入同步队列中,等待其他线程释放对象锁。常常是当一个线程从等待队列中被唤醒后,由于由于没有获得锁而进入阻塞状态。
- 运行(running):线程在此状态下开始运行或者等待CPU执行
- 结束(terminated): 线程结束
线程安全
线程安全是指多线程访问时,保证操作的可见性、有序性、原子性。之所以有会存在不安全的情况,是因为线程的工作模型是基于工作内存+主内存结构。
上述主内存可以大致理解为堆内存/磁盘,工作内存可以大致理解为虚拟机栈相关内存或者寄存器/高速缓存,Java线程执行时,先从主内存将共享数据拷贝到工作内存(线程私有),然后执行相关指令。那么各线程在工作内存操作数据时就会出现和主内存不一致的情况,导致出现奇怪的结果。
那么如何实现线程安全呢?
volatile变量
volatile变量有以下两条语义:
- 保证此变量对所有变量可见,即其他线程修改变量之后,当前线程可以立即得知。但是其并不能保证操作的原子性,即如果操作不是原子性的,那么仍然不是线程安全的。
- 插入同步栅栏,保证栅栏前后的指令在执行优化时不会重排序。
synchronized
synchronized基于互斥同步来保证可见性、原子性、有序性。即通过在字节码中插入moniterenter和moniterexit指令来使得只有获得了对象锁的线程才能执行当前代码块,而其他线程则必须等待当前锁释放后才能执行。
ReentrantLock
ReentrantLock是java并发包提供的线程安全工具类,其相比synchronized有以下优势
- 其等待可以被中断
- 公平锁:当多个线程在等待同一个锁时,必须按照申请时间顺序来依次获得锁
- 能在一个ReentrantLock对象上绑定多个Condition对象,能和多个条件关联。、
ReentrantLock其原理是AQS+CAS实现,CAS采用硬件保证操作的原子性,即先比较地址上变量的值是否满足预期,满足预期再将其更改为新的值。而在ReentrantLock中满足预期则表示当前线程拿到了锁,如果不满足,则将对象加入AQS队列,当其他线程释放释放后,再从AQS队列中取出等待对象,再次执行CAS。
线程模型
Java虚拟机屏蔽了各操作系统及其硬件层的差异,使得一套代码可以无缝在多个平台上运行,那么对于Java中的Thread与操作系统的线程的映射关系是如何的呢?
我们常规使用的Sun JDK的Window和Linux版本中都是使用一对一的线程模型来实现Java线程的,即一个Java Thread映射到一条轻量级进程中。
常见线程面试问题
-
谈谈你对AQS的理解lock和synchronized 区别
-
线程池如何知道一个线程的任务已经执行完成什么叫做阻塞队列的有界和无界
-
lock和Synchronized 区别
-
讲一下wait和notify这个为什么要在synchronized 代码块中?你是怎么理解线程安全问题的?
-
什么是守护线程,它有什么特点谈谈你对AQS的理解
-
AbstractQueuedSynchronized为什么采用双向链 表lock和synchronized 区别
-
线程池如何知道一个线程的任务已经执行完成什么
-
ConcurrentHashMap底层具体实现知道吗?实现原 理是什么?能谈一下CAS机制吗?
-
死锁的发生原因和怎么避免
-
volatile关键字有什么用?它的实现原理是什么?
-
请说一下ReentrantLock的实现原理?
-
基于数组的阻塞队列ArrayBlockingQueue原理怎么 理解线程安全?