线程状态
- NEW(新建) 编程语言创建线程,还未被操作系统创建
- RUNNABLE(可运行) 线程已被操作系统创建,但还未被分配CPU运行
- RUNNING(运行) 线程执行
- BLOCKED/WATTING/TIMED_WATTING(阻塞) 线程释放CPU执行权
- DEAD(死亡) 线程结束,不可复生
happens-before
happens-before的概念指的是两个操作之前的执行顺序
假设A happens-before B,那么A的操作对B是可见的,也可以理解为A一定比B先执行,比如说当A修改了一个变量以后,B立马就能看到修改后的值
synchornized关键字
基于操作系统Mutex Lock实现。
synchornized关键字修饰的代码块为同步代码块,多个线程访问只有一个线程能获得锁,其余线程会阻塞等待释放锁后再去获取锁
获取锁释放锁会导致用户态和内核态的切换,这是操作系统级别的切换,非常影响性能
synchornized优化
JDK1.6之后引入偏向锁
轻量级锁
重量级锁
的概念
多个线程访问同步代码块,当一个线程第一个获取到锁,这个时候获得偏向锁
,当另一个线程试图获取锁发现已经被别的线程获取,锁升级为轻量级锁
,这个线程开始自旋(死循环试图获取锁),自旋重试次数超过之后还未获取到锁,则升级为重量级锁
,此时会阻塞。
偏向锁乐观认为没有竞争,偏向于第一个获取到他的锁,并且不会主动释放,当别的线程试图获取锁的时候才会去检查该线程是否存活,如果存活则升级为轻量级锁,否则释放
轻量级锁认为存在竞争,并且通过自旋去试图获取锁,如果次数超过了以后还未获取到锁,锁升级为重量级锁,未获取到锁的线程则阻塞
锁获取流程:java对象头包含mark word和class metadata address,当线程获取到锁,mark word会存储这个线程ID,另一个线程试图获取锁的时候通过CAS操作替换存储的线程ID,如果前一个线程已经退出,则CAS成功,否则失败,重复这个操作
性能优化:
- -XX:-UseSpinning 关闭自旋
- -XX:PreBlockSpin 修改自旋次数(JDK1.7以后不支持,由JVM控制)
- -XX:-UseBiasedLocking 关闭偏向锁
- -XX:+UseHeavyMonitors 设置重量级锁
volatile关键字
volatile关键字修饰的变量,线程在修改的时候会强制同步到主内存,线程在读的时候会强制从主内存读取,保证线程能获取到最新的值
volatile实现原理
在生成汇编(javap -v xxx.class)代码时会在volatile修饰的共享变量进行写操作的时候会多出Lock前缀的指令,Lock指令有如下的作用:
- Lock前缀的指令会引起处理器缓存写回内存;
- 一个处理器的缓存回写到内存会导致其他处理器的缓存失效;
- 当处理器发现本地缓存失效后,就会从内存中重读该变量数据,即可以获取当前最新值
volatile不能保证原子性
对于一个i++操作,如果两个线程同时执行,这里面大致是这三个步骤:
A: 读取i -> 执行i+1 -> 将结果赋值给i
B: 读取i -> 执行i+1 -> 将结果赋值给i
首先A线程从主内存中读取i的值到工作内存中,然后在工作内存中计算i+1的结果,最后赋值给i并写入主内存,如果在计算i+1但还未来得及将值赋值给i的时候B线程去做i++的操作,这时候B读取到i的值是未修改的值,最终结果i的值任然是i+1而不是预期的i+2。
Lock
ReentrantLock
ReentrantLock为可重入锁,其实现锁的逻辑由内部类Sync实现,Sync类实现了AQS(AbstractQueuedSynchronizer),派生两个类分别是FairSync(公平锁)和NonfairSync(非公平锁),ReentrantLock构造方法默认为非公平锁。
公平锁与非公平锁的区别:若是公平锁,处于等待队列的线程会按顺序一个一个运行,而非公平锁则刚运行完的线程可能会继续运行
加锁流程:
非公平锁
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
公平锁
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
- 非公平锁在加锁时首先尝试CAS替换state,如果cas成功,则调用AbstractOwnableSynchronizer的setExclusiveOwnerThread方法获取锁,该方法将当前线程设置为独占模式(这里就是非公平锁的体现,如果线程CAS成功就获取锁)
- 若CAS失败,调用AQS的acquire()方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
- acquire()方法中先调用tryAcquire方法,最终调用Sync的nonfairTryAcquire方法
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
/**
* Performs {@link Lock#lock}. The main reason for subclassing
* is to allow fast path for nonfair version.
*/
abstract void lock();
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
...
}
- nonfairTryAcquire第一步判断当前线程state是否为0,若是则CAS替换state,并获取锁;若不是0并且当前线程是exclusiveOwnerThread则将state+1,这里没有加锁是该线程已经获取锁,此时不需要再加锁,这也就是可重入的体现
- 若获取锁失败,将该线程加入等待队列
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
- addWaiter方法仅仅是向队列中追加一个节点,若队列为空则新建一个空节点为头节点,再将当前节点放在头节点后面
- acquireQueued比较重要,线程未获取到锁的挂起操作是在这个方法中实现的
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
* 首先获取当前节点的前驱节点,若前驱节点是头节点,那么直接尝试获取锁,如果获取成功则将当前节点设为头节点,这里说明头节点就是占有锁的节点
* 获取失败执行shouldParkAfterFailedAcquire方法,来看看该方法的逻辑
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
* 这个方法用来判断节点是否应该被挂起,首先获取当前节点的前驱节点,若前驱节点的waitState为Node.SIGNAL(-1),则返回true,该状态表示后继结点正在等待当前节点唤醒,因此返回true后当前节点被挂起等待唤醒。
* 若前驱节点>0,循环查找最近的一个waitState<=0的前驱节点(waitState>0的只有1,表示节点已取消),并把当前节点设置到该节点之后
* 若waitState=0(初始值)或者<-1,则将前驱节点的值更新为Node.SIGNAL,下一次循环进来时就可让当前节点挂起
* 最后方法返回了true就走到parkAndCheckInterrupt方法,该方法调用LockSupport.park方法将线程挂起
我们再来看公平锁
- 公平锁加锁直接调用acquire方法,这与非公平锁CAS失败后的逻辑一致
- 公平锁的acquire方法会调用公平锁的tryAcquire方法,这里与非公平锁不同的是,非公平锁在判断了当前state是0后直接CAS操作获取锁,而公平锁在CAS之前会调用hasQueuedPredecessors方法判断等待队列中是否有线程正在排队的情况,如果没有才会获取锁
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
- 这样可以保证遵循FIFO的原则,每一个先来的线程都可以最先获取到锁,但是增加了上下文切换与等待线程的状态变换时间。所以效率相较于非公平锁较慢
之后的逻辑与非公平锁一致
解锁流程:
- ReentrantLock解锁需要手动调用unlock方法
/**
* Attempts to release this lock.
*
* <p>If the current thread is the holder of this lock then the hold
* count is decremented. If the hold count is now zero then the lock
* is released. If the current thread is not the holder of this
* lock then {@link IllegalMonitorStateException} is thrown.
*
* @throws IllegalMonitorStateException if the current thread does not
* hold this lock
*/
public void unlock() {
sync.release(1);
}
- unlock会调用AQS的release方法,我们来看看release方法
/**
* Releases in exclusive mode. Implemented by unblocking one or
* more threads if {@link #tryRelease} returns true.
* This method can be used to implement method {@link Lock#unlock}.
*
* @param arg the release argument. This value is conveyed to
* {@link #tryRelease} but is otherwise uninterpreted and
* can represent anything you like.
* @return the value returned from {@link #tryRelease}
*/
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
- 这里首先执行tryRelease方法,该方法具体内容由Sync类实现
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
- 我们可以看到,tryRelease中实现比较简单,只是判断一下当前state-1是否为0,如果为0的话调用setExclusiveOwnerThread(null)释放线程,并设置线程状态为0
- 如果当前state-1不为0的话返回false,表示解锁失败
- 当解锁成功之后,调用unparkSuccessor唤醒后面的节点,该方法原理就是遍历后面的节点,唤醒wateState为0或者-1的节点
ReentrantLock状态总结
state:初始为0,获取到锁+1,之后每重入一次都会执行+1的操作;释放锁(调用unlock)会-1,当state为0的时候真正释放资源
waitState:
- 初始为0,抢锁失败之后前驱节点的waitState会被设置为-1,该状态的意思是后面的节点被挂起需要释放
- CANCELLED(1): 当前节点已取消
- SIGNAL(-1):后继节点等待唤醒,后继结点入队时,会将前继结点的状态更新为SIGNAL。
- CONDITION(-2):表示结点等待在Condition上,当其他线程调用了Condition的signal()方法后,CONDITION状态的结点将从等待队列转移到同步队列中,等待获取同步锁。
- PROPAGATE(-3):共享模式下,前继结点不仅会唤醒其后继结点,同时也可能会唤醒后继的后继结点。
ReentrantReadWriteLock
RRWL允许多个线程同时读取共享变量,只允许一个线程写共享变量,当有线程在写变量的时候又不允许线程读取共享变量。
RRWL的state高16位记录了读锁的数量,低16为记录了写锁的数量
RRWL写锁加锁流程:
- 首先判断当前state是否为0,如果为0,就判断线程是否应该阻塞,这里走的是公平锁和非公平锁的逻辑,不阻塞的话就获取锁
- 若state不为0,说明有线程获取锁,首先获取state的低16位w(即写锁数量),如果w=0(说明此刻有读锁)或者当前线程未获取锁,则加锁失败,否则说明当前线程已经占有锁,cas替换state
RRWL读锁加锁流程: - 首先获取写锁数量w,若w>0并且获取写锁的线程不是当前线程,则加锁失败,如果当前线程持有写锁,则可以获取读锁(支持锁降级)
- 获取读锁的数量r,如果r不被阻塞,则CAS替换state,否则进入阻塞队列等待
ThreadLocal
在多线程环境中常常用到ThreadLocal,ThreadLocal中有个内部类ThreadLocalMap,内部有一个entry数组,其key为ThreadLocal本身,value为任意object
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
Thread存储的数据在Thread.threadLocals上,其类型正好是ThreadLocalMap
public class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
}
也就是说,每个线程都存储了ThreadLocalMap<ThreadLocal, Object>这样的数据,当我们在使用ThrealLocal.get()方法时,都会获取到当前线程的threadLocals,根据threadLocal为key获取值。
- ThreadLocal与Thread的设计不容易产生内存泄漏,在多线程环境中,ThreadLocal的存活时间一般都比较长,如果threadLocals是放在ThreadLocal中,那么threadLocal就会一直持有thread的引用,导致线程不会被回收
- 线程池中使用ThreadLocal,容易导致内存泄漏,由于线程池中的线程存活时间特别长,因此threadLocalMap一直不会被回收,虽然ThreadLocalMap对ThreadLocal是弱引用(Entry extends WeakReference<ThreadLocal<?>>),但对于value是强引用,所以value不会被清除,可以手动调用threadLocal.remove()方法清除value
ThreadPoolExecutor
在需要并发操作的场景下,频繁的创建销毁线程不仅占用内存,还占用cpu资源,影响系统性能,利用线程池可以很好的解决这个问题,ThreadPoolExecutor为jdk1.5提供的线程池执行器。
构造方法:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue);
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory);
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler);
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
参数:
- corePoolSize 线程池保留线程数
- maximumPoolSize 最大线程数
- keepAliveTime 空闲线程等待最长时间,超时销毁
- unit 等待时间的时间单位
- workQueue 线程等待队列
- threadFactory 创建线程的工厂
- handler 线程池拒绝线程任务时的处理,默认抛异常
- 核心线程数corePoolSize的值是多少,那么线程池最少就有多少个线程,即使线程都是空闲的。
- 当线程数量超过corePoolSize时,剩下的线程如果没有超过workQueue的容量,则剩下的线程会进入队列中等待核心线程执行完。
- 当线程数量超过corePoolSize与workQueue容量的总和,但剩下的部分没有超过maximumPoolSize时,会开启新的线程来执行剩下的线程。
- 当线程数量超过前三者之和时,剩余的线程会走RejectedExecutionHandler 的rejectedExecution方法,如果没有配置的话,这些线程会被拒绝执行,并抛出异常。
- 如果设置了allowCoreThreadTimeOut为true的话,即便空闲线程数量低于核心线程数也会在超时的时候销毁。
ThreadPoolExecutor执行线程有两种方式,一种是threadPoolExecutor.submit(),一种是threadPoolExecutor.execute()
- submit
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
submit执行的是Runnable类型或者Callable类型的任务,并且返回Future对象
- execute
void execute(Runnable command);
execute方法直接执行Runnable类型的任务
submit与execute的区别
- submit有返回值,execute没有
- submit任务执行失败后不会抛异常,但在调用get()方法后会抛出异常,而execute会立即抛出来
关闭线程池
ThreadPoolExecutor提供了两个关闭线程池的方法
/**
* 启动有序关闭,在该关闭中执行先前提交的任务,但不接受任何新任务。如果已经关闭,则调用不会产生任何其他影响。
*
* <p>此方法不等待先前提交的任务完成执行。使用{@link #awaitTermination awaitTermination}可以做到这一点。
*
* <p>除了尽力而为后,无法保证停止处理主动执行的任务。此实现通过{@link Thread#interrupt}取消任务,因此任何未能响应中断的任务都可能永远不会终止。
*
* @throws SecurityException(如果存在安全管理器并关闭此ExecutorService)可能会操纵不允许调用方修改的线程,
* 因为该调用方不包含{@link java.lang.RuntimePermission}{@code(“ modifyThread”)},
* 或安全管理器的{@code checkAccess}方法拒绝访问。
*/
void shutdown();
/**
* 尝试停止所有正在执行的任务,暂停正在等待的任务的处理,并返回正在等待执行的任务的列表。
*
* <p>此方法不等待主动执行的任务终止。使用{@link #awaitTermination awaitTermination}可以做到这一点。
*
* <p>除了尽力而为后,无法保证停止处理主动执行的任务。例如,典型的实现将通过{@link Thread#interrupt}取消,因此任何无法响应中断的任务都可能永远不会终止。
*
* @return 从未开始执行的任务列表
* @thrwos 如果存在安全管理器并关闭此ExecutorService
* 则抛出SecurityException
* 这可能会操纵不允许调用者修改的线程
* 因为该调用者不持有{@link java.lang.RuntimePermission}
* {@code (“ modifyThread”)}
* 或安全管理器的{@code checkAccess}方法拒绝访问。
*/
List<Runnable> shutdownNow();
- shutdown方法不接受新的任务,等待之前提交的任务执行完后关闭
- shutdownNow方法会停止正在执行的任务,并将已提交但未执行的任务返回