基本概念
只有操作系统的内核才能提供创建线程的能力,java的线程也就是调用了内核提供的api接口 clone.
java调用内核的方式称为 系统调用
线程的创建和使用
创建线程的方式一:继承Thread类,重写run
方法,调用start
方法。
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("自定义方法"+Thread.currentThread().getName());
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}
start 方法只能调用一次!
public class MyThread2 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"----"+i);
}
}
public static void main(String[] args) throws InterruptedException {
MyThread2 myThread2 = new MyThread2();
myThread2.setName("子线程");
myThread2.start();
for (int i = 0; i < 100; i++) {
System.out.println("主线程"+i);
if (i == 44){
<!-- 暂停当前线程,等待mythread2 线程执行结束再执行主线程 -->
myThread2.join();
}
}
}
}
- stop() 强制结束当前线程,已经过时了
- sleep() cpu在指定时间内不会执行,该线程也
不会释放资源
- isAlive() 判断当前线程是否还存在
创建线程的方式二:实现runnable 接口,放入Thread的构造方法中
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
public class MyThread {
public static void main(String[] args) {
new Thread(new MyRunnable()).start();
for (int i = 0; i < 100; i++) {
System.out.println("主线程"+i);
}
}
}
创建线程的方式三: 实现callable 接口,放入 FutureTask中,然后再放入Thread类中
class MyCallable implements Callable<String>{
@Override
public String call() throws Exception {
for (int i = 0; i < 10; i++) {
Thread.sleep(1000);
System.out.println("执行了一次 "+Thread.currentThread().getName());
}
return "ok";
}
}
public class FutureTaskTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> task = new FutureTask<String>(new MyCallable());
Thread thread = new Thread(task);
thread.start();
String s = task.get();
System.out.println(s);
}
}
线程的状态
- 新建状态(new) 当新建了一个 Thread 类之后,该线程就处于新建状态
- 就绪状态(runnable) 调用了Thread的start方法之后,线程变为就绪状态,等待cpu执行
- 阻塞状态(Blocking) 线程等待进入synchronized 关键字方法或者代码块时的状态
- 等待(waiting) 调用了wait() 或者 join() 方法之后,当前线程就处于等待状态,等待其他线程唤醒
- 计时等待(time waiting) 调用了 wait(time) join(time) Thread.sleep 方法之后,不会永远等待下去,时间到了会自动切换为runnable状态
- 终止(Terminated) 执行结束只有就变成终止状态
Synchronized
synchronized 被字节码编译后通过 monitorenter 和 monitorexit 指令来标记锁定的代码块,线程通过访问 锁定对象的markword 的两位来判断锁定信息
锁升级:
markword 记录这个线程的ID (偏向锁) 当这个线程再次进入的时候直接判断,就重入了
如果有线程争抢: 升级为自旋锁
10次以后,升级为重量级锁。 线程就变成Block状态
。 如果是 ReentrantLock 则是 被 LockSupport.park 变成 waiting状态
volatile
- 保证线程的可见性
线程空间操作数据的时候会将主线程的数据拷贝到线程空间,然后最后在刷回主线程,被标记了volatile的对象,线程在操作的时候就是直接操作主线程的数据。 - 禁止指令重排
LockSupport
阻塞和唤醒线程的类,可以指定唤醒某个线程
- LockSupport.park(); 阻塞当前线程
- LockSupport.unpark(thread); 指定解锁某个线程
- thread.interrupt(); 中断后也会解锁线程
isInterrupted = true
Thread thread = new Thread(() -> {
System.out.println("睡眠了");
LockSupport.park();
System.out.println("醒了");
});
thread.start();
thread.interrupt();
LockSupport.unpark(thread);
AQS 相关
JUC并发包里面最重要的内容就是 AbstractQueuedSynchronizer
类,其他相关类都是基于 AQS得来的,我们先来看看其中是怎么实现的。
以及推导出其他类。
概念
AQS使用一个volatile的int类型成员变量来表示同步状态, 通过内置的 FIFO 队列来完成资源获取的排队工作,将每条要去抢占资源的线程
封装成一个 Node节点来实现锁的分配, 通过CAS完成state的修改。
AQS本身有一个state,表示锁的状态。Node里面也有一个 waitStatus 表示线程的状态。
AQS 的本质就是控制多个线程并发访问一个资源的时候,对于那些没有抢到资源的线程是如何维护的一个思想。
AQS分为独占和共享两种不同方式,公平和非公平很简单,公平就是队列里有线程的时候,新来的线程直接往队列里插。非公平就是就算队列里有线程,新来的还是会尝试去抢一下。重点是看独占和非独占。
AQS独占非独占思想
AQS中有一个资源的概念,就是成员变量state
, 独占就是只能有一个
线程操作资源,而非独占就是可以同时有N个
线程操作资源(这部分逻辑是在子类的实现中完成的,例如Semaphore的判断逻辑是资源如果有剩余就返回成功,而RenentryLock则是如果资源是1了就返回失败)
。 AQS抽象了几组方法来让子类自定义对资源操作成功的判断
,如果获取资源失败的管理线程由AQS封装逻辑。
并且独占会将 AQS的 exclusiveOwnerThread
设为当前线程
子类实现的
- tryAcquire 独占方式占有资源
- tryRelease 独占方式释放资源
- tryAcquireShared 共享方式占有资源
- tryReleaseShared 共享方式释放资源
对应的,在AQS自身也有对应的方法来处理独占和非独占的逻辑
AQS中对应的逻辑
- acquire 独占方式占有资源, 当子类的
tryAcquire
获取失败的时候,就调用addWaiter
创建节点,然后acquireQueued
入队列
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
- release 独占方式释放资源, 当子类
tryRelease
释放资源成功的时候,找到从头开始unparkSuccessor
唤醒下一个节点 (还有判断逻辑)
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
- acquireShared 共享方式占有资源 . 当子类
tryAcquireShared
失败了(小于0表示失败), 就调用doAcquireSharedInterruptibly
创建节点加入队列
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
- releaseShared 共享方式释放资源, 当子类尝试释放资源成功的时候,调用
doReleaseShared
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
源码阅读
独占方式
- tryAcquire: 尝试获取锁,tryRelease 尝试释放锁
由子类实现
- addWaiter 将当前线程包装成一个 Node并插入队列中,
如果队列中没有数据,则头部先创建一个哨兵节点
private Node addWaiter(Node mode) {
# 创建Node
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
# 插入队列尾部
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
# 要么CAS插入队列尾部失败,要么没有尾部指针
enq(node);
return node;
}
- enq: 初始化队列或者自旋插入尾部
注意,这个方法是 unparkSuccessor 尾部唤醒的逻辑点
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
// 假设线程1执行到这里被挂起,此时 next 指针还没有关联到,后新来的线程 n 可能已经被排列到后面去了,
// 所以当 t 被需要时,它的 next 指针还没有设置或者重置;故需要从后到前寻找,而如果找寻下一个,而这个可能被遗漏了;
t.next = node;
return t;
}
}
}
}
- acquireQueued 在队列中的Node尝试获取锁,获取不到则陷入
SLEEP
状态
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
# 自旋,只有获取到锁才能出来
# tryAcquire 尝试获取锁
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 判断是不是要进入
Sleep阻塞
,是通过前一个节点的状态来判断
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
# 如果是取消状态就直接把这个节点给删了
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
- parkAndCheckInterrupt 睡眠
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
- release : 释放资源,如果释放成功,则唤醒其他node
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
- unparkSuccessor 唤醒一个Node,继续循环
acquireQueued
方法。如果这个Node已经取消了,那么从尾部找一个可用的唤醒
,为什么是从尾部找?看enq
方法解释
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
- cancelAcquire 将中断的线程踢出队列。
测试过程中只有 doAcquireInterruptibly才能执行 acquireQueued执行不到
非独占
- acquireShared 子类尝试获取资源,如果失败 <0 的话,准备入队列
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
- doAcquireShared 先创建节点
addWaiter
, 然后再次尝试获取资源tryAcquireShared
如果失败再判断状态shouldParkAfterFailedAcquire
,然后睡眠parkAndCheckInterrupt
. 非独占这里很多都和独占公用方法。
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
- releaseShared 释放资源,先让子类尝试释放资源,如果成功则唤醒其他等待线程
doReleaseShared
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
- doReleaseShared 找到前置节点为
SIGNAL
也就是自己状态正确的节点唤醒
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
AQS子类实现
CountDownLatch
CountDownLatch 属于共享锁
,先创建 N个资源,然后自己阻塞的等待什么时候资源变成0.
public static void main(String[] args) throws Exception {
CountDownLatch latch = new CountDownLatch(2);
for (int i = 0; i < 2; i++) {
new Thread(()->{
System.out.println("do something");
latch.countDown();
}).start();
}
latch.await();
System.out.println("finished");
}
-------------------CountDownLatch ------------------------
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public void countDown() {
sync.releaseShared(1);
}
await
就是尝试获取资源的自定义实现,如何算获取到资源由 CountDownLatch 自己说了算 tryAcquireShared
方法。
countDown
就是尝试释放资源,如何算获取到资源由 CountDownLatch 自己说了算 tryReleaseShared
方法。
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
CountDownLatch 对于 获取到资源
的定义就是 资源什么时候等于0就算获取成功。
对于 释放资源
的逻辑是CAS对状态 -1 操作,直到资源变为0了才会通知AQS唤醒等待的线程
Semaphore
Semaphore属于共享锁
思想是 一开始创建N个资源, 多个线程尝试去消费资源 acquire
, 如果资源不够就阻塞。消费结束之后释放资源 release
public static void main(String[] args) throws Exception {
Semaphore semaphore = new Semaphore(10);
semaphore.acquire(4);
semaphore.release(2);
}
# acquire 最终调用这个
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
# release 最终调用这个
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
ReentrantLock
独占锁, 思想是每个线程都 尝试将资源设置为1(如果是自身线程,则往上叠加,可重入的概念)
, 释放资源的时候 就是将资源设置为0的时候就唤醒其他线程。
public static void main(String[] args) throws Exception {
ReentrantLock lock = new ReentrantLock();
lock.lock();
lock.unlock();
}
-------------------------------------------------------------------------------
# lock 的逻辑最终调用
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;
}
# unlock的逻辑最终调用
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;
}